SHA256
SomniaGuestLog - Address search/query using Nominatim - Initial implementation
Initial implementation for address search/query using external APIs (OSM Nominatim) & Added required classes for supporting more APIs in the future.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Somnia.GuestLog.Models.Configuration;
|
||||
using Somnia.GuestLog.Utils.DI;
|
||||
|
||||
namespace Somnia.GuestLog.Services.AddressQuery;
|
||||
|
||||
public class ConfigFileAddressQueryFactory(
|
||||
IServiceProvider serviceProvider,
|
||||
IOptions<AddressQueryConfig> config
|
||||
) : IAddressQueryFactory, IFactoryDI
|
||||
{
|
||||
public static void RegisterFactoryItems(IServiceCollection services)
|
||||
{
|
||||
services.AddKeyedTransient<IAddressQueryService, NominatimAddressQueryService>(AddressQueryConfig.AddressQueryProviderEnum.Nominatim);
|
||||
}
|
||||
|
||||
public IAddressQueryService GetService()
|
||||
{
|
||||
if (config.Value.EnableAddressQuery == false)
|
||||
{
|
||||
throw new InvalidOperationException("AddressQuery is disabled. Enable and configure it first!");
|
||||
}
|
||||
|
||||
return serviceProvider.GetKeyedService<IAddressQueryService>(config.Value.Provider)
|
||||
?? throw new InvalidOperationException($"AddressQueryService not found for provider: '{config.Value.Provider}'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Somnia.GuestLog.Services.AddressQuery;
|
||||
|
||||
public interface IAddressQueryFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the AddressQuery service implementation given current configuration.
|
||||
/// </summary>
|
||||
public IAddressQueryService GetService();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
|
||||
namespace Somnia.GuestLog.Services.AddressQuery;
|
||||
|
||||
public interface IAddressQueryService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Search an address by plain text query.
|
||||
/// Data is returned as a structured address.
|
||||
/// This service allow to complete and validate addresses.
|
||||
///
|
||||
/// Note: Not all fields are always available for a given query.
|
||||
/// </summary>
|
||||
/// <param name="query">Address to search</param>
|
||||
/// <returns>List of resulting structured addresses</returns>
|
||||
public Task<List<AddressResultDTO>> SearchAddressAsync(string query);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Somnia.GuestLog.Models.Configuration;
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
|
||||
namespace Somnia.GuestLog.Services.AddressQuery;
|
||||
|
||||
public class NominatimAddressQueryService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<NominatimAddressQueryService> logger,
|
||||
IOptions<AddressQueryConfig> configOptions
|
||||
) : IAddressQueryService
|
||||
{
|
||||
private const string defaultBaseAddr = "https://nominatim.openstreetmap.org";
|
||||
private const string defaultUserAgent = "Somnia-GuestLog";
|
||||
private const string apiUri = "/search";
|
||||
|
||||
private readonly AddressQueryConfig config = configOptions.Value;
|
||||
|
||||
|
||||
public async Task<List<AddressResultDTO>> SearchAddressAsync(string query)
|
||||
{
|
||||
var http = GetHttpClient();
|
||||
List<AddressResultDTO> rsList = [];
|
||||
|
||||
try
|
||||
{
|
||||
string uri = GetRequestUri(query);
|
||||
List<ApiResult> apiList = (await http.GetFromJsonAsync<List<ApiResult>>(uri)) ?? [];
|
||||
|
||||
foreach (ApiResult result in apiList)
|
||||
{
|
||||
if (result.Address is null)
|
||||
{
|
||||
logger.LogWarning("Skipping result without address details. '{result}'", result);
|
||||
continue;
|
||||
}
|
||||
|
||||
rsList.Add(new()
|
||||
{
|
||||
HouseNumber = result.Address.House_number,
|
||||
AddressRoad = result.Address.Road ?? result.Address.Square,
|
||||
AddressCity = result.Address.City ?? result.Address.Municipality ?? result.Address.Village ?? result.Address.Village,
|
||||
AddressProvince = result.Address.Province,
|
||||
AddressState = result.Address.State,
|
||||
AddressCountry = result.Address.Country,
|
||||
ZipCode = result.Address.Postcode
|
||||
});
|
||||
}
|
||||
|
||||
return rsList;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error getting info from API!");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HttpClient GetHttpClient()
|
||||
{
|
||||
HttpClient client = httpClientFactory.CreateClient();
|
||||
|
||||
Uri baseAddr = new(config.ServiceURL ?? defaultBaseAddr);
|
||||
|
||||
client.BaseAddress = baseAddr;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(config.UserAgent ?? defaultUserAgent);
|
||||
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("es");
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static string GetRequestUri(string query)
|
||||
{
|
||||
return QueryHelpers.AddQueryString(apiUri, [
|
||||
new KeyValuePair<string, string?>("format", "jsonv2"),
|
||||
new KeyValuePair<string, string?>("addressdetails", "1"),
|
||||
new KeyValuePair<string, string?>("layer", "address"),
|
||||
new KeyValuePair<string, string?>("q", query),
|
||||
]);
|
||||
}
|
||||
|
||||
private record class ApiResultAddress(
|
||||
string? House_number = null,
|
||||
string? Road = null,
|
||||
string? Square = null,
|
||||
string? City = null,
|
||||
string? Municipality = null,
|
||||
string? Village = null,
|
||||
string? Town = null,
|
||||
string? Province = null,
|
||||
string? State = null,
|
||||
string? Country = null,
|
||||
string? Postcode = null
|
||||
);
|
||||
|
||||
private record class ApiResult(
|
||||
string? Osm_type = null,
|
||||
long? Osm_id = null,
|
||||
string? Category = null,
|
||||
string? AddressType = null,
|
||||
string? Name = null,
|
||||
string? Display_name = null,
|
||||
ApiResultAddress? Address = null
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user