Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into remove-obsolete-boreh…
Browse files Browse the repository at this point in the history
…ole-geometry-fields
  • Loading branch information
patrickackermann committed May 16, 2024
2 parents 4e5939d + d44aacf commit efde159
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 358 deletions.
87 changes: 87 additions & 0 deletions src/api/BoreholeGeometryFormat/AzIncFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using BDMS.Models;
using CsvHelper;
using CsvHelper.Configuration.Attributes;

namespace BDMS.BoreholeGeometryFormat;

/// <summary>
/// Accepts a CSV file where every data point has an Azimuth and Inclination.
/// </summary>
internal sealed class AzIncFormat : IBoreholeGeometryFormat
{
public string Key => "AzInc";
public string Name => "Azimuth Inclination";
private Lazy<string> expectedCsvHeader = new(Helper.GetCSVHeader<Geometry>);
public string CsvHeader => expectedCsvHeader.Value;

public IList<BoreholeGeometryElement> ReadCsv(IFormFile file, int boreholeId)
{
using var reader = new StreamReader(file.OpenReadStream());
using var csv = new CsvReader(reader, Helper.CsvConfig);

var data = csv.GetRecords<Geometry>().ToList();

// Convert degrees to radians
foreach (var entry in data)
{
entry.Azimuth = Helper.ToRadians(entry.Azimuth);
entry.Inclination = Helper.ToRadians(entry.Inclination);
}

return XYZFormat.ToBoreholeGeometry(ConvertToXYZ(data), boreholeId);
}

/// <summary>
/// Convert <see cref="Geometry"/> data to <see cref="XYZFormat.Geometry"/> data using the
/// <see href="https://www.drillingformulas.com/minimum-curvature-method/">Minimum Curvature</see> algorithm.
/// </summary>
/// <param name="data">The <see cref="Geometry"/> data.</param>
public static List<XYZFormat.Geometry> ConvertToXYZ(IList<Geometry> data)
{
List<XYZFormat.Geometry> result = new(data.Count);
result.Add(new XYZFormat.Geometry { X = 0, Y = 0, Z = 0 });

for (int i = 1; i < data.Count; i++)
{
var a = data[i - 1];
var b = data[i];

// Change in measured depth
double deltaMD = b.MeasuredDepth - a.MeasuredDepth;

// Dogleg Severity Angle
double beta = Math.Acos(Math.Cos(b.Inclination - a.Inclination) - (Math.Sin(a.Inclination) * Math.Sin(b.Inclination) * (1 - Math.Cos(b.Azimuth - a.Azimuth))));

// Ratio factor
double ratioFactor = beta == 0 ? 1 : (2 / beta) * Math.Tan(beta / 2);

// Half delta measured depth multiplied by ratio factor
double factor = (deltaMD / 2) * ratioFactor;

// Change in easting, northing and elevation
double deltaN = factor * ((Math.Sin(a.Inclination) * Math.Cos(a.Azimuth)) + (Math.Sin(b.Inclination) * Math.Cos(b.Azimuth)));
double deltaE = factor * ((Math.Sin(a.Inclination) * Math.Sin(a.Azimuth)) + (Math.Sin(b.Inclination) * Math.Sin(b.Azimuth)));
double deltaTVD = factor * (Math.Cos(a.Inclination) + Math.Cos(b.Inclination));

var previous = result[i - 1];
result.Add(new XYZFormat.Geometry
{
X = previous.X + deltaE,
Y = previous.Y + deltaN,
Z = previous.Z + deltaTVD,
});
}

return result;
}

internal sealed class Geometry
{
[Name("MD_m")]
public double MeasuredDepth { get; set; }
[Name("HAZI_deg")]
public double Azimuth { get; set; }
[Name("DEVI_deg")]
public double Inclination { get; set; }
}
}
35 changes: 35 additions & 0 deletions src/api/BoreholeGeometryFormat/Helper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using CsvHelper;
using CsvHelper.Configuration;
using Humanizer;
using System.Globalization;

namespace BDMS.BoreholeGeometryFormat;

public static class Helper
{
internal static readonly CsvConfiguration CsvConfig = new(new CultureInfo("de-CH"))
{
Delimiter = ";",
IgnoreReferences = true,
PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title),
MissingFieldFound = null,
};

/// <summary>
/// Get the CSV header <see cref="CsvHelper"/> expects to read a class <typeparamref name="T"/>.
/// Uses the map generated by <see cref="CsvHelper.CsvContext.AutoMap{T}()"/>.
/// If a property has multiple possible column names only the first is considered.
/// </summary>
/// <typeparam name="T">The class to get the header for.</typeparam>
internal static string GetCSVHeader<T>()
{
var context = new CsvContext(CsvConfig);
var map = context.AutoMap<T>();
return string.Join("; ", map.MemberMaps.Select(m => m.Data.Names.FirstOrDefault(m.Data.Member.Name)));
}

internal static double ToRadians(double degrees)
{
return degrees * Math.PI / 180;
}
}
32 changes: 32 additions & 0 deletions src/api/BoreholeGeometryFormat/IBoreholeGeometryFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using BDMS.Models;

namespace BDMS.BoreholeGeometryFormat;

/// <summary>
/// Geometry format with method to read and convert the input CSV file to <see cref="BoreholeGeometryElement"/>s.
/// </summary>
public interface IBoreholeGeometryFormat
{
/// <summary>
/// Key to identify this <see cref="IBoreholeGeometryFormat"/>.
/// </summary>
string Key { get; }

/// <summary>
/// Human readable name of the <see cref="IBoreholeGeometryFormat"/>.
/// </summary>
string Name { get; }

/// <summary>
/// The expected header of the input CSV file.
/// </summary>
string CsvHeader { get; }

/// <summary>
/// Convert the provided CSV file into a List of <see cref="BoreholeGeometryElement"/>.
/// </summary>
/// <param name="file">The input CSV file.</param>
/// <param name="boreholeId">The id of the borehole this geometry belongs to.</param>
/// <returns></returns>
IList<BoreholeGeometryElement> ReadCsv(IFormFile file, int boreholeId);
}
71 changes: 71 additions & 0 deletions src/api/BoreholeGeometryFormat/PitchRollFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using BDMS.Models;
using CsvHelper;
using CsvHelper.Configuration.Attributes;

namespace BDMS.BoreholeGeometryFormat;

/// <summary>
/// Accepts a CSV file where every data point has a Pitch, Roll and Yaw angle.
/// </summary>
internal sealed class PitchRollFormat : IBoreholeGeometryFormat
{
public string Key => "PitchRoll";
public string Name => "Pitch Roll";
private Lazy<string> expectedCsvHeader = new(Helper.GetCSVHeader<Geometry>);
public string CsvHeader => expectedCsvHeader.Value;

public IList<BoreholeGeometryElement> ReadCsv(IFormFile file, int boreholeId)
{
using var reader = new StreamReader(file.OpenReadStream());
using var csv = new CsvReader(reader, Helper.CsvConfig);

var data = csv.GetRecords<Geometry>().ToList();

// Convert degrees to radians
foreach (var entry in data)
{
entry.Pitch = Helper.ToRadians(entry.Pitch);
entry.Roll = Helper.ToRadians(entry.Roll);
entry.MagneticRotation = Helper.ToRadians(entry.MagneticRotation);
}

return XYZFormat.ToBoreholeGeometry(AzIncFormat.ConvertToXYZ(ConvertToAzInc(data)), boreholeId);
}

/// <summary>
/// Convert the <see cref="Geometry"/> data to <see cref="AzIncFormat.Geometry"/> data by
/// calculating the Azimuth and Inclination of a the vector tangential to the borehole.
/// </summary>
/// <param name="data">The <see cref="Geometry"/> data.</param>
public static IList<AzIncFormat.Geometry> ConvertToAzInc(IEnumerable<Geometry> data)
{
return data.Select(d =>
{
var result = new AzIncFormat.Geometry() { MeasuredDepth = d.MeasuredDepth };

var alpha = d.MagneticRotation; // Rotation around z axis (down)
var beta = d.Pitch; // Rotation around y axis (north)
var gamma = d.Roll; // Rotation around x axis (east)

// Unit vector tangential to the borehole path
var x = (Math.Cos(alpha) * Math.Sin(beta) * Math.Cos(gamma)) + (Math.Sin(alpha) * Math.Sin(gamma));
var y = (Math.Sin(alpha) * Math.Sin(beta) * Math.Cos(gamma)) - (Math.Cos(alpha) * Math.Sin(gamma));
var z = Math.Cos(beta) * Math.Cos(gamma);

result.Azimuth = Math.Atan2(y, x);
result.Inclination = Math.Acos(z);

return result;
}).ToList();
}

internal sealed class Geometry
{
[Name("Kabellaenge")]
public double MeasuredDepth { get; set; }
public double Roll { get; set; }
public double Pitch { get; set; }
[Name("Magnetische Rotation")]
public double MagneticRotation { get; set; }
}
}
50 changes: 50 additions & 0 deletions src/api/BoreholeGeometryFormat/XYZFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using BDMS.Models;
using CsvHelper;

namespace BDMS.BoreholeGeometryFormat;

/// <summary>
/// Accepts a CSV file with XYZ values that can be used directly without conversion.
/// </summary>
internal sealed class XYZFormat : IBoreholeGeometryFormat
{
public string Key => "XYZ";
public string Name => "X Y Z";
private Lazy<string> expectedCsvHeader = new(Helper.GetCSVHeader<Geometry>);
public string CsvHeader => expectedCsvHeader.Value;

public IList<BoreholeGeometryElement> ReadCsv(IFormFile file, int boreholeId)
{
using var reader = new StreamReader(file.OpenReadStream());
using var csv = new CsvReader(reader, Helper.CsvConfig);

var data = csv.GetRecords<Geometry>();
return ToBoreholeGeometry(data, boreholeId);
}

/// <summary>
/// Convert <see cref="Geometry"/> data to <see cref="BoreholeGeometryElement"/> data that can be
/// written to the Database.
/// </summary>
/// <param name="data">The <see cref="Geometry"/> data.</param>
/// <param name="boreholeId">The borehole this geometry belongs to.</param>
public static List<BoreholeGeometryElement> ToBoreholeGeometry(IEnumerable<Geometry> data, int boreholeId)
{
return data
.Select(g => new BoreholeGeometryElement
{
BoreholeId = boreholeId,
X = g.X,
Y = g.Y,
Z = g.Z,
})
.ToList();
}

internal sealed class Geometry
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
}
Loading

0 comments on commit efde159

Please sign in to comment.