mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-22 05:26:39 +00:00
make structure
add tests
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
---
|
||||
description: Coding rules
|
||||
alwaysApply: true
|
||||
---
|
||||
# Coding preferences
|
||||
- Always prefer simple solution
|
||||
- Generate concise code
|
||||
- Do not put comments in the code
|
||||
- Do not put logs unless it is an exception, or was asked specifically
|
||||
- Do not put code annotations unless it was asked specifically
|
||||
- Your changes should be well correlated with what was requested. In case of any uncertainties ask questions.
|
||||
- Mocking data is needed only for tests
|
||||
- When you add new libraries or dependencies make sure you are using the same version of it as other parts of the code
|
||||
|
||||
- Focus on the areas of code relevant to the task
|
||||
- Do not touch code that is unrelated to the task
|
||||
- Always think about what other methods and areas of code might be affected by the code changes
|
||||
- When you think you are done with changes, run tests and make sure they are not broken
|
||||
- Do not rename any databases or tables or table columns without confirmation. Avoid such renaming if possible.
|
||||
- Do not create diagrams unless I ask explicitly
|
||||
- Do not commit binaries, create and keep .gitignore up to date and delete binaries after you are done with the task
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
description: Techstack
|
||||
alwaysApply: true
|
||||
---
|
||||
# Tech Stack
|
||||
- Use poetry
|
||||
- Cython for backend
|
||||
- Using Postgres database
|
||||
- document api with OpenAPI
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"editor.background": "#191A1C",
|
||||
"sideBar.background": "#191A1C",
|
||||
"activityBar.background": "#191A1C",
|
||||
"panel.background": "#191A1C",
|
||||
"terminal.background": "#191A1C",
|
||||
"editorGroupHeader.tabsBackground": "#191A1C",
|
||||
"tab.inactiveBackground": "#191A1C"
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -13,4 +13,8 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SatelliteProvider.Common\SatelliteProvider.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace SatelliteProvider.Configs;
|
||||
namespace SatelliteProvider.Common.Configs;
|
||||
|
||||
public class MapConfig
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace SatelliteProvider.DTO;
|
||||
namespace SatelliteProvider.Common.DTO;
|
||||
|
||||
public class Direction
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace SatelliteProvider;
|
||||
namespace SatelliteProvider.Common.DTO;
|
||||
|
||||
public class GeoPoint
|
||||
{
|
||||
@@ -1,9 +1,12 @@
|
||||
namespace SatelliteProvider.DTO;
|
||||
using SatelliteProvider.Common.Utils;
|
||||
|
||||
namespace SatelliteProvider.Common.DTO;
|
||||
|
||||
public class SatTile
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public int Zoom { get; }
|
||||
public GeoPoint LeftTop { get; }
|
||||
public GeoPoint BottomRight { get; }
|
||||
public string Url { get; set; }
|
||||
@@ -13,13 +16,14 @@ public class SatTile
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Zoom = zoom;
|
||||
Url = url;
|
||||
|
||||
LeftTop = GeoUtils.TileToWorldPos(x, y, zoom);
|
||||
BottomRight = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom);
|
||||
}
|
||||
|
||||
public string FileName => $"tile_lt_{LeftTop.Lat:F6}_{LeftTop.Lon:F6}_br_{BottomRight.Lat:F6}_{BottomRight.Lon:F6}.jpg";
|
||||
public string FileName => $"{X}.{Y}.{Zoom}.jpg";
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -0,0 +1,8 @@
|
||||
using SatelliteProvider.Common.DTO;
|
||||
|
||||
namespace SatelliteProvider.Common.Interfaces;
|
||||
|
||||
public interface ISatelliteDownloader
|
||||
{
|
||||
Task GetTiles(GeoPoint geoPoint, double radiusM, int zoomLevel, CancellationToken token = default);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,16 +1,16 @@
|
||||
using SatelliteProvider.DTO;
|
||||
using SatelliteProvider.Common.DTO;
|
||||
|
||||
namespace SatelliteProvider;
|
||||
namespace SatelliteProvider.Common.Utils;
|
||||
|
||||
public static class GeoUtils
|
||||
{
|
||||
private const double EARTH_RADIUS = 6378137;
|
||||
|
||||
public static (int x, int y) WorldToTilePos(double lat, double lon, int zoom)
|
||||
public static (int x, int y) WorldToTilePos(GeoPoint point, int zoom)
|
||||
{
|
||||
var latRad = lat * Math.PI / 180.0;
|
||||
var latRad = point.Lat * Math.PI / 180.0;
|
||||
var n = Math.Pow(2.0, zoom);
|
||||
var xTile = (int)Math.Floor((lon + 180.0) / 360.0 * n);
|
||||
var xTile = (int)Math.Floor((point.Lon + 180.0) / 360.0 * n);
|
||||
var yTile = (int)Math.Floor((1.0 - Math.Log(Math.Tan(latRad) + 1.0 / Math.Cos(latRad)) / Math.PI) / 2.0 * n);
|
||||
return (xTile, yTile);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using SatelliteProvider.Common.Configs;
|
||||
using SatelliteProvider.Common.DTO;
|
||||
using SatelliteProvider.Common.Interfaces;
|
||||
using SatelliteProvider.Common.Utils;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace SatelliteProvider.Services;
|
||||
|
||||
|
||||
public class GoogleMapsDownloader(ILogger<GoogleMapsDownloader> logger, IOptions<MapConfig> mapConfig, IHttpClientFactory httpClientFactory)
|
||||
: ISatelliteDownloader
|
||||
{
|
||||
private const string TILE_URL_TEMPLATE = "https://mt{0}.google.com/vt/lyrs=s&x={1}&y={2}&z={3}&token={4}";
|
||||
private const string USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36";
|
||||
private const int NUM_SERVERS = 4;
|
||||
private readonly string _apiKey = mapConfig.Value.ApiKey;
|
||||
|
||||
private readonly string _satDirectory = Path.Combine(Directory.GetCurrentDirectory(), "maps");
|
||||
|
||||
|
||||
private record SessionResponse(string Session);
|
||||
|
||||
private async Task<string?> GetSessionToken()
|
||||
{
|
||||
var url = $"https://tile.googleapis.com/v1/createSession?key={_apiKey}";
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
try
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(new { mapType = "satellite" });
|
||||
var response = await httpClient.PostAsync(url, new StringContent(str));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var sessionResponse = await response.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
return sessionResponse?.Session;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to get session token");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GetTiles(GeoPoint centerGeoPoint, double radiusM, int zoomLevel, CancellationToken token = default)
|
||||
{
|
||||
var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(centerGeoPoint, radiusM);
|
||||
|
||||
var (xMin, yMin) = GeoUtils.WorldToTilePos(new GeoPoint(latMax, lonMin), zoomLevel);
|
||||
var (xMax, yMax) = GeoUtils.WorldToTilePos(new GeoPoint(latMin, lonMax), zoomLevel);
|
||||
|
||||
var tilesToDownload = new ConcurrentQueue<SatTile>();
|
||||
var server = 0;
|
||||
var sessionToken = await GetSessionToken();
|
||||
|
||||
for (var y = yMin; y <= yMax + 1; y++)
|
||||
for (var x = xMin; x <= xMax + 1; x++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var url = string.Format(TILE_URL_TEMPLATE, server, x, y, zoomLevel, sessionToken);
|
||||
|
||||
tilesToDownload.Enqueue(new SatTile(x, y, zoomLevel, url));
|
||||
server = (server + 1) % NUM_SERVERS;
|
||||
}
|
||||
|
||||
var downloadTasks = new List<Task>();
|
||||
|
||||
for (int i = 0; i < NUM_SERVERS; i++)
|
||||
{
|
||||
downloadTasks.Add(Task.Run(() => DownloadTilesWorker(tilesToDownload, token), token));
|
||||
}
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
private async Task DownloadTilesWorker(ConcurrentQueue<SatTile> tilesToDownload, CancellationToken token)
|
||||
{
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
while (tilesToDownload.TryDequeue(out var tileInfo))
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
try
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT);
|
||||
var response = await httpClient.GetAsync(tileInfo.Url, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var tileData = await response.Content.ReadAsByteArrayAsync(token);
|
||||
using var tileImage = Image.Load(tileData);
|
||||
await tileImage.SaveAsync(Path.Combine(_satDirectory, tileInfo.FileName), token);
|
||||
}
|
||||
catch (HttpRequestException requestException)
|
||||
{
|
||||
logger.LogError(requestException, "Failed to download tile. Url: {Url}", tileInfo.Url);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Failed to download tile");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SatelliteProvider.Common\SatelliteProvider.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,57 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SatelliteProvider.Common.Configs;
|
||||
using SatelliteProvider.Common.DTO;
|
||||
using SatelliteProvider.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace SatelliteProvider.Tests;
|
||||
|
||||
public class GoogleMapsDownloaderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task IntegrationTest_DownloadRealTiles_ShouldDownloadBytes()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build();
|
||||
|
||||
var mapConfig = new MapConfig();
|
||||
configuration.GetSection("MapConfig").Bind(mapConfig);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddHttpClient();
|
||||
services.AddLogging(builder => builder.AddConsole());
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<GoogleMapsDownloader>>();
|
||||
var options = Options.Create(mapConfig);
|
||||
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
|
||||
|
||||
var downloader = new GoogleMapsDownloader(logger, options, httpClientFactory);
|
||||
|
||||
var centerPoint = new GeoPoint(37.7749, -122.4194);
|
||||
var radius = 200.0;
|
||||
var zoomLevel = 15;
|
||||
|
||||
await downloader.GetTiles(centerPoint, radius, zoomLevel);
|
||||
|
||||
var mapsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "maps");
|
||||
Directory.Exists(mapsDirectory).Should().BeTrue();
|
||||
|
||||
var files = Directory.GetFiles(mapsDirectory, "*.jpg");
|
||||
files.Should().NotBeEmpty();
|
||||
|
||||
var totalBytes = files.Sum(file => new FileInfo(file).Length);
|
||||
totalBytes.Should().BeGreaterThan(0);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SatelliteProvider.Services\SatelliteProvider.Services.csproj" />
|
||||
<ProjectReference Include="..\SatelliteProvider.Common\SatelliteProvider.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"MapConfig": {
|
||||
"ApiKey": "AIzaSyAXRBDBOskC5QOHG6VJWzmVJwYKcu6WH8k"
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -1,6 +1,13 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider", "SatelliteProvider\SatelliteProvider.csproj", "{35C6FC8B-92D8-4D8D-BE36-D6B181715019}"
|
||||
#
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Api", "SatelliteProvider.Api\SatelliteProvider.Api.csproj", "{35C6FC8B-92D8-4D8D-BE36-D6B181715019}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Common", "SatelliteProvider.Common\SatelliteProvider.Common.csproj", "{5499248E-F025-4091-9103-6AA02C6CB613}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services", "SatelliteProvider.Services\SatelliteProvider.Services.csproj", "{452166A0-28C3-429F-B2BD-39041FB7B5A5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Tests", "SatelliteProvider.Tests\SatelliteProvider.Tests.csproj", "{A44A2E49-9270-4938-9D34-A31CE63E636C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -12,5 +19,17 @@ Global
|
||||
{35C6FC8B-92D8-4D8D-BE36-D6B181715019}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{35C6FC8B-92D8-4D8D-BE36-D6B181715019}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{35C6FC8B-92D8-4D8D-BE36-D6B181715019}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5499248E-F025-4091-9103-6AA02C6CB613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5499248E-F025-4091-9103-6AA02C6CB613}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5499248E-F025-4091-9103-6AA02C6CB613}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5499248E-F025-4091-9103-6AA02C6CB613}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{452166A0-28C3-429F-B2BD-39041FB7B5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{452166A0-28C3-429F-B2BD-39041FB7B5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{452166A0-28C3-429F-B2BD-39041FB7B5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{452166A0-28C3-429F-B2BD-39041FB7B5A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A44A2E49-9270-4938-9D34-A31CE63E636C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A44A2E49-9270-4938-9D34-A31CE63E636C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A44A2E49-9270-4938-9D34-A31CE63E636C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A44A2E49-9270-4938-9D34-A31CE63E636C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace SatelliteProvider.Configs;
|
||||
|
||||
public class DirectoriesConfig
|
||||
{
|
||||
public string? ApiResourcesDirectory { get; set; } = null!;
|
||||
|
||||
public string VideosDirectory { get; set; } = null!;
|
||||
public string LabelsDirectory { get; set; } = null!;
|
||||
public string ImagesDirectory { get; set; } = null!;
|
||||
public string ResultsDirectory { get; set; } = null!;
|
||||
public string ThumbnailsDirectory { get; set; } = null!;
|
||||
|
||||
public string GpsSatDirectory { get; set; } = null!;
|
||||
public string GpsRouteDirectory { get; set; } = null!;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using SatelliteProvider.Configs;
|
||||
using SatelliteProvider.DTO;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace SatelliteProvider;
|
||||
|
||||
public interface ISatelliteDownloader
|
||||
{
|
||||
Task GetTiles(GeoPoint geoPoint, double radiusM, int zoomLevel, CancellationToken token = default);
|
||||
}
|
||||
|
||||
public class SatelliteDownloader(ILogger<SatelliteDownloader> logger, IOptions<MapConfig> mapConfig, IHttpClientFactory httpClientFactory)
|
||||
: ISatelliteDownloader
|
||||
{
|
||||
private const string TILE_URL_TEMPLATE = "https://mt{0}.google.com/vt/lyrs=s&x={1}&y={2}&z={3}&token={4}";
|
||||
private const int NUM_SERVERS = 4;
|
||||
private readonly string _apiKey = mapConfig.Value.ApiKey;
|
||||
|
||||
private readonly string _satDirectory = Path.Combine(Directory.GetCurrentDirectory(), "maps");
|
||||
|
||||
|
||||
private record SessionResponse(string Session);
|
||||
|
||||
private async Task<string?> GetSessionToken()
|
||||
{
|
||||
var url = $"https://tile.googleapis.com/v1/createSession?key={_apiKey}";
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
try
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(new { mapType = "satellite" });
|
||||
var response = await httpClient.PostAsync(url, new StringContent(str));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var sessionResponse = await response.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
return sessionResponse?.Session;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GetTiles(GeoPoint centerGeoPoint, double radiusM, int zoomLevel, CancellationToken token = default)
|
||||
{
|
||||
var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(centerGeoPoint, radiusM);
|
||||
|
||||
var (xMin, yMin) = GeoUtils.WorldToTilePos(latMax, lonMin, zoomLevel); // Top-left corner
|
||||
var (xMax, yMax) = GeoUtils.WorldToTilePos(latMin, lonMax, zoomLevel); // Bottom-right corner
|
||||
|
||||
var tilesToDownload = new ConcurrentQueue<SatTile>();
|
||||
var server = 0;
|
||||
var sessionToken = await GetSessionToken();
|
||||
|
||||
for (var y = yMin; y <= yMax + 1; y++)
|
||||
for (var x = xMin; x <= xMax + 1; x++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var url = string.Format(TILE_URL_TEMPLATE, server, x, y, zoomLevel, sessionToken);
|
||||
|
||||
tilesToDownload.Enqueue(new SatTile(x, y, zoomLevel, url));
|
||||
server = (server + 1) % NUM_SERVERS;
|
||||
}
|
||||
|
||||
var downloadTasks = new List<Task>();
|
||||
int downloadedCount = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < NUM_SERVERS; i++)
|
||||
{
|
||||
downloadTasks.Add(Task.Run(async () =>
|
||||
{
|
||||
using var httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
while (tilesToDownload.TryDequeue(out var tileInfo))
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
try
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36");
|
||||
var response = await httpClient.GetAsync(tileInfo.Url, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var tileData = await response.Content.ReadAsByteArrayAsync(token);
|
||||
using var tileImage = Image.Load(tileData);
|
||||
await tileImage.SaveAsync(Path.Combine(_satDirectory, tileInfo.FileName), token);
|
||||
if (tileData.Length > 0)
|
||||
{
|
||||
Interlocked.Increment(ref downloadedCount);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException requestException)
|
||||
{
|
||||
logger.LogError(requestException, $"Fail to download tile! Url: {tileInfo.Url}. {requestException.Message}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, $"Fail to download tile! {e.Message}");
|
||||
}
|
||||
}
|
||||
}, token));
|
||||
}
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user