diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 77640c4..34ee467 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -32,4 +32,4 @@ jobs: run: dotnet build -v quiet --configuration Release --no-restore - name: Test Solution - run: dotnet test --nologo --no-build --filter RunsOn=Remote + run: dotnet test --nologo diff --git a/Consequences.sln b/Consequences.sln index 4915721..aff0f90 100644 --- a/Consequences.sln +++ b/Consequences.sln @@ -3,13 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consequences", "Consequences\Consequences.csproj", "{CA11B719-FD8E-46EE-8938-C7E7C36F0DB8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consequences", "Consequences\Consequences.csproj", "{CA11B719-FD8E-46EE-8938-C7E7C36F0DB8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D661B25B-F61F-48AA-B626-09DDFC8FB7E6}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .github\workflows\CI.yml = .github\workflows\CI.yml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsequencesTest", "ConsequencesTest\ConsequencesTest.csproj", "{95BCC49B-7780-41E9-8365-C51B5E1B3D5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,6 +23,10 @@ Global {CA11B719-FD8E-46EE-8938-C7E7C36F0DB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {CA11B719-FD8E-46EE-8938-C7E7C36F0DB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {CA11B719-FD8E-46EE-8938-C7E7C36F0DB8}.Release|Any CPU.Build.0 = Release|Any CPU + {95BCC49B-7780-41E9-8365-C51B5E1B3D5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95BCC49B-7780-41E9-8365-C51B5E1B3D5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95BCC49B-7780-41E9-8365-C51B5E1B3D5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95BCC49B-7780-41E9-8365-C51B5E1B3D5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Consequences/Consequences/IBBoxStreamingProcessor.cs b/Consequences/Consequences/IBBoxStreamingProcessor.cs new file mode 100644 index 0000000..d7d036d --- /dev/null +++ b/Consequences/Consequences/IBBoxStreamingProcessor.cs @@ -0,0 +1,7 @@ +using USACE.HEC.Geography; + +namespace USACE.HEC.Consequences; +public interface IBBoxStreamingProcessor +{ + public void Process(BoundingBox boundingBox, Action consequenceReceptorProcess); +} diff --git a/Consequences/Consequences/IConsequencesReceptor.cs b/Consequences/Consequences/IConsequencesReceptor.cs index ba9c1af..f694280 100644 --- a/Consequences/Consequences/IConsequencesReceptor.cs +++ b/Consequences/Consequences/IConsequencesReceptor.cs @@ -1,8 +1,10 @@ -using USACE.HEC.Hazards; +using USACE.HEC.Geography; +using USACE.HEC.Hazards; using USACE.HEC.Results; namespace USACE.HEC.Consequences; public interface IConsequencesReceptor { public Result Compute(IHazard hi); + public Location GetLocation(); } diff --git a/Consequences/Consequences/NSIStreamingProcessor.cs b/Consequences/Consequences/NSIStreamingProcessor.cs new file mode 100644 index 0000000..0939417 --- /dev/null +++ b/Consequences/Consequences/NSIStreamingProcessor.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using USACE.HEC.Geography; + +namespace USACE.HEC.Consequences; +public class NSIStreamingProcessor : IBBoxStreamingProcessor +{ + public void Process(BoundingBox boundingBox, Action ConsequenceReceptorProcess) + { + Structure s = new Structure(); + ConsequenceReceptorProcess(s); + } + + + + /* + static async Task Test() + { + // Define the API endpoint + string apiEndpoint = "https://nsi.sec.usace.army.mil/nsiapi/structures?bbox=-81.58418,30.25165,-81.58161,30.26939,-81.55898,30.26939,-81.55281,30.24998,-81.58418,30.25165"; + + // Create an instance of HttpClient + using (HttpClient client = new HttpClient()) + { + try + { + // Make the GET request + HttpResponseMessage response = await client.GetAsync(apiEndpoint); + + // Check if the request was successful + response.EnsureSuccessStatusCode(); + + // Read and process the response content + string responseBody = await response.Content.ReadAsStringAsync(); + + // Output the response content (for demonstration purposes) + Console.WriteLine("API Response:"); + Console.WriteLine(responseBody); + } + catch (HttpRequestException e) + { + // Handle any errors that occur during the request + Console.WriteLine($"Request error: {e.Message}"); + } + } + } + */ +} diff --git a/Consequences/Consequences/Structure.cs b/Consequences/Consequences/Structure.cs new file mode 100644 index 0000000..7e6b9ab --- /dev/null +++ b/Consequences/Consequences/Structure.cs @@ -0,0 +1,106 @@ +using System.Text.Json.Serialization; +using USACE.HEC.Geography; +using USACE.HEC.Hazards; +using USACE.HEC.Results; + +namespace USACE.HEC.Consequences; +public class Structure : IConsequencesReceptor +{ + [JsonPropertyName("fd_id")] + public int Name { get; set; } + + [JsonPropertyName("st_damcat")] + public string DamCat { get; set; } + + [JsonPropertyName("cbfips")] + public string CBFips { get; set; } + + [JsonPropertyName("x")] + public double X { get; set; } + + [JsonPropertyName("y")] + public double Y { get; set; } + + [JsonPropertyName("ground_elv")] + public double GroundElevation { get; set; } + + [JsonPropertyName("occtype")] + public string Occtype { get; set; } + + [JsonPropertyName("found_type")] + public string FoundationType { get; set; } + + [JsonPropertyName("firmzone")] + public string FirmZone { get; set; } + + [JsonPropertyName("bldgtype")] + public string ConstructionType { get; set; } + + [JsonPropertyName("val_struct")] + public double StructVal { get; set; } + + [JsonPropertyName("val_cont")] + public double ContVal { get; set; } + + [JsonPropertyName("found_ht")] + public double FoundHt { get; set; } + + [JsonPropertyName("num_story")] + public int NumStories { get; set; } + + [JsonPropertyName("pop2pmo65")] + public int Popo65day { get; set; } + + [JsonPropertyName("pop2pmu65")] + public int Popu65day { get; set; } + + [JsonPropertyName("pop2amo65")] + public int Popo65night { get; set; } + + [JsonPropertyName("pop2amu65")] + public int Popu65night { get; set; } + + public Location GetLocation() + { + return new Location { X = X, Y = Y }; + } + + public Result Compute(IHazard hazard) + { + List resultItems = new List(); + + if (hazard.Has(HazardParameter.Depth)) + { + resultItems.Add(new ResultItem { + ResultName = "Depth", + Result = hazard.Get(HazardParameter.Depth) + }); + } + + if (hazard.Has(HazardParameter.Velocity)) + { + resultItems.Add(new ResultItem { + ResultName = "Velocity", + Result = hazard.Get(HazardParameter.Velocity) + }); + } + + if (hazard.Has(HazardParameter.ArrivalTime)) + { + resultItems.Add(new ResultItem { + ResultName = "ArrivalTime", + Result = hazard.Get(HazardParameter.ArrivalTime) + }); + } + + if (hazard.Has(HazardParameter.ArrivalTime2ft)) + { + resultItems.Add(new ResultItem { + ResultName = "ArrivalTime2ft", + Result = hazard.Get(HazardParameter.ArrivalTime2ft) + }); + } + + return new Result(resultItems.ToArray()); + } +} diff --git a/Consequences/Geography/BoundingBox.cs b/Consequences/Geography/BoundingBox.cs new file mode 100644 index 0000000..d5d2e0f --- /dev/null +++ b/Consequences/Geography/BoundingBox.cs @@ -0,0 +1,30 @@ +namespace USACE.HEC.Geography; +public class BoundingBox +{ + public Location UpperLeft { get; } + public Location LowerRight { get; } + + public BoundingBox(Location upperLeft, Location lowerRight) + { + UpperLeft = upperLeft; + LowerRight = lowerRight; + } + + public string NSIFormat() + { + return string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}", + UpperLeft.X, UpperLeft.Y, + LowerRight.X, UpperLeft.Y, + LowerRight.X, LowerRight.Y, + UpperLeft.X, LowerRight.Y, + UpperLeft.X, UpperLeft.Y); + } + + // not sure about this functionality right now, just a placeholder + public string GDALFormat() + { + return string.Format("{0}{1}{2}{3}", + UpperLeft.X, UpperLeft.Y, + LowerRight.X, LowerRight.X); + } +} diff --git a/Consequences/Geography/Location.cs b/Consequences/Geography/Location.cs new file mode 100644 index 0000000..d84006d --- /dev/null +++ b/Consequences/Geography/Location.cs @@ -0,0 +1,6 @@ +namespace USACE.HEC.Geography; +public struct Location +{ + public double X; + public double Y; +} diff --git a/Consequences/Hazards/DepthHazard.cs b/Consequences/Hazards/DepthHazard.cs new file mode 100644 index 0000000..57fd137 --- /dev/null +++ b/Consequences/Hazards/DepthHazard.cs @@ -0,0 +1,36 @@ +namespace USACE.HEC.Hazards; +public class DepthHazard : IHazard +{ + private float _depth; + public DepthHazard(float depth) + { + _depth = depth; + } + + public bool Has(HazardParameter hp) + { + return (hp & HazardParameter.Depth) == hp; + } + + public T Get(HazardParameter hp) + { + // assumes get will always ask for a single parameter + // passing in a compound HazardParameter will throw an exception + if (typeof(T) == typeof(float)) + { + if (hp == HazardParameter.Depth) + { + // compiler cannot prove T is float at compile time + return (T)(object)_depth; + } + else + { + throw new NotSupportedException(); + } + } + else + { + throw new InvalidCastException(); + } + } +} diff --git a/Consequences/Hazards/IHazardProvider.cs b/Consequences/Hazards/IHazardProvider.cs new file mode 100644 index 0000000..dc2561c --- /dev/null +++ b/Consequences/Hazards/IHazardProvider.cs @@ -0,0 +1,9 @@ +using USACE.HEC.Geography; + +namespace USACE.HEC.Hazards; + +public interface IHazardProvider +{ + public BoundingBox Extent { get; set; } + public IHazard Hazard(Location location); +} diff --git a/Consequences/Hazards/LifeLossHazard.cs b/Consequences/Hazards/LifeLossHazard.cs new file mode 100644 index 0000000..1108cfa --- /dev/null +++ b/Consequences/Hazards/LifeLossHazard.cs @@ -0,0 +1,64 @@ +namespace USACE.HEC.Hazards; +public class LifeLossHazard : IHazard +{ + private float _depth; + private float _velocity; + private DateTime _time; + + public LifeLossHazard(float depth, float velocity, DateTime time) + { + _depth = depth; + _velocity = velocity; + _time = time; + } + + public bool Has(HazardParameter hp) + { + // compound HazardParameter representing all three parameters + HazardParameter combinedHP = HazardParameter.Depth | + HazardParameter.Velocity | + HazardParameter.ArrivalTime2ft; + return (hp & combinedHP) == hp; + } + + public T Get(HazardParameter hp) + { + // assumes get will always ask for a single parameter + // passing in a compound HazardParameter will throw an exception + if (typeof(T) == typeof(float)) + { + // check for valid float-typed parameters + if (hp == HazardParameter.Depth) + { + return (T)(object)_depth; + } + else if (hp == HazardParameter.Velocity) + { + return (T)(object)_velocity; + } + else + { + throw new NotSupportedException(); + } + } + else if (typeof(T) == typeof(DateTime)) + { + // check for valid DateTime-typed parameters + if (hp == HazardParameter.ArrivalTime2ft) + { + return (T)(object)_time; + } + else + { + throw new NotSupportedException(); + } + } + else + { + throw new InvalidCastException(); + } + + + + } +} diff --git a/Consequences/Hazards/RandomDepthHazardProvider.cs b/Consequences/Hazards/RandomDepthHazardProvider.cs new file mode 100644 index 0000000..f2f8f96 --- /dev/null +++ b/Consequences/Hazards/RandomDepthHazardProvider.cs @@ -0,0 +1,27 @@ +using USACE.HEC.Geography; + +namespace USACE.HEC.Hazards; +public class RandomDepthHazardProvider : IHazardProvider +{ + private BoundingBox _bbox; + + private Random _rng; + + public RandomDepthHazardProvider(int seed) + { + _rng = new Random(seed); + } + + public BoundingBox Extent + { + get { return _bbox; } + set { _bbox = value; } + } + + public IHazard Hazard(Location location) + { + // generate random depth float between 1 and 10 + double depth = 1.0 + (_rng.NextDouble() * 9.0); + return new DepthHazard((float)depth); + } +} diff --git a/Consequences/Results/ConsoleWriter.cs b/Consequences/Results/ConsoleWriter.cs new file mode 100644 index 0000000..50f550f --- /dev/null +++ b/Consequences/Results/ConsoleWriter.cs @@ -0,0 +1,71 @@ +using System.Text; + +namespace USACE.HEC.Results; +public class ConsoleWriter : IResultsWriter +{ + private bool hasHeaderWritten = false; + private List headers = new List(); + + // check to make sure the result being written matches the headers already written + // assumes headers are in the same order + private void CheckIfSameHeaders(Result res) + { + // different number of headers + if (res.ResultItems.Length != headers.Count) + { + throw new InvalidOperationException(); + } + for (int i = 0; i < headers.Count; i++) + { + // headers do not match + if (res.ResultItems[i].ResultName != headers[i]) + { + throw new InvalidOperationException(); + } + } + } + + public string WriteString(Result res) + { + StringBuilder output = new StringBuilder(); + if (!hasHeaderWritten) + { + // write the headers to the top of the file + for (int i = 0; i < res.ResultItems.Length; i++) + { + //sb.Append(res.GetResultItems()[i].ResultName); + output.Append(res.ResultItems[i].ResultName); + if (i < res.ResultItems.Length - 1) + { + output.Append(','); + } + headers.Add(res.ResultItems[i].ResultName); + } + output.Append("\r\n"); + hasHeaderWritten = true; + } + CheckIfSameHeaders(res); + foreach (string header in headers) + { + object val = res.Fetch(header).Result; + output.Append(val.ToString()); + if (header != headers.Last()) + { + output.Append(','); + } + } + output.Append("\r\n"); + return output.ToString(); + } + + public void Write(Result res) + { + Console.Write(WriteString(res)); + } + + + public void Dispose() + { + Console.WriteLine("END OF FILE"); + } +} diff --git a/Consequences/Results/IResultsWriter.cs b/Consequences/Results/IResultsWriter.cs new file mode 100644 index 0000000..df00e87 --- /dev/null +++ b/Consequences/Results/IResultsWriter.cs @@ -0,0 +1,5 @@ +namespace USACE.HEC.Results; +public interface IResultsWriter : IDisposable +{ + public void Write(Result res); +} diff --git a/Consequences/Results/Result.cs b/Consequences/Results/Result.cs index 55d1a05..bf4004d 100644 --- a/Consequences/Results/Result.cs +++ b/Consequences/Results/Result.cs @@ -1,19 +1,19 @@ namespace USACE.HEC.Results; public class Result { - private ResultItem[] _results; + public ResultItem[] ResultItems { get; } - public Result(ResultItem[] results) + public Result(ResultItem[] resultItems) { - _results = results; + ResultItems = resultItems; } // retrieve a ResultItem from a Result by name public ResultItem Fetch(string name) { - for (int i = 0; i < _results.Length; i++) - if (_results[i].ResultName == name) - return _results[i]; + for (int i = 0; i < ResultItems.Length; i++) + if (ResultItems[i].ResultName == name) + return ResultItems[i]; // return empty ResultItem if not found ResultItem item = new ResultItem(); item.ResultName = name; diff --git a/ConsequencesTest/BoundingBoxTest.cs b/ConsequencesTest/BoundingBoxTest.cs new file mode 100644 index 0000000..873b7a6 --- /dev/null +++ b/ConsequencesTest/BoundingBoxTest.cs @@ -0,0 +1,18 @@ +using USACE.HEC.Geography; + +namespace ConsequencesTest; +public class BoundingBoxTest +{ + [Fact] + public void NSIFormat_ArbitraryLocation_ReturnCorrectFormat() + { + Location upperLeft = new Location { X = -40, Y = 50 }; + Location lowerRight = new Location { X = 60, Y = -50 }; + BoundingBox boundingBox = new BoundingBox(upperLeft, lowerRight); + + string NSIFormat = boundingBox.NSIFormat(); + + string expected = "-40,50,60,50,60,-50,-40,-50,-40,50"; + Assert.Equal(expected, NSIFormat); + } +} diff --git a/ConsequencesTest/ConsequencesTest.csproj b/ConsequencesTest/ConsequencesTest.csproj new file mode 100644 index 0000000..568518b --- /dev/null +++ b/ConsequencesTest/ConsequencesTest.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/ConsequencesTest/ConsoleWriterTest.cs b/ConsequencesTest/ConsoleWriterTest.cs new file mode 100644 index 0000000..5a292ba --- /dev/null +++ b/ConsequencesTest/ConsoleWriterTest.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestPlatform.Utilities; +using USACE.HEC.Results; + +namespace ConsequencesTest; +public class ConsoleWrite +{ + static ResultItem r1 = new ResultItem { ResultName = "Depth", Result = 1.03f }; + static ResultItem r2 = new ResultItem { ResultName = "Velocity", Result = 2.02f }; + static ResultItem r3 = new ResultItem { ResultName = "ArrivalTime2ft", Result = new DateTime() }; + static ResultItem[] resultItems = { r1, r2, r3 }; + Result res = new Result(resultItems); + [Fact] + public void Write_PrintsHeadersOnce() + { + string headers = "Depth,Velocity,ArrivalTime2ft\r\n"; + string row1 = "1.03,2.02,1/1/0001 12:00:00 AM\r\n"; + // string eof = "END OF FILE\r\n"; + string output = ""; + ConsoleWriter cw = new ConsoleWriter(); + + // changed the tests to test strings and not direct console output + using (cw) + { + output += cw.WriteString(res); + output += cw.WriteString(res); + } + // can't test EOF here because it is written to console and not a string, but can confirm + // it is written to console + Assert.Equal(headers + row1 + row1, output); + } + + [Fact] + public void Write_InvalidRowWritten_ThrowsException() + { + // Result with headers that do not match res + ResultItem[] bad = { r1, r1, r3 }; + Result invalidResult = new Result(bad); + + ConsoleWriter cw = new ConsoleWriter(); + cw.WriteString(res); + + // throw exception when adding a row with differing headers + Assert.Throws(() => cw.WriteString(invalidResult)); + } +} diff --git a/ConsequencesTest/DepthHazardTest.cs b/ConsequencesTest/DepthHazardTest.cs new file mode 100644 index 0000000..b9578ef --- /dev/null +++ b/ConsequencesTest/DepthHazardTest.cs @@ -0,0 +1,52 @@ +using USACE.HEC.Hazards; + +namespace ConsequencesTest; +public class DepthHazardTest +{ + static float input = 1.01f; + IHazard dh = new DepthHazard(input); + + [Fact] + public void Has_ValidParameter_ReturnsTrue() + { + Assert.True(dh.Has(HazardParameter.Depth)); + } + + [Fact] + public void Has_InvalidParameter_ReturnsFalse() + { + Assert.False(dh.Has(HazardParameter.Velocity)); + Assert.False(dh.Has(HazardParameter.ArrivalTime)); + Assert.False(dh.Has(HazardParameter.ArrivalTime2ft)); + // compound parameter, must have all individual parameters to pass + Assert.False(dh.Has(HazardParameter.ArrivalTime | HazardParameter.Depth)); + } + + [Fact] + public void Get_ValidParameter_ReturnValue() + { + Assert.Equal(input, dh.Get(HazardParameter.Depth)); + } + + [Fact] + public void Get_ValidParameter_ValueNotEqualToRandom() + { + Assert.NotEqual(input + 0.1f, dh.Get(HazardParameter.Depth)); + Assert.NotEqual(input * 1.23f, dh.Get(HazardParameter.Depth)); + } + + [Fact] + public void Get_InvalidParameter_ThrowNotSupported() + { + Assert.Throws(() => dh.Get(HazardParameter.ArrivalTime)); + Assert.Throws(() => dh.Get(HazardParameter.Velocity)); + + } + + [Fact] + public void Get_InvalidType_ThrowInvalidCast() + { + Assert.Throws(() => dh.Get(HazardParameter.Depth)); + Assert.Throws(() => dh.Get(HazardParameter.Depth)); + } +} diff --git a/ConsequencesTest/LifeLossHazardTest.cs b/ConsequencesTest/LifeLossHazardTest.cs new file mode 100644 index 0000000..d9a0fce --- /dev/null +++ b/ConsequencesTest/LifeLossHazardTest.cs @@ -0,0 +1,59 @@ +using USACE.HEC.Hazards; + +namespace ConsequencesTest; +public class LifeLossHazardTest +{ + static float depth = 1.01f; + static float velocity = 6.03f; + static DateTime time = new DateTime(2024, 8, 10); + IHazard llh = new LifeLossHazard(depth, velocity, time); + + [Fact] + public void Has_ValidParameter_ReturnsTrue() + { + Assert.True(llh.Has(HazardParameter.Depth)); + Assert.True(llh.Has(HazardParameter.Velocity)); + Assert.True(llh.Has(HazardParameter.ArrivalTime2ft)); + // compound parameter, must have all individual parameters to pass + Assert.True(llh.Has(HazardParameter.Depth | HazardParameter.Velocity)); + } + + [Fact] + public void Has_InvalidParameter_ReturnsFalse() + { + Assert.False(llh.Has(HazardParameter.ArrivalTime)); + // compound parameter, must have all individual parameters to pass + Assert.False(llh.Has(HazardParameter.ArrivalTime | HazardParameter.Depth)); + } + + [Fact] + public void Get_ValidParameter_ReturnValue() + { + Assert.Equal(depth, llh.Get(HazardParameter.Depth)); + Assert.Equal(velocity, llh.Get(HazardParameter.Velocity)); + Assert.Equal(time, llh.Get(HazardParameter.ArrivalTime2ft)); + } + + [Fact] + public void Get_ValidParameter_ValueNotEqualToRandom() + { + Assert.NotEqual(depth + 0.1f, llh.Get(HazardParameter.Depth)); + Assert.NotEqual(velocity + 0.1f, llh.Get(HazardParameter.Velocity)); + Assert.NotEqual(DateTime.Now, llh.Get(HazardParameter.ArrivalTime2ft)); + } + + [Fact] + public void Get_InvalidParameter_ThrowNotSupported() + { + Assert.Throws(() => llh.Get(HazardParameter.ArrivalTime)); + Assert.Throws(() => llh.Get(HazardParameter.Velocity | HazardParameter.Depth)); + } + + [Fact] + public void Get_InvalidType_ThrowInvalidCast() + { + Assert.Throws(() => llh.Get(HazardParameter.Depth)); + Assert.Throws(() => llh.Get(HazardParameter.Velocity)); + } +} + diff --git a/ConsequencesTest/NSIStreamingProcessorTest.cs b/ConsequencesTest/NSIStreamingProcessorTest.cs new file mode 100644 index 0000000..f7de2c8 --- /dev/null +++ b/ConsequencesTest/NSIStreamingProcessorTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization; +using USACE.HEC.Consequences; +using USACE.HEC.Geography; +using USACE.HEC.Hazards; +using USACE.HEC.Results; + +namespace ConsequencesTest; +public class NSIStreamingProcessorTest +{ + [Fact] + public void Test() + { + IBBoxStreamingProcessor sp = new NSIStreamingProcessor(); + // int i = 0; + IHazardProvider depthHazardProvider = new RandomDepthHazardProvider(25); + IResultsWriter consoleWriter = new ConsoleWriter(); + + sp.Process(depthHazardProvider.Extent, (IConsequencesReceptor s) => { + Result r = s.Compute(depthHazardProvider.Hazard(s.GetLocation())); + consoleWriter.Write(r); + }); + } +} diff --git a/ConsequencesTest/RandomDepthHazardProviderTest.cs b/ConsequencesTest/RandomDepthHazardProviderTest.cs new file mode 100644 index 0000000..65e0299 --- /dev/null +++ b/ConsequencesTest/RandomDepthHazardProviderTest.cs @@ -0,0 +1,37 @@ +using USACE.HEC.Geography; +using USACE.HEC.Hazards; + +namespace ConsequencesTest; +public class RandomDepthHazardProviderTest +{ + [Fact] + public void Extent_0000_CreateCorrectBox() + { + Location location1 = new Location { X = 0, Y = 0 }; + Location location2 = new Location { X = 0, Y = 0 }; + IHazardProvider depthProvider = new RandomDepthHazardProvider(26) { Extent = new BoundingBox(location1, location2) }; + + BoundingBox box = depthProvider.Extent; + + Assert.Equal(0, box.UpperLeft.X); + Assert.Equal(0, box.UpperLeft.Y); + Assert.Equal(0, box.LowerRight.X); + Assert.Equal(0, box.LowerRight.Y); + } + + [Fact] + public void Hazard_RandomDepthHazards_ConsistentDepthValues() + { + // seeded randomly generated depths to ensure consistency when testing + int seed = 26; + IHazardProvider randomDepthProvider = new RandomDepthHazardProvider(seed); + Location location1 = new Location { X = 100, Y = 50 }; + Location location2 = new Location { X = 0, Y = 40 }; + + IHazard depthHazard1 = randomDepthProvider.Hazard(location1); + IHazard depthHazard2 = randomDepthProvider.Hazard(location2); + + Assert.Equal(3.78371286f, depthHazard1.Get(HazardParameter.Depth)); + Assert.Equal(5.01588488f, depthHazard2.Get(HazardParameter.Depth)); + } +} diff --git a/ConsequencesTest/StructureTest.cs b/ConsequencesTest/StructureTest.cs new file mode 100644 index 0000000..9e4b71b --- /dev/null +++ b/ConsequencesTest/StructureTest.cs @@ -0,0 +1,118 @@ +using USACE.HEC.Results; +using USACE.HEC.Consequences; +using USACE.HEC.Hazards; +using Microsoft.VisualStudio.TestPlatform.Utilities; + +namespace ConsequencesTest; +public class StructureTest +{ + [Fact] + public void Compute_SimpleDepth() + { + Structure s = new Structure(); + IHazard dh = new DepthHazard(4.56f); + + Result res = s.Compute(dh); + ResultItem item1 = res.Fetch("Depth"); + + Assert.Equal("Depth", item1.ResultName); + Assert.Equal(typeof(float), item1.Result.GetType()); + Assert.Equal(4.56f, item1.Result); + } + + [Fact] + public void Compute_ArrayDepth_CorrectConsoleOutput() + { + Structure s = new Structure(); + DepthHazard[] depthHazardArray = + { + new DepthHazard(3.45f), + new DepthHazard(6.89f), + new DepthHazard(42.6f), + new DepthHazard(0.001f), + new DepthHazard(5.55f), + new DepthHazard(100.45f), + new DepthHazard(0.2f), + new DepthHazard(23.23f) + }; + string expectedConsoleOutput = "Depth\r\n"; + string actualOutput = ""; + ConsoleWriter cw = new ConsoleWriter(); + + using (cw) + { + foreach (DepthHazard depthHazard in depthHazardArray) + { + Result res = s.Compute(depthHazard); + + ResultItem depthItem = res.Fetch("Depth"); + Assert.Equal("Depth", depthItem.ResultName); + Assert.Equal(typeof(float), depthItem.Result.GetType()); + Assert.Equal(depthHazard.Get(HazardParameter.Depth), depthItem.Result); + + expectedConsoleOutput += depthItem.Result.ToString() + "\r\n"; + actualOutput += cw.WriteString(res); + } + } + // can't test EOF here because it is written to console and not a string, but can confirm + // it is written to console + // expectedConsoleOutput += "END OF FILE\r\n"; + + Assert.Equal(expectedConsoleOutput, actualOutput); + } + + + [Fact] + public void Compute_ArrayLifeLoss_CorrectConsoleOutput() + { + Structure s = new Structure(); + LifeLossHazard[] lifeLossHazardArray = + { + new LifeLossHazard(3.45f, 52.6f, new DateTime(2024, 8, 20)), + new LifeLossHazard(6.89f, 5.6f, new DateTime(1999, 9, 12)), + new LifeLossHazard(42.6f, 12.2f, new DateTime(2000, 10, 3)), + new LifeLossHazard(0.001f, 0.0002f, new DateTime(2002, 1, 18)), + new LifeLossHazard(5.55f, 90.4f, new DateTime(1600, 3, 3)), + new LifeLossHazard(100.45f, 1.5f, new DateTime(1492, 6, 7)), + new LifeLossHazard(0.2f, 5.55f, new DateTime(1989, 4, 16)), + new LifeLossHazard(23.23f, 8.88f, new DateTime(1800, 7, 25)) + }; + string expectedConsoleOutput = "Depth,Velocity,ArrivalTime2ft\r\n"; + string actualOutput = ""; + ConsoleWriter cw = new ConsoleWriter(); + + using (cw) + { + foreach (LifeLossHazard lifeLossHazard in lifeLossHazardArray) + { + Result res = s.Compute(lifeLossHazard); + + ResultItem depthItem = res.Fetch("Depth"); + Assert.Equal("Depth", depthItem.ResultName); + Assert.Equal(typeof(float), depthItem.Result.GetType()); + Assert.Equal(lifeLossHazard.Get(HazardParameter.Depth), depthItem.Result); + + ResultItem velocityItem = res.Fetch("Velocity"); + Assert.Equal("Velocity", velocityItem.ResultName); + Assert.Equal(typeof(float), velocityItem.Result.GetType()); + Assert.Equal(lifeLossHazard.Get(HazardParameter.Velocity), velocityItem.Result); + + ResultItem arrivalTime2ftItem = res.Fetch("ArrivalTime2ft"); + Assert.Equal("ArrivalTime2ft", arrivalTime2ftItem.ResultName); + Assert.Equal(typeof(DateTime), arrivalTime2ftItem.Result.GetType()); + Assert.Equal(lifeLossHazard.Get(HazardParameter.ArrivalTime2ft), arrivalTime2ftItem.Result); + + expectedConsoleOutput += depthItem.Result.ToString() + ','; + expectedConsoleOutput += velocityItem.Result.ToString() + ','; + expectedConsoleOutput += arrivalTime2ftItem.Result.ToString() + "\r\n"; + actualOutput += cw.WriteString(res); + } + } + // can't test EOF here because it is written to console and not a string, but can confirm + // it is written to console + // expectedConsoleOutput += "END OF FILE\r\n"; + + Assert.Equal(expectedConsoleOutput, actualOutput); + } + +}