/// <summary> /// Recursively calculates the nearest sound speed profiles along a given radial using a binary search-like algorithm /// 1. If start and end points are provided, use them, otherwise find the nearest SSP to each of those points /// 2. If the start point was calculated, add the SSP closest to the calculated start point to the enumerable /// 2. If the SSPs closest to the start and end points are within 10m of each other they are considered identical and there are /// assumed to be no more intervening points /// 3. If the SSPs closest to the start and end points are NOT within 10m of each other, calculate the midpoint of the segment /// and find the nearest SSP to that point. /// 4. If the SSP nearest the midpoint is not within 10m of the SSP nearest to the start point, recursively call this function to /// find the new midpoint between the start point and the current midpoint /// 5. Return the /// </summary> /// <param name="segment"></param> /// <param name="startDistance"></param> /// <param name="startProfile"></param> /// <param name="endProfile"></param> /// <param name="bottomProfile"></param> /// <param name="soundSpeedData"></param> /// <param name="deepestProfile"></param> /// <returns></returns> static IEnumerable<Tuple<double, SoundSpeedProfile>> ProfilesAlongRadial(GeoSegment segment, double startDistance, SoundSpeedProfile startProfile, SoundSpeedProfile endProfile, BottomProfile bottomProfile, EnvironmentData<SoundSpeedProfile> soundSpeedData, SoundSpeedProfile deepestProfile) { var returnStartProfile = false; var returnEndProfile = false; if (startProfile == null) { returnStartProfile = true; startProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment[0]).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment[0]).Extend(deepestProfile); } if (endProfile == null) { returnEndProfile = true; endProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment[1]).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment[1]).Extend(deepestProfile); } if (returnStartProfile) yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, startDistance), startProfile); // If the start and end profiles are the same, we're done if (startProfile.DistanceKilometers(endProfile) <= 0.01) yield break; // If not, create a middle profile var middleProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment.Center).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment.Center).Extend(deepestProfile); // If the center profile is different from BOTH endpoints if (startProfile.DistanceKilometers(middleProfile) > 0.01 && middleProfile.DistanceKilometers(endProfile) > 0.01) { // Recursively create and return any new sound speed profiles between the start and the center var firstHalfSegment = new GeoSegment(segment[0], segment.Center); foreach (var tuple in ProfilesAlongRadial(firstHalfSegment, startDistance, startProfile, middleProfile, bottomProfile, soundSpeedData, deepestProfile)) yield return tuple; var centerDistance = startDistance + Geo.RadiansToKilometers(segment[0].DistanceRadians(segment.Center)); // return the center profile yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, centerDistance), middleProfile); // Recursively create and return any new sound speed profiles between the center and the end var secondHalfSegment = new GeoSegment(segment.Center, segment[1]); foreach (var tuple in ProfilesAlongRadial(secondHalfSegment, centerDistance, middleProfile, endProfile, bottomProfile, soundSpeedData, deepestProfile)) yield return tuple; } var endDistance = startDistance + Geo.RadiansToKilometers(segment.LengthRadians); // return the end profile if (returnEndProfile) yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, endDistance), endProfile); }
public static void CreateBellhopEnvironment(string outputDirectory, string name, double sourceDepth, double frequency, double verticalBeamWidth, double depressionElevationAngle, List<double> bathymetryRanges, List<double> bathymetryDepths, List<double> soundspeedDepths, List<double> soundspeedSpeeds, List<double> receiverRanges, List<double> receiverDepths, int sedimentType, int beamCount, double maxDepth) { if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); // Write the bathymetry file var bathymetryFilename = Path.Combine(outputDirectory, name + ".bty"); using (var writer = new StreamWriter(bathymetryFilename)) { writer.WriteLine("'C'"); writer.WriteLine(bathymetryRanges.Count); for (var index = 0; index < bathymetryRanges.Count; index++) writer.WriteLine("{0} {1}", bathymetryRanges[index], bathymetryDepths[index]); } var acousticProperties = new AcousticProperties { HighFrequency = (float)frequency, LowFrequency = (float)frequency, DepressionElevationAngle = (float)depressionElevationAngle, SourceDepth = (float)sourceDepth, VerticalBeamWidth = (float)verticalBeamWidth, }; var maxRadius = (int)Math.Ceiling(receiverRanges.Last() * 1.01); // Allow an extra 1% of range so the beams don't run off the end before they hit the last column of receivers var sspData = soundspeedDepths.Select((t, index) => new SoundSpeedSample((float)t, (float)soundspeedSpeeds[index])).ToList(); var soundSpeedProfile = new SoundSpeedProfile { Data = sspData }; var result = GetRadialConfiguration(acousticProperties, soundSpeedProfile, SedimentTypes.Find(sedimentType), maxDepth, maxRadius, receiverRanges, receiverDepths, false, true, true, beamCount); File.WriteAllText(Path.Combine(outputDirectory, name + ".env"), result, new ASCIIEncoding()); }
public static string GetRadialConfiguration(AcousticProperties acousticProperties, SoundSpeedProfile ssp, SedimentType sediment, double maxDepth, double maxRadius, List<double> ranges, List<double> depths, bool useSurfaceReflection, bool useVerticalBeamforming, bool generateArrivalsFile, int beamCount) { using (var sw = new StringWriter()) { sw.WriteLine("'TL' ! Title"); sw.WriteLine("{0} ! Frequency (Hz)", acousticProperties.Frequency); sw.WriteLine("1 ! NMedia"); // was NMEDIA in gui_genbellhopenv.m sw.WriteLine(useSurfaceReflection ? "'CFFT ' ! Top Option" : "'CVFT ' ! Top Option"); sw.WriteLine("0 0.00 {0} ! N sigma depth", ssp.Data[ssp.Data.Count - 1].Depth); // If SSP is shallower than the bathymetry then extrapolate an SSP entry for the deepest part of the water //if (SSP.DepthVector[SSP.DepthVector.Length - 1] < RealBottomDepth_Meters) // SoundSpeedProfile = ExtrapolateSSP(SoundSpeedProfile, RealBottomDepth_Meters); foreach (var sample in ssp.Data) sw.WriteLine("{0:0.00} {1:0.00} 0.00 1.00 0.00 0.00 / ! z c cs rho", sample.Depth, sample.SoundSpeed); sw.WriteLine("'A*' 0.00 ! Bottom Option, sigma"); // A = Acoustic halfspace, ~ = read bathymetry file, 0.0 = bottom roughness (currently ignored) sw.WriteLine("{0} {1} {2} {3} {4} {5} / ! lower halfspace", maxDepth, sediment.CompressionWaveSpeed, sediment.ShearWaveSpeed, sediment.Density, sediment.LossParameter, 0); // Source and Receiver Depths and Ranges sw.WriteLine("1"); // Number of Source Depths sw.WriteLine("{0} /", acousticProperties.SourceDepth); // source depth sw.WriteLine("{0}", depths.Count); // Number of Receiver Depths foreach (var depth in depths) sw.Write("{0} ", depth); sw.WriteLine("/ ! Receiver Depths (m)"); sw.WriteLine("{0}", ranges.Count); // Number of Receiver Ranges foreach (var range in ranges) sw.Write("{0} ", range); sw.WriteLine("/ ! Receiver Ranges (km)"); if (generateArrivalsFile) sw.WriteLine("'aG'"); // aB else sw.WriteLine(useVerticalBeamforming ? "'IG*'" : "'I'"); // if useVerticalBeamforming is true, then SBPFIL must be present (Source Beam Pattern file) sw.WriteLine("{0}", beamCount); // Number of beams //sw.WriteLine("0"); // Number of beams var verticalHalfAngle = acousticProperties.VerticalBeamWidth / 2; var angle1 = acousticProperties.DepressionElevationAngle - verticalHalfAngle; var angle2 = acousticProperties.DepressionElevationAngle + verticalHalfAngle; sw.WriteLine("{0} {1} /", angle1, angle2); // Beam fan half-angles (negative angles are toward the surface //sw.WriteLine("-60.00 60.00 /"); // Beam fan half-angles (negative angles are toward the surface //sw.WriteLine("{0:F} {1:F} {2:F} ! step zbox(meters) rbox(km)", experiment.TransmissionLossSettings.DepthCellSize, RealBottomDepth_Meters + 100, (bottomProfile.Length / 1000.0) * 1.01); sw.WriteLine("{0} {1} {2}", (ranges[0] / 2) * 1000, maxDepth, maxRadius); return sw.ToString(); } }
public static SoundSpeedField ReadFile(string gdemDirectory, TimePeriod month, GeoRect region) { var temperatureFile = NetCDFFile.Open(FindTemperatureFile(month, gdemDirectory)); var temperatureLatitudes = ((NcVarDouble)temperatureFile.Variables.Single(var => var.Name == "lat")).ToArray(); var temperatureLongitudes = ((NcVarDouble)temperatureFile.Variables.Single(var => var.Name == "lon")).ToArray(); var temperatureDepths = ((NcVarDouble)temperatureFile.Variables.Single(var => var.Name == "depth")).ToArray(); var temperatureData = ((NcVarShort)temperatureFile.Variables.Single(var => var.Name == "water_temp")); var temperatureMissingValue = ((NcAttShort)temperatureData.Attributes.Single(att => att.Name == "missing_value"))[0]; var temperatureScaleFactor = ((NcAttFloat)temperatureData.Attributes.Single(att => att.Name == "scale_factor"))[0]; var temperatureAddOffset = ((NcAttFloat)temperatureData.Attributes.Single(att => att.Name == "add_offset"))[0]; temperatureData.Filename = FindTemperatureFile(month, gdemDirectory); var salinityFile = NetCDFFile.Open(FindSalinityFile(month, gdemDirectory)); //var salinityLatitudes = ((NcVarDouble)salinityFile.Variables.Single(var => var.Name == "lat")).ToArray(); //var salinityLongitudes = ((NcVarDouble)salinityFile.Variables.Single(var => var.Name == "lon")).ToArray(); //var salinityDepths = ((NcVarDouble)salinityFile.Variables.Single(var => var.Name == "depth")).ToArray(); var salinityData = ((NcVarShort)salinityFile.Variables.Single(var => var.Name == "salinity")); var salinityMissingValue = ((NcAttShort)salinityData.Attributes.Single(att => att.Name == "missing_value"))[0]; var salinityScaleFactor = ((NcAttFloat)salinityData.Attributes.Single(att => att.Name == "scale_factor"))[0]; var salinityAddOffset = ((NcAttFloat)salinityData.Attributes.Single(att => att.Name == "add_offset"))[0]; salinityData.Filename = FindSalinityFile(month, gdemDirectory); var north = region.North; var south = region.South; var east = region.East; var west = region.West; if (temperatureLongitudes.First() > west) west += 360; if (temperatureLongitudes.Last() < west) west -= 360; if (temperatureLongitudes.First() > east) east += 360; if (temperatureLongitudes.Last() < east) east -= 360; var lonMap = new List<AxisMap>(); var latMap = new List<AxisMap>(); int i; if (east < west) { for (i = 0; i < temperatureLongitudes.Length; i++) if ((temperatureLongitudes[i] <= east) || (temperatureLongitudes[i] >= west)) lonMap.Add(new AxisMap((float)temperatureLongitudes[i], i)); } else { for (i = 0; i < temperatureLongitudes.Length; i++) if ((temperatureLongitudes[i] >= west) && (temperatureLongitudes[i] <= east)) lonMap.Add(new AxisMap((float)temperatureLongitudes[i], i)); } for (i = 0; i < temperatureLatitudes.Length; i++) if (temperatureLatitudes[i] >= south && temperatureLatitudes[i] <= north) latMap.Add(new AxisMap((float)temperatureLatitudes[i], i)); var selectedLons = lonMap.Select(x => x.Value).ToArray(); var selectedLats = latMap.Select(y => y.Value).ToArray(); var latCount = selectedLats.Length; var lonCount = selectedLons.Length; var newFieldEnvironmentData = new List<SoundSpeedProfile>(); for (var lonIndex = 0; lonIndex < lonCount; lonIndex++) { var lon = lonMap[lonIndex].Value; var wrappedLon = lon; while (wrappedLon > 180) wrappedLon -= 360; while (wrappedLon < -180) wrappedLon += 360; var lonSourceIndex = lonMap[lonIndex].Index; for (var latIndex = 0; latIndex < latCount; latIndex++) { var lat = latMap[latIndex].Value; var latSourceIndex = latMap[latIndex].Index; var newProfile = new SoundSpeedProfile(new Geo(lat, wrappedLon)); for (var depthIndex = 0; depthIndex < temperatureDepths.Length; depthIndex++) { var temperatureValue = temperatureData[(uint)depthIndex, (uint)latSourceIndex, (uint)lonSourceIndex]; var salinityValue = salinityData[(uint)depthIndex, (uint)latSourceIndex, (uint)lonSourceIndex]; if ((Math.Abs(temperatureValue - temperatureMissingValue) < 0.0001) || (Math.Abs(salinityValue - salinityMissingValue) < 0.0001)) break; var temperature = (temperatureValue * temperatureScaleFactor) + temperatureAddOffset; var salinity = (salinityValue * salinityScaleFactor) + salinityAddOffset; newProfile.Add(new SoundSpeedSample((float)temperatureDepths[depthIndex], temperature, salinity, ChenMilleroLi.SoundSpeed(newProfile, (float)temperatureDepths[depthIndex], temperature, salinity))); } if (newProfile.Data.Count > 0) newFieldEnvironmentData.Add(newProfile); } } var newField = new SoundSpeedField { TimePeriod = month }; newField.EnvironmentData.AddRange(newFieldEnvironmentData); newField.EnvironmentData.Sort(); newField.EnvironmentData.TrimToNearestPoints(region); return newField; }