void Calculate(Radial radial)
        {
            try
            {
                var scenario = radial.TransmissionLoss.AnalysisPoint.Scenario;
                var mode = (from m in radial.TransmissionLoss.Modes
                            orderby m.MaxPropagationRadius
                            select m).Last();
                var platform = mode.Source.Platform;
                var timePeriod = platform.Scenario.TimePeriod;
                if (radial.IsDeleted) return;
                var wind = (Wind)_cacheService[scenario.Wind].Result;
                if (radial.IsDeleted) return;
                var soundSpeed = (SoundSpeed)_cacheService[scenario.SoundSpeed].Result;
                if (radial.IsDeleted) return;
                var bathymetry = (Bathymetry)_cacheService[scenario.Bathymetry].Result;
                if (radial.IsDeleted) return;
                var sediment = (Sediment)_cacheService[scenario.Sediment].Result;
                if (radial.IsDeleted) return;
                var deepestPoint = bathymetry.DeepestPoint;
                var deepestProfile = soundSpeed[timePeriod].GetDeepestSSP(deepestPoint).Extend(deepestPoint.Data);

                var depthAtAnalysisPoint = bathymetry.Samples.IsFast2DLookupAvailable
                                               ? bathymetry.Samples.GetNearestPointAsync(radial.TransmissionLoss.AnalysisPoint.Geo).Result
                                               : bathymetry.Samples.GetNearestPoint(radial.TransmissionLoss.AnalysisPoint.Geo);

                // If there is less than one meter of water at the analysis point, discard this radial
                if (depthAtAnalysisPoint.Data > -1)
                {
                    radial.Delete();
                    return;
                }

                var windData = wind[timePeriod].EnvironmentData;
                var windSample = windData.IsFast2DLookupAvailable
                                     ? windData.GetNearestPointAsync(radial.Segment.Center).Result
                                     : windData.GetNearestPoint(radial.Segment.Center);

                var sedimentSample = sediment.Samples.IsFast2DLookupAvailable
                                         ? sediment.Samples.GetNearestPointAsync(radial.Segment.Center).Result
                                         : sediment.Samples.GetNearestPoint(radial.Segment.Center);
                
                var bottomProfile = new BottomProfile(99, radial.Segment, bathymetry);

                var directoryPath = Path.GetDirectoryName(radial.BasePath);
                if (directoryPath == null) return;
                if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
                if (Globals.PluginManagerService != null && Globals.PluginManagerService[PluginType.TransmissionLossCalculator] != null)
                {
                    var profilesAlongRadial = ProfilesAlongRadial(radial.Segment, 0.0, null, null, bottomProfile, soundSpeed[timePeriod].EnvironmentData, deepestProfile).ToList();
                    if (radial.IsDeleted) return;
                    radial.CalculationStarted = DateTime.Now;
                    try
                    {
                        mode.GetTransmissionLossPlugin(Globals.PluginManagerService).CalculateTransmissionLoss(platform, mode, radial, bottomProfile, sedimentSample, windSample.Data, profilesAlongRadial);
                    }
                    catch (RadialDeletedByUserException)
                    {
                        radial.CleanupFiles();
                    }
                    radial.CalculationCompleted = DateTime.Now;
                    radial.Length = mode.MaxPropagationRadius;
                    radial.IsCalculated = true;
                    LocationContext.Modify(c =>
                    {
                        var transmissionLoss = (from tl in c.TransmissionLosses
                                                where tl.Guid == radial.TransmissionLoss.Guid
                                                select tl).Single();
                        transmissionLoss.Radials.Add(radial);
                        radial.TransmissionLoss = transmissionLoss;
                    });
                }
                else Debug.WriteLine("TransmissionLossCalculatorService: PluginManagerService is not initialized, or there are no transmission loss calculator plugins defined");
            }
            catch (Exception e)
            {
                Debug.WriteLine("{0}: FAIL: Calculation of transmission loss for radial bearing {1} degrees, of mode {2} in analysis point {3}.  Exception: {4}",
                                DateTime.Now,
                                radial == null ? "(null)" : radial.Bearing.ToString(CultureInfo.InvariantCulture),
                                radial == null || radial.TransmissionLoss == null || radial.TransmissionLoss.Modes == null || radial.TransmissionLoss.Modes.Count == 0 ? "(null)" : radial.TransmissionLoss.Modes[0].ToString(),
                                radial == null || radial.TransmissionLoss == null || radial.TransmissionLoss.AnalysisPoint == null || radial.TransmissionLoss.AnalysisPoint.Geo == null ? "(null)" : ((Geo)radial.TransmissionLoss.AnalysisPoint.Geo).ToString(), e.Message);
            }
        }
        protected void CalculateTransmissionLossInternal(Platform platform, Mode mode, Radial radial, BottomProfile bottomProfile, SedimentType sedimentType, double windSpeed, IList<Tuple<double, SoundSpeedProfile>> soundSpeedProfilesAlongRadial, bool createArrivalsFile)
        {
            var depthCellCount = (int)Math.Ceiling(bottomProfile.MaxDepth / DepthCellSize);
            var rangeCellCount = (int)Math.Ceiling(mode.MaxPropagationRadius / RangeCellSize);
            var startProfile = soundSpeedProfilesAlongRadial[0].Item2;
            var sourceDepth = platform.Depth;
            var frequency = (float)Math.Sqrt(mode.HighFrequency * mode.LowFrequency);
            if (mode.Depth.HasValue) sourceDepth += mode.Depth.Value;
            var maxCalculationDepthMeters = bottomProfile.MaxDepth * 1.01;
            var directoryPath = Path.GetDirectoryName(radial.BasePath);
            if (directoryPath == null) throw new NullReferenceException("radial.BasePath does not point to a valid directory");
            if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);

            using (var envFile = new StreamWriter(radial.BasePath + ".env", false))
            {
                envFile.WriteLine("Scenario: '{0}' Mode: '{2}' Analysis point: {1} Bearing: {3}",
                                  radial.TransmissionLoss.AnalysisPoint.Scenario.Name,
                                  radial.TransmissionLoss.AnalysisPoint.Geo,
                                  radial.TransmissionLoss.Modes[0].ModeName,
                                  radial.Bearing);
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", frequency));
                envFile.WriteLine("1"); // was NMEDIA in gui_genbellhopenv.m
                envFile.WriteLine(UseSurfaceReflection ? "'QFLT'" : "'QVLT'");

                //if (depthCellCount < 5) throw new BathymetryTooShallowException("Error: Maximum depth of transect (" + maxCalculationDepthMeters + " meters) less than minimum required for transmission loss calculations.\nPlease choose a different location for this transect.");

                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "0, 0.0, {0}", startProfile.Data[startProfile.Data.Count - 1].Depth));
                foreach (var soundSpeedSample in startProfile.Data)
                    envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1} 0.0 1.0 0.0 0.0", soundSpeedSample.Depth, soundSpeedSample.SoundSpeed));

                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "'A*' 0.0")); // A = Acoustic halfspace, * = read bathymetry file 'BTYFIL', 0.0 = bottom roughness (currently ignored)
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3} {4} {5} /", maxCalculationDepthMeters, sedimentType.CompressionWaveSpeed, sedimentType.ShearWaveSpeed, sedimentType.Density, sedimentType.LossParameter, 0));
                // Source and Receiver Depths and Ranges
                envFile.WriteLine("1"); // Number of Source Depths
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} /", sourceDepth)); // source depth
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", depthCellCount)); // Number of Receiver Depths
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "0.0 {0} /", maxCalculationDepthMeters));
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", rangeCellCount)); // Number of receiver ranges
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "0.0 {0} /", mode.MaxPropagationRadius / 1000.0));

                envFile.WriteLine(createArrivalsFile ? "'a'" : "'I'");
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", RayCount)); // Number of beams
                var verticalHalfAngle = mode.VerticalBeamWidth / 2;
                var angle1 = mode.DepressionElevationAngle - verticalHalfAngle;
                var angle2 = mode.DepressionElevationAngle + verticalHalfAngle;
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1} /", angle1, angle2)); // Beam fan half-angles (negative angles are toward the surface
                envFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "0.0 {0} {1}", maxCalculationDepthMeters, (mode.MaxPropagationRadius / 1000.0) * 1.01)); // step zbox(meters) rbox(km)
            }
            using (var sspFile = new StreamWriter(radial.BasePath + ".ssp", false))
            {
                if (soundSpeedProfilesAlongRadial.Count == 1) soundSpeedProfilesAlongRadial.Add(Tuple.Create(Geo.RadiansToKilometers(radial.Segment.LengthRadians), new SoundSpeedProfile(soundSpeedProfilesAlongRadial[0].Item2)));
                sspFile.WriteLine("{0}", soundSpeedProfilesAlongRadial.Count);
                foreach (var rangeProfileTuple in soundSpeedProfilesAlongRadial) sspFile.Write(string.Format(CultureInfo.InvariantCulture, "{0,-10:0.###}", rangeProfileTuple.Item1));
                sspFile.WriteLine();
                //sspFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0,-10:0.###}{1,-10:0.###}{2,-10:0.###}", 0.0, bottomProfile.Profile[bottomProfile.Profile.Count / 2].Range, bottomProfile.Profile[bottomProfile.Profile.Count - 1].Range));
                for (var depthIndex = 0; depthIndex < startProfile.Data.Count; depthIndex++)
                {
                    foreach (var rangeProfileTuple in soundSpeedProfilesAlongRadial) sspFile.Write(string.Format(CultureInfo.InvariantCulture, "{0,-10:0.###}", rangeProfileTuple.Item2.Data[depthIndex].SoundSpeed));
                    sspFile.WriteLine();
                }
                //sspFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0,-10:0.###}{1,-10:0.###}{2,-10:0.###}", startProfile.Data[depthIndex].SoundSpeed, middleProfile.Data[depthIndex].SoundSpeed, endProfile.Data[depthIndex].SoundSpeed));
            }
            using (var trcFile = new StreamWriter(radial.BasePath + ".trc", false))
            {
                var topReflectionCoefficients = GenerateReflectionCoefficients(windSpeed, frequency);
                trcFile.WriteLine(topReflectionCoefficients.GetLength(0));
                for (var rowIndex = 0; rowIndex < topReflectionCoefficients.GetLength(0); rowIndex++)
                    trcFile.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} ", topReflectionCoefficients[rowIndex, 0], topReflectionCoefficients[rowIndex, 1], topReflectionCoefficients[rowIndex, 2]));
            }
            using (var writer = new StreamWriter(radial.BasePath + ".bty")) writer.Write(bottomProfile.ToBellhopString());

            // Now that we've got the files ready to go, we can launch bellhop to do the actual calculations
            var bellhopProcess = new Process
            {
                StartInfo = new ProcessStartInfo(Path.Combine(AssemblyLocation, "bellhop.exe"), radial.Filename)
                {
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardInput = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    WorkingDirectory = directoryPath
                }
            };
            if (radial.IsDeleted) throw new RadialDeletedByUserException();
            bellhopProcess.Start();
            try
            {
                bellhopProcess.PriorityClass = ProcessPriorityClass.Idle;
            }
            catch (InvalidOperationException) {}
            //bellhopProcess.BeginOutputReadLine();
            while (!bellhopProcess.HasExited)
            {
                if (radial.IsDeleted)
                {
                    bellhopProcess.Kill();
                    throw new RadialDeletedByUserException();
                }
                Thread.Sleep(20);
            }
            if (bellhopProcess.ExitCode == 0) return;
            var bellhopOutput = bellhopProcess.StandardOutput.ReadToEnd();
            var bellhopError = bellhopProcess.StandardError.ReadToEnd();
            Debug.WriteLine("Bellhop process for radial {0} exited with error code {1:X}", radial.BasePath, bellhopProcess.ExitCode);
            Debug.WriteLine("Bellhop stdout: " + bellhopOutput);
            Debug.WriteLine("Bellhop stderr: " + bellhopError);
            radial.CleanupFiles();
        }