mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-22 05:26:39 +00:00
107 lines
4.3 KiB
C#
107 lines
4.3 KiB
C#
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);
|
|
}
|
|
} |