diff --git a/AsbCloudApp/Data/TrajectoryVisualizationDto.cs b/AsbCloudApp/Data/TrajectoryVisualizationDto.cs new file mode 100644 index 00000000..1deb4fbd --- /dev/null +++ b/AsbCloudApp/Data/TrajectoryVisualizationDto.cs @@ -0,0 +1,24 @@ +namespace AsbCloudApp.Data +{ +#nullable enable + /// + /// Визуализация траектории 3D + /// + public class TrajectoryVisualizationDto + { + /// + /// Координаты по оси X, в сторону севера (м) + /// + public double X { get; set; } + + /// + /// Координаты по оси Y, в сторону востока (м) + /// + public double Y { get; set; } + + /// + /// Координаты по оси Z, глубина (м) + /// + public double Z { get; set; } + } +} diff --git a/AsbCloudApp/Services/IPlannedTrajectoryService.cs b/AsbCloudApp/Repositories/IPlannedTrajectoryRepository.cs similarity index 96% rename from AsbCloudApp/Services/IPlannedTrajectoryService.cs rename to AsbCloudApp/Repositories/IPlannedTrajectoryRepository.cs index 31fc1130..b18f72cf 100644 --- a/AsbCloudApp/Services/IPlannedTrajectoryService.cs +++ b/AsbCloudApp/Repositories/IPlannedTrajectoryRepository.cs @@ -3,14 +3,14 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudApp.Services +namespace AsbCloudApp.Repositories { #nullable enable /// /// CRUD для работы с плановой траекторией из клиента /// /// - public interface IPlannedTrajectoryService + public interface IPlannedTrajectoryRepository { /// /// Получить все добавленные по скважине координаты плановой траектории diff --git a/AsbCloudApp/Services/ITrajectoryVisualizationService.cs b/AsbCloudApp/Services/ITrajectoryVisualizationService.cs new file mode 100644 index 00000000..a387e501 --- /dev/null +++ b/AsbCloudApp/Services/ITrajectoryVisualizationService.cs @@ -0,0 +1,23 @@ +using AsbCloudApp.Data; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ +#nullable enable + /// + /// Сервис "Визуализация траектории 3D" + /// + public interface ITrajectoryVisualizationService + { + /// + /// Получение траектории по скважине + /// + /// + /// + /// + Task> GetTrajectoryAsync(int idWell, CancellationToken token); + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 500299b9..ffc380f4 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -12,7 +12,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -70,5 +70,4 @@ CommonLibs\AsbWitsInfo.dll - diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index dcc2eb70..2da84cc4 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -12,10 +12,10 @@ using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.DailyReport; using AsbCloudInfrastructure.Services.DetectOperations; using AsbCloudInfrastructure.Services.DrillingProgram; -using AsbCloudInfrastructure.Services.PlannedTrajectory; using AsbCloudInfrastructure.Services.ProcessMap; using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.Subsystems; +using AsbCloudInfrastructure.Services.Trajectory; using AsbCloudInfrastructure.Services.WellOperationService; using AsbCloudInfrastructure.Validators; using FluentValidation.AspNetCore; @@ -126,7 +126,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -140,6 +139,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // admin crud services: services.AddTransient, CrudCacheRepositoryBase>(s => @@ -177,6 +177,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Subsystem service services.AddTransient, CrudCacheRepositoryBase>(); diff --git a/AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryService.cs b/AsbCloudInfrastructure/Repository/PlannedTrajectoryRepository.cs similarity index 79% rename from AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryService.cs rename to AsbCloudInfrastructure/Repository/PlannedTrajectoryRepository.cs index d00a9563..dba77d7b 100644 --- a/AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryService.cs +++ b/AsbCloudInfrastructure/Repository/PlannedTrajectoryRepository.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; @@ -10,31 +11,33 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services.PlannedTrajectory +namespace AsbCloudInfrastructure.Repository { #nullable enable - public class PlannedTrajectoryService : IPlannedTrajectoryService + public class PlannedTrajectoryRepository : IPlannedTrajectoryRepository { private readonly IAsbCloudDbContext db; private readonly IWellService wellService; - public PlannedTrajectoryService(IAsbCloudDbContext db, IWellService wellService) + public PlannedTrajectoryRepository(IAsbCloudDbContext db, IWellService wellService) { this.db = db; this.wellService = wellService; } /// public async Task AddRangeAsync(IEnumerable plannedTrajectoryRows, CancellationToken token) - { + { var idWell = plannedTrajectoryRows.First().IdWell; if (!plannedTrajectoryRows.All(r => r.IdWell == idWell)) throw new ArgumentInvalidException("Все строки должны относиться к одной скважине", nameof(plannedTrajectoryRows)); var offsetHours = wellService.GetTimezone(idWell).Hours; var entities = plannedTrajectoryRows - .Select(e => { + .Select(e => + { var entity = Convert(e, offsetHours); entity.Id = 0; - return entity;}); - + return entity; + }); + db.PlannedTrajectories.AddRange(entities); return await db.SaveChangesAsync(token) .ConfigureAwait(false); @@ -46,7 +49,7 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory var offsetHours = wellService.GetTimezone(plannedTrajectoryRow.IdWell).Hours; var entity = Convert(plannedTrajectoryRow, offsetHours); entity.Id = 0; - db.PlannedTrajectories.Add(entity); + db.PlannedTrajectories.Add(entity); return await db.SaveChangesAsync(token) .ConfigureAwait(false); } @@ -77,13 +80,13 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory var well = wellService.GetOrDefault(idWell); if (well is null || well.Timezone is null) throw new ArgumentInvalidException("idWell doesn`t exist", nameof(idWell)); - var offsetHours = well.Timezone.Hours; + var offsetHours = well.Timezone.Hours; var query = db.PlannedTrajectories .AsNoTracking() - .Where(x => x.IdWell == idWell); + .Where(x => x.IdWell == idWell); var entities = await query .OrderBy(e => e.WellboreDepth) - .ToListAsync(token); + .ToListAsync(token); var result = entities .Select(r => Convert(r, offsetHours)); return result; @@ -99,19 +102,19 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory .ConfigureAwait(false); } - private PlannedTrajectoryDto Convert(AsbCloudDb.Model.PlannedTrajectory entity, double offsetHours) - { - var dto = entity.Adapt(); + private PlannedTrajectoryDto Convert(PlannedTrajectory entity, double offsetHours) + { + var dto = entity.Adapt(); dto.UpdateDate = entity.UpdateDate.ToRemoteDateTime(offsetHours); return dto; } - private AsbCloudDb.Model.PlannedTrajectory Convert(PlannedTrajectoryDto dto, double offsetHours) - { - var entity = dto.Adapt(); + private PlannedTrajectory Convert(PlannedTrajectoryDto dto, double offsetHours) + { + var entity = dto.Adapt(); entity.UpdateDate = DateTime.Now.ToUtcDateTimeOffset(offsetHours); return entity; - } + } } #nullable disable } diff --git a/AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryImportService.cs b/AsbCloudInfrastructure/Services/Trajectory/PlannedTrajectoryImportService.cs similarity index 95% rename from AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryImportService.cs rename to AsbCloudInfrastructure/Services/Trajectory/PlannedTrajectoryImportService.cs index 8e2278fa..810530d5 100644 --- a/AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryImportService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/PlannedTrajectoryImportService.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using ClosedXML.Excel; using System; @@ -8,7 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services.PlannedTrajectory +namespace AsbCloudInfrastructure.Services.Trajectory { #nullable enable public class PlannedTrajectoryImportService : IPlannedTrajectoryImportService @@ -16,9 +17,9 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory /* * password for PlannedTrajectoryTemplate.xlsx is Drill2022 */ - + private readonly IWellService wellService; - private readonly IPlannedTrajectoryService plannedTrajectoryService; + private readonly IPlannedTrajectoryRepository plannedTrajectoryService; private const string templateFileName = "PlannedTrajectoryTemplate.xlsx"; private const string usingTemplateFile = "AsbCloudInfrastructure.Services.PlannedTrajectory"; @@ -40,9 +41,9 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory private const int ColumnOrificeOffset = 14; private const int ColumnComment = 15; - public PlannedTrajectoryImportService(IWellService wellService, IPlannedTrajectoryService plannedTrajectoryService) + public PlannedTrajectoryImportService(IWellService wellService, IPlannedTrajectoryRepository plannedTrajectoryService) { - + this.wellService = wellService; this.plannedTrajectoryService = plannedTrajectoryService; } @@ -56,21 +57,21 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory return stream; } - public async Task GetFileNameAsync (int idWell, CancellationToken token) + public async Task GetFileNameAsync(int idWell, CancellationToken token) { var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_plannedTrajectory.xlsx"; return fileName; } public async Task ExportAsync(int idWell, CancellationToken token) - { - var plannedTrajectorys = await plannedTrajectoryService.GetAsync(idWell, token); + { + var plannedTrajectorys = await plannedTrajectoryService.GetAsync(idWell, token); return MakeExelFileStream(plannedTrajectorys); } private Stream MakeExelFileStream(IEnumerable plannedTrajectories) { - using Stream ecxelTemplateStream = GetTemplateFile(); + using Stream ecxelTemplateStream = GetTemplateFile(); using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled); AddPlannedTrajecoryToWorkbook(workbook, plannedTrajectories); MemoryStream memoryStream = new MemoryStream(); @@ -123,12 +124,12 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); var trajectoryRows = ParseFileStream(stream); foreach (var row in trajectoryRows) - { - row.IdWell = idWell; + { + row.IdWell = idWell; row.IdUser = idUser; } - var rowsCount = await SavePlannedTrajectoryAsync(idWell,trajectoryRows, deletePrevRows, token); + var rowsCount = await SavePlannedTrajectoryAsync(idWell, trajectoryRows, deletePrevRows, token); return rowsCount; } @@ -205,9 +206,9 @@ namespace AsbCloudInfrastructure.Services.PlannedTrajectory var _comment = row.Cell(ColumnComment).Value; var trajectoryRow = new PlannedTrajectoryDto(); - + static double getDoubleValue(object value, string nameParam, IXLRow row) - { + { if (value is double _value) return _value; throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} - некорректные данные - {nameParam}"); diff --git a/AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryTemplate.xlsx b/AsbCloudInfrastructure/Services/Trajectory/PlannedTrajectoryTemplate.xlsx similarity index 100% rename from AsbCloudInfrastructure/Services/PlannedTrajectory/PlannedTrajectoryTemplate.xlsx rename to AsbCloudInfrastructure/Services/Trajectory/PlannedTrajectoryTemplate.xlsx diff --git a/AsbCloudInfrastructure/Services/Trajectory/TrajectoryVisualizationService.cs b/AsbCloudInfrastructure/Services/Trajectory/TrajectoryVisualizationService.cs new file mode 100644 index 00000000..d8a30c7d --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/TrajectoryVisualizationService.cs @@ -0,0 +1,57 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Trajectory +{ +#nullable enable + public class TrajectoryVisualizationService : ITrajectoryVisualizationService + { + private readonly IPlannedTrajectoryRepository repository; + + public TrajectoryVisualizationService(IPlannedTrajectoryRepository repository) + { + this.repository = repository; + } + + public async Task> GetTrajectoryAsync(int idWell, CancellationToken token) + { + var geoCoordinates = (await repository.GetAsync(idWell, token)).ToArray(); + + if (geoCoordinates.Length < 2) + return Enumerable.Empty(); + + var cartesianCoordinates = new List(geoCoordinates.Length) { + new (), + }; + + for (var i = 1; i < geoCoordinates.Length; i++) + { + var intervalGeoParams = geoCoordinates[i - 1]; + var deltaWellLength = geoCoordinates[i].WellboreDepth - intervalGeoParams.WellboreDepth; + var projectionLengthToXYSurface = deltaWellLength * Math.Sin(intervalGeoParams.ZenithAngle * Math.PI / 180); + + var dz = deltaWellLength * Math.Cos(intervalGeoParams.ZenithAngle * Math.PI / 180); + var dx = projectionLengthToXYSurface * Math.Sin(intervalGeoParams.AzimuthGeo * Math.PI / 180); + var dy = projectionLengthToXYSurface * Math.Cos(intervalGeoParams.AzimuthGeo * Math.PI / 180); + + var preCoordinates = cartesianCoordinates[i - 1]; + var coordinates = new TrajectoryVisualizationDto + { + Z = preCoordinates.Z + dz, + X = preCoordinates.X + dx, + Y = preCoordinates.Y + dy, + }; + + cartesianCoordinates.Add(coordinates); + } + + return cartesianCoordinates; + } + } +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/TrajectoryVisualizationServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/TrajectoryVisualizationServiceTest.cs new file mode 100644 index 00000000..7ba99ce0 --- /dev/null +++ b/AsbCloudWebApi.Tests/ServicesTests/TrajectoryVisualizationServiceTest.cs @@ -0,0 +1,90 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudInfrastructure.Services; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AsbCloudWebApi.Tests.ServicesTests +{ + public class TrajectoryVisualizationServiceTest + { + private Mock MakePlannedTrajectoryRepositoryMock(IEnumerable dateForGetMethod) + { + var mock = new Mock(); + + mock.Setup(r => r.GetAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(dateForGetMethod)); + + return mock; + } + + [Fact] + public async Task GetTrajectoryAsync_SameCounts() + { + var plannedTrajectory = new PlannedTrajectoryDto[] + { + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 0d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 10d}, + new() { AzimuthGeo = 0d, ZenithAngle = 30d, WellboreDepth = 20d}, + new() { AzimuthGeo = 30d, ZenithAngle = 0d, WellboreDepth = 30d}, + new() { AzimuthGeo = 30d, ZenithAngle = 90d, WellboreDepth = 40d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 50d}, + }; + + var mock = MakePlannedTrajectoryRepositoryMock(plannedTrajectory); + var service = new TrajectoryVisualizationService(mock.Object); + var result = await service.GetTrajectoryAsync(1, CancellationToken.None); + + Assert.Equal(plannedTrajectory.Length, result.Count()); + } + + [Fact] + public async Task GetTrajectoryAsync_StraigthBore() + { + var plannedTrajectory = new PlannedTrajectoryDto[] + { + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 0d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 0d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 20d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 20d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 30d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 50d}, + }; + + var mock = MakePlannedTrajectoryRepositoryMock(plannedTrajectory); + var service = new TrajectoryVisualizationService(mock.Object); + var result = await service.GetTrajectoryAsync(1, CancellationToken.None); + var lastPoint = result.Last(); + + Assert.Equal(0d, lastPoint.X, 0.1d); + Assert.Equal(0d, lastPoint.Y, 0.1d); + Assert.Equal(50d, lastPoint.Z, 0.1d); + } + + [Fact] + public async Task GetTrajectoryAsync_Match() + { + var plannedTrajectory = new PlannedTrajectoryDto[] + { + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 0d}, + new() { AzimuthGeo = 30d, ZenithAngle = 30d, WellboreDepth = 10d}, + new() { AzimuthGeo = 0d, ZenithAngle = 0d, WellboreDepth = 20d}, + }; + + var mock = MakePlannedTrajectoryRepositoryMock(plannedTrajectory); + var service = new TrajectoryVisualizationService(mock.Object); + var result = await service.GetTrajectoryAsync(1, CancellationToken.None); + var lastPoint = result.Last(); + var tolerance = 0.001d; + + Assert.InRange(lastPoint.Z, 10 + tolerance, 20 - tolerance); + Assert.InRange(lastPoint.Y, 0 + tolerance, 10 - tolerance); + Assert.InRange(lastPoint.X, 0 + tolerance, 10 - tolerance); + } + } +} diff --git a/AsbCloudWebApi/Controllers/PlannedTrajectoryController.cs b/AsbCloudWebApi/Controllers/PlannedTrajectoryController.cs index cd1c1ed6..f44f1600 100644 --- a/AsbCloudWebApi/Controllers/PlannedTrajectoryController.cs +++ b/AsbCloudWebApi/Controllers/PlannedTrajectoryController.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -21,13 +22,18 @@ namespace AsbCloudWebApi.Controllers { private readonly IWellService wellService; private readonly IPlannedTrajectoryImportService plannedTrajectoryImportService; - private readonly IPlannedTrajectoryService plannedTrajectoryService; + private readonly IPlannedTrajectoryRepository plannedTrajectoryRepository; + private readonly ITrajectoryVisualizationService trajectoryVisualizationService; - public PlannedTrajectoryController(IWellService wellService, IPlannedTrajectoryImportService plannedTrajectoryImportService, IPlannedTrajectoryService plannedTrajectoryService) + public PlannedTrajectoryController(IWellService wellService, + IPlannedTrajectoryImportService plannedTrajectoryImportService, + IPlannedTrajectoryRepository plannedTrajectoryRepository, + ITrajectoryVisualizationService trajectoryVisualizationService) { this.plannedTrajectoryImportService = plannedTrajectoryImportService; this.wellService = wellService; - this.plannedTrajectoryService = plannedTrajectoryService; + this.plannedTrajectoryRepository = plannedTrajectoryRepository; + this.trajectoryVisualizationService = trajectoryVisualizationService; } /// @@ -121,7 +127,7 @@ namespace AsbCloudWebApi.Controllers if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); - var result = await plannedTrajectoryService.GetAsync(idWell, token); + var result = await plannedTrajectoryRepository.GetAsync(idWell, token); return Ok(result); } @@ -146,7 +152,7 @@ namespace AsbCloudWebApi.Controllers return Forbid(); row.IdUser = idUser.Value; row.IdWell = idWell; - var result = await plannedTrajectoryService.AddAsync(row, token); + var result = await plannedTrajectoryRepository.AddAsync(row, token); return Ok(result); } @@ -174,7 +180,7 @@ namespace AsbCloudWebApi.Controllers item.IdUser = idUser.Value; item.IdWell = idWell; } - var result = await plannedTrajectoryService.AddRangeAsync(rows, token); + var result = await plannedTrajectoryRepository.AddRangeAsync(rows, token); return Ok(result); } @@ -200,7 +206,7 @@ namespace AsbCloudWebApi.Controllers row.Id = idRow; row.IdUser = idUser.Value; row.IdWell = idWell; - var result = await plannedTrajectoryService.UpdateAsync(row, token); + var result = await plannedTrajectoryRepository.UpdateAsync(row, token); return Ok(result); } @@ -220,11 +226,29 @@ namespace AsbCloudWebApi.Controllers token).ConfigureAwait(false)) return Forbid(); - var result = await plannedTrajectoryService.DeleteRangeAsync(new int[] { idRow }, token); + var result = await plannedTrajectoryRepository.DeleteRangeAsync(new int[] { idRow }, token); return Ok(result); } + /// + /// Получение координат для визуализации траектории + /// + /// + /// + /// + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public async Task GetTrajectoryAsync(int idWell, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, + token).ConfigureAwait(false)) + return Forbid(); + + var result = await trajectoryVisualizationService.GetTrajectoryAsync(idWell, token); + return Ok(result); + } + private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token = default) { int? idCompany = User.GetCompanyId();