diff --git a/Library/App.config b/Library/App.config new file mode 100644 index 0000000..5754728 --- /dev/null +++ b/Library/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Library/EQData.cs b/Library/EQData.cs new file mode 100644 index 0000000..24ab0b7 --- /dev/null +++ b/Library/EQData.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Library { + public class EQData { + // https://earthquake.usgs.gov/data/comcat/index.php + public DateTime EventTime { get; private set; } + public double Latitude { get; private set; } + public double Longitude { get; private set; } + + public double Magnitude { get; private set; } + public string EventId { get; private set; } + public string Place { get; private set; } + + public EQData(string timeStr, + string latitudeStr, + string longitudeStr, + string magnitudeStr, + string idStr, + string placeStr) { + EventTime = DateTime.Parse(timeStr); + Latitude = double.Parse(latitudeStr); + Longitude = double.Parse(longitudeStr); + Magnitude = double.Parse(magnitudeStr); + EventId = idStr; + Place = placeStr; + } + } +} diff --git a/Library/EQDataFrame.cs b/Library/EQDataFrame.cs new file mode 100644 index 0000000..badf3de --- /dev/null +++ b/Library/EQDataFrame.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Library { + public class EQDataFrame { + Dictionary eqMap = new Dictionary(); // + Dictionary> eqIdsByEventDate = new Dictionary>(); // + + // https://en.wikipedia.org/wiki/Haversine_formula + double CalcDistBetweenTwoLocationsInMiles(double latA, double longA, + double latB, double longB) { + double latDiffHalf = (latB - latA) * 0.5; + double latSumHalf = (latB + latA) * 0.5; + double longDiffHalf = (longB - longA) * 0.5; + + double sinSqLatDiffHalf = Math.Sin(latDiffHalf) * Math.Sin(latDiffHalf); + double sinSqLatSumHalf = Math.Sin(latSumHalf) * Math.Sin(latSumHalf); + double sinSqLongDiffHalf = Math.Sin(longDiffHalf) * Math.Sin(longDiffHalf); + + const double EarthRad = 3959.0; + double dist = 2.0 * EarthRad * Math.Asin(Math.Sqrt(sinSqLatDiffHalf + (1.0 - sinSqLatDiffHalf - sinSqLatSumHalf) * sinSqLongDiffHalf)); + return dist; + } + + // return null if error + public List QueryEndpoint(double eq_lat, double eq_long, DateTime eq_start_date, DateTime eq_end_date) { + // https://earthquake.usgs.gov/data/comcat/index.php + if (eq_lat < -90.0 || eq_lat > 90.0) + return null; + else if (eq_long < -180.0 || eq_long > 180.0) + return null; + else if (eq_end_date < eq_start_date) + return null; + + List ret = new List(); + + // to store the day of the date + DateTime searchDateStart = eq_start_date.Subtract(eq_start_date.TimeOfDay); + DateTime searchDateEnd = eq_end_date.Subtract(eq_end_date.TimeOfDay); + + int durationInDays = (searchDateEnd - searchDateStart).Duration().Days + 1; + DateTime searchDate = searchDateStart; + for (int i = 0; i < durationInDays; ++ i) { + List eqsOnDay; + if (!eqIdsByEventDate.TryGetValue(searchDate, out eqsOnDay) + || eqsOnDay == null + || eqsOnDay.Count == 0) + continue; + foreach (var id in eqsOnDay) { + var eqData = eqMap[id]; + if (eqData == null) + continue; + + double dist = CalcDistBetweenTwoLocationsInMiles(eqData.Latitude, eqData.Longitude, + eq_lat, eq_long); + if (dist > (eqData.Magnitude * 100.0)) + continue; + ret.Add(eqData); + } + searchDate = searchDate.AddDays(1.0); // walk through the days + } + + ret.Sort(delegate(EQData x, EQData y) { + return y.EventTime.CompareTo(x.EventTime); + }); + return ret; + } + + public void ParseLine(string lineToParse) { + if (lineToParse == null) + return; + + // to handle cases of commas in quote + // https://stackoverflow.com/a/48275050 + //this regular expression splits string on the separator character NOT inside double quotes. + //separatorChar can be any character like comma or semicolon etc. + //it also allows single quotes inside the string value: e.g. "Mike's Kitchen","Jane's Room" + Regex regx = new Regex("," + "(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))"); + string[] splitted = regx.Split(lineToParse); + //string[] splitted = lineToParse.Split(','); + if (splitted[14] != "earthquake") // event type + return; + + string timeStr = splitted[0]; + string latitudeStr = splitted[1]; + string longitudeStr = splitted[2]; + string magnitudeStr = splitted[4]; + string idStr = splitted[11]; + string placeStr = splitted[13]; + + if (timeStr == string.Empty + || latitudeStr == string.Empty + || longitudeStr == string.Empty + || magnitudeStr == string.Empty + || idStr == string.Empty) + return; + + var eqData = new EQData(timeStr: timeStr, + latitudeStr: latitudeStr, + longitudeStr: longitudeStr, + magnitudeStr: magnitudeStr, + idStr: idStr, + placeStr: placeStr); + eqMap.Add(eqData.EventId, eqData); + + DateTime timeToStore = eqData.EventTime.Subtract(eqData.EventTime.TimeOfDay); + if (!eqIdsByEventDate.ContainsKey(timeToStore)) { + var eqs = new List(); + eqs.Add(eqData.EventId); + eqIdsByEventDate.Add(timeToStore, eqs); + } else { + var eqs = eqIdsByEventDate[timeToStore]; + eqs.Add(eqData.EventId); + eqIdsByEventDate[timeToStore] = eqs; + } + } + } +} diff --git a/Library/EQNancyModule.cs b/Library/EQNancyModule.cs new file mode 100644 index 0000000..b0362ab --- /dev/null +++ b/Library/EQNancyModule.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nancy; +using System.IO; + +namespace Library { + public class EQNancyModule : NancyModule { + public EQNancyModule() { + EQDataFrame dataFrame = new EQDataFrame(); + using (var sr = new StreamReader("../../../all_month.csv")) { + string line; + bool isFirstLine = true; + while ((line = sr.ReadLine()) != null) { + if (!isFirstLine) + dataFrame.ParseLine(line); + else + isFirstLine = false; + } + } + + Get("/", parameters => { + var query = Request.Query; + double queryEqLong = (double)query.@long; + double queryEqLat = (double)query.lat; + string queryEqStartDate = (string)query.start_date; + string queryEqEndDate = (string)query.end_date; + + var ret = dataFrame.QueryEndpoint(queryEqLat, queryEqLong, DateTime.Parse(queryEqStartDate), DateTime.Parse(queryEqEndDate)); + if (ret == null) + return HttpStatusCode.BadRequest; + else if (ret.Count == 0) + return HttpStatusCode.NotFound; + return Response.AsJson(ret); + }); + } + } +} diff --git a/Library/Library.csproj b/Library/Library.csproj new file mode 100644 index 0000000..c9bf695 --- /dev/null +++ b/Library/Library.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {A4D3553A-6116-476B-B4B2-1CBF70912C23} + Exe + Library + Library + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + packages\Nancy.2.0.0\lib\net452\Nancy.dll + + + packages\Nancy.Hosting.Self.2.0.0\lib\net452\Nancy.Hosting.Self.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Library/Library.sln b/Library/Library.sln new file mode 100644 index 0000000..6901597 --- /dev/null +++ b/Library/Library.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Library", "Library.csproj", "{A4D3553A-6116-476B-B4B2-1CBF70912C23}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A4D3553A-6116-476B-B4B2-1CBF70912C23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4D3553A-6116-476B-B4B2-1CBF70912C23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4D3553A-6116-476B-B4B2-1CBF70912C23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4D3553A-6116-476B-B4B2-1CBF70912C23}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1B6BBC3F-6E6F-4F13-982E-11EF3EEB0CF4} + EndGlobalSection +EndGlobal diff --git a/Library/Program.cs b/Library/Program.cs new file mode 100644 index 0000000..2e83bb7 --- /dev/null +++ b/Library/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using Nancy; +using Nancy.Hosting.Self; + +namespace Library { + class Program { + static void Main(string[] args) { + // https://volkanpaksoy.com/archive/2015/11/11/building-a-simple-http-server-with-nancy/ + string url = "http://localhost"; + int port = 8000; + var server = new NancyHost(new Uri($"{url}:{port}/")); + server.Start(); + Console.WriteLine("Server is running"); + Console.ReadKey(); + server.Stop(); + } + } +} diff --git a/Library/Properties/AssemblyInfo.cs b/Library/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9fe2d4e --- /dev/null +++ b/Library/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Library")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Library")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a4d3553a-6116-476b-b4b2-1cbf70912c23")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Library/README.txt b/Library/README.txt new file mode 100644 index 0000000..c6058f1 --- /dev/null +++ b/Library/README.txt @@ -0,0 +1,18 @@ +This is README file for explanations of the challenge. + +* How to Test +1. Open the Library.sln file in Adminstrator and Build the solution. + You may need to install Nancy and Nancy.Hosting.Self from NuGet. +2. Run the application. +3. Send GET request by using the program like Postman +ex) http://localhost:8000/?lat=-5.0888&long=146.3141&start_date=2021/10/05&end_date=2021/10/08 +4. The result would be in json. + +* Description +The program uses NancyFx(Http Server) to handle request and return the response(port:8000, localhost). +When the server is up, the program parses the csv file per each line and convert the line to EQData which holds data we interest(time, lat, long, mag, place). +The collection I used is a dictionary with id as key(eqMap). +To save time, I put the ids in a dictionary by date(eqIdsByEventDate). + +When the program gets a query, it computes the range of date and search the endpoints by each day of the date. +If the event matches, add to the list to return and then sort from newest to oldest date when return the list. diff --git a/Library/packages.config b/Library/packages.config new file mode 100644 index 0000000..93ef021 --- /dev/null +++ b/Library/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file