dotnet-geo-management/Svrnty.GeoManagement.Google/GeoManagementGoogleProvider.cs

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;
}
}
}