210 lines
6.6 KiB
C#
210 lines
6.6 KiB
C#
using GoogleApi;
|
|
using GoogleApi.Entities.Maps.Geocoding.Address.Request;
|
|
using GoogleApi.Entities.Maps.Geocoding.Location.Request;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Svrnty.GeoManagement.Abstractions.Abstractions;
|
|
using Svrnty.GeoManagement.Abstractions.Models;
|
|
using Svrnty.GeoManagement.Google.Configuration;
|
|
using Svrnty.GeoManagement.Google.Mapping;
|
|
|
|
namespace Svrnty.GeoManagement.Google;
|
|
|
|
/// <summary>
|
|
/// Google Geocoding API implementation of IGeoManagementProvider
|
|
/// </summary>
|
|
public class GeoManagementGoogleProvider : IGeoManagementProvider
|
|
{
|
|
private readonly GoogleGeoManagementOptions _options;
|
|
private readonly ILogger<GeoManagementGoogleProvider> _logger;
|
|
|
|
public GeoManagementGoogleProvider(
|
|
IOptions<GoogleGeoManagementOptions> options,
|
|
ILogger<GeoManagementGoogleProvider> logger)
|
|
{
|
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
|
|
if (string.IsNullOrWhiteSpace(_options.ApiKey))
|
|
{
|
|
throw new ArgumentException("Google API key is required", nameof(options));
|
|
}
|
|
}
|
|
|
|
public async Task<GeoPoint?> GetGeoPointAsync(
|
|
Address address,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var addressString = address.GetFormattedAddress();
|
|
_logger.LogDebug("Geocoding address: {Address}", addressString);
|
|
|
|
var request = new AddressGeocodeRequest
|
|
{
|
|
Key = _options.ApiKey,
|
|
Address = addressString
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(_options.Language) &&
|
|
Enum.TryParse<GoogleApi.Entities.Common.Enums.Language>(_options.Language, true, out var language))
|
|
request.Language = language;
|
|
|
|
if (!string.IsNullOrWhiteSpace(_options.Region))
|
|
request.Region = _options.Region;
|
|
|
|
var response = await GoogleMaps.Geocode.AddressGeocode.QueryAsync(request, cancellationToken);
|
|
|
|
if (response.Status != GoogleApi.Entities.Common.Enums.Status.Ok)
|
|
{
|
|
_logger.LogWarning("Google Geocoding API returned status: {Status}", response.Status);
|
|
return null;
|
|
}
|
|
|
|
var result = response.Results?.FirstOrDefault();
|
|
var geoPoint = GoogleAddressMapper.MapToGeoPoint(result);
|
|
|
|
if (geoPoint != null)
|
|
{
|
|
_logger.LogDebug("Successfully geocoded address to: {Latitude}, {Longitude}",
|
|
geoPoint.Latitude, geoPoint.Longitude);
|
|
}
|
|
|
|
return geoPoint;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error geocoding address: {Address}", address.GetFormattedAddress());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<Address?> ReverseGeocodeAsync(
|
|
GeoPoint geoPoint,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Reverse geocoding location: {Latitude}, {Longitude}",
|
|
geoPoint.Latitude, geoPoint.Longitude);
|
|
|
|
var request = new LocationGeocodeRequest
|
|
{
|
|
Key = _options.ApiKey,
|
|
Location = new GoogleApi.Entities.Common.Coordinate(geoPoint.Latitude, geoPoint.Longitude)
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(_options.Language) &&
|
|
Enum.TryParse<GoogleApi.Entities.Common.Enums.Language>(_options.Language, true, out var language))
|
|
request.Language = language;
|
|
|
|
var response = await GoogleMaps.Geocode.LocationGeocode.QueryAsync(request, cancellationToken);
|
|
|
|
if (response.Status != GoogleApi.Entities.Common.Enums.Status.Ok)
|
|
{
|
|
_logger.LogWarning("Google Reverse Geocoding API returned status: {Status}", response.Status);
|
|
return null;
|
|
}
|
|
|
|
var result = response.Results?.FirstOrDefault();
|
|
var address = GoogleAddressMapper.MapToAddress(result);
|
|
|
|
if (address != null)
|
|
{
|
|
_logger.LogDebug("Successfully reverse geocoded to: {Address}", address.GetFormattedAddress());
|
|
}
|
|
|
|
return address;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error reverse geocoding location: {Latitude}, {Longitude}",
|
|
geoPoint.Latitude, geoPoint.Longitude);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<Address?> NormalizeAddressAsync(
|
|
Address address,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Normalizing address: {Address}", address.GetFormattedAddress());
|
|
|
|
// First geocode to get coordinates
|
|
var geoPoint = await GetGeoPointAsync(address, cancellationToken);
|
|
if (geoPoint == null)
|
|
{
|
|
_logger.LogWarning("Could not geocode address for normalization: {Address}",
|
|
address.GetFormattedAddress());
|
|
return null;
|
|
}
|
|
|
|
// Then reverse geocode to get normalized address
|
|
var normalizedAddress = await ReverseGeocodeAsync(geoPoint, cancellationToken);
|
|
|
|
if (normalizedAddress != null)
|
|
{
|
|
_logger.LogDebug("Successfully normalized address from {Original} to {Normalized}",
|
|
address.GetFormattedAddress(), normalizedAddress.GetFormattedAddress());
|
|
}
|
|
|
|
return normalizedAddress;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error normalizing address: {Address}", address.GetFormattedAddress());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<Address?> NormalizeAddressAsync(
|
|
string address,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Normalizing address string: {Address}", address);
|
|
|
|
var request = new AddressGeocodeRequest
|
|
{
|
|
Key = _options.ApiKey,
|
|
Address = address
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(_options.Language) &&
|
|
Enum.TryParse<GoogleApi.Entities.Common.Enums.Language>(_options.Language, true, out var language))
|
|
request.Language = language;
|
|
|
|
if (!string.IsNullOrWhiteSpace(_options.Region))
|
|
request.Region = _options.Region;
|
|
|
|
var response = await GoogleMaps.Geocode.AddressGeocode.QueryAsync(request, cancellationToken);
|
|
|
|
if (response.Status != GoogleApi.Entities.Common.Enums.Status.Ok)
|
|
{
|
|
_logger.LogWarning("Google Geocoding API returned status: {Status} for address: {Address}",
|
|
response.Status, address);
|
|
return null;
|
|
}
|
|
|
|
var result = response.Results?.FirstOrDefault();
|
|
var normalizedAddress = GoogleAddressMapper.MapToAddress(result);
|
|
|
|
if (normalizedAddress != null)
|
|
{
|
|
_logger.LogDebug("Successfully normalized address string to: {Normalized}",
|
|
normalizedAddress.GetFormattedAddress());
|
|
}
|
|
|
|
return normalizedAddress;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error normalizing address string: {Address}", address);
|
|
return null;
|
|
}
|
|
}
|
|
}
|