using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using gehGassi.Core.Interfaces;
using gehGassi.Domain.Common;
namespace gehGassi.External.Services
{
///
/// Service der zu einer Adresse Längen- und Breitengrad ermittelt, oder zu Längen- und Breitengrad eine Adresse
///
public class GeoLocationService : IGeoLocationService
{
private readonly HttpClient _httpClient;
///
/// Erstellt eine Instanz
///
/// Instanz eines HttpClient
public GeoLocationService(HttpClient httpClient)
{
_httpClient = httpClient;
}
///
/// Längen und Breitengrad zu einer Adresse suchen.
/// Zuerst wird versucht die Position mit Strasse, PLZ, Ort und Land zu finden.
/// Wenn das nicht klappt, dann mit PLZ, Ort, Land.
/// Wenn das nicht klappt, dann PLZ Land.
///
/// Adresse
/// Gewünschte Sprache ISO2
/// Geo-Position
public async Task GetLocationAsync(Address address, string language)
{
var result = new GeoLocation()
{
Latitude = 0,
Longitude = 0,
Success = false,
ErrorMessage = "No matching address was found."
};
if (address == null)
{
result.ErrorMessage = "No address provided.";
return result;
}
if (string.IsNullOrEmpty(address.CountryCode))
{
result.ErrorMessage = "No country was specified.";
return result;
}
if (string.IsNullOrEmpty(address.State))
{
result.ErrorMessage = "No state code was specified.";
return result;
}
var innerAddress = new Address
{
AddressLine1 = address.AddressLine1,
AddressLine2 = address.AddressLine2,
Zip = address.Zip,
City = address.City,
State = address.State,
CountryCode = address.CountryCode
};
try
{
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear();
_httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
List locations = new List();
var queryLine = GenerateQueryLine(innerAddress);
var query = WebUtility.UrlEncode(queryLine);
var apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
locations = await apiResult.Content.ReadFromJsonAsync>();
}
if (locations == null || locations.Count == 0)
{
await Task.Delay(1000);
innerAddress.AddressLine1 = string.Empty;
queryLine = GenerateQueryLine(innerAddress);
query = WebUtility.UrlEncode(queryLine);
apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
locations = await apiResult.Content.ReadFromJsonAsync>();
}
}
if (locations == null || locations.Count == 0)
{
await Task.Delay(1000);
innerAddress.City = string.Empty;
queryLine = GenerateQueryLine(innerAddress);
query = WebUtility.UrlEncode(queryLine);
apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
locations = await apiResult.Content.ReadFromJsonAsync>();
}
}
if (locations == null || locations.Count == 0)
{
await Task.Delay(1000);
innerAddress.Zip = string.Empty;
queryLine = GenerateQueryLine(innerAddress);
query = WebUtility.UrlEncode(queryLine);
apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
locations = await apiResult.Content.ReadFromJsonAsync>();
}
}
if (locations != null && locations.Count > 0)
{
if (locations is { Count: 1 })
{
result.Success = true;
result.ErrorMessage = string.Empty;
result.Latitude = double.Parse(locations[0].Latitude, CultureInfo.InvariantCulture);
result.Longitude = double.Parse(locations[0].Longitude, CultureInfo.InvariantCulture);
}
else
{
result.Success = false;
result.ErrorMessage = "No matching address was found.";
}
}
else
{
result.Success = false;
result.ErrorMessage = "Error in communication with geo service. Code: " + apiResult.StatusCode;
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
///
/// Längen und Breitengrad zu einer Adresse suchen.
///
/// Adresse als Text-Zeile
/// Gewünschte Sprache ISO2
/// Geo-Position
public async Task GetLocationAsync(string address, string language)
{
var result = new GeoLocation()
{
Latitude = 0,
Longitude = 0,
Success = false,
ErrorMessage = "No matching address was found."
};
if (string.IsNullOrWhiteSpace(address))
{
result.ErrorMessage = "No address provided.";
return result;
}
try
{
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear();
_httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
var query = WebUtility.UrlEncode($"{address}");
var apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
var locations = await apiResult.Content.ReadFromJsonAsync>();
if (locations is { Count: 1 })
{
result.Success = true;
result.ErrorMessage = string.Empty;
result.Latitude = double.Parse(locations[0].Latitude, CultureInfo.InvariantCulture);
result.Longitude = double.Parse(locations[0].Longitude, CultureInfo.InvariantCulture);
}
else
{
result.Success = false;
result.ErrorMessage = "No matching address was found.";
}
}
else
{
result.Success = false;
result.ErrorMessage = "Error in communication with geo service. Code: " + apiResult.StatusCode;
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
///
/// Längen und Breitengrad zu einer Adresse suchen. Adresse wird mit Details mitgeliefert.
/// Zuerst wird versucht die Position mit Strasse, PLZ, Ort und Land zu finden.
/// Wenn das nicht klappt, dann mit PLZ, Ort, Land.
/// Wenn das nicht klappt, dann PLZ Land.
///
/// Adresse
/// Gewünschte Sprache ISO2
/// Geo-Position
public async Task GetLocationFillAsync(Address address, string language)
{
var result = new GeoLocationFill()
{
Latitude = 0,
Longitude = 0,
Success = false,
ErrorMessage = "No matching address was found."
};
if (address == null)
{
result.ErrorMessage = "No address provided.";
return result;
}
if (string.IsNullOrEmpty(address.CountryCode))
{
result.ErrorMessage = "No country was specified.";
return result;
}
if (string.IsNullOrEmpty(address.State))
{
result.ErrorMessage = "No state code was specified.";
return result;
}
try
{
var queryLine = GenerateQueryLine(address);
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear();
_httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
var query = WebUtility.UrlEncode(queryLine);
var apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1&addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
var locations = await apiResult.Content.ReadFromJsonAsync>();
if (locations is { Count: 1 })
{
result.Success = true;
result.ErrorMessage = string.Empty;
result.Latitude = double.Parse(locations[0].Latitude, CultureInfo.InvariantCulture);
result.Longitude = double.Parse(locations[0].Longitude, CultureInfo.InvariantCulture);
result.Address.AddressLine1 = $"{locations[0].Address.AddressLine1} {locations[0].Address.HouseNumber}";
result.Address.AddressLine2 = string.Empty;
result.Address.City = locations[0].Address.City;
result.Address.Zip = locations[0].Address.Zip;
result.Address.State = locations[0].Address.StateCode;
result.Address.CountryCode = locations[0].Address.CountryIso;
}
else
{
result.Success = false;
result.ErrorMessage = "No matching address was found.";
}
}
else
{
result.Success = false;
result.ErrorMessage = "Error in communication with geo service. Code: " + apiResult.StatusCode;
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
///
/// Längen und Breitengrad zu einer Adresse suchen. Adresse wird mit Details mitgeliefert.
///
/// Adresse als Text-Zeile
/// Gewünschte Sprache ISO2
/// Geo-Position
public async Task GetLocationFillAsync(string address, string language)
{
var result = new GeoLocationFill()
{
Latitude = 0,
Longitude = 0,
Success = false,
ErrorMessage = "No matching address was found."
};
if (string.IsNullOrWhiteSpace(address))
{
result.ErrorMessage = "No address provided.";
return result;
}
try
{
//Es darf immer nur alle 1 Sekunden eine Anfrage gesendet werden. Daher "emulieren" wir das hier.
await Task.Delay(1000);
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear();
_httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
var query = WebUtility.UrlEncode($"{address}");
var apiResult = await _httpClient.GetAsync($"search?q={query}&format=jsonv2&limit=1addressdetails=1");
if (apiResult.IsSuccessStatusCode)
{
var locations = await apiResult.Content.ReadFromJsonAsync>();
if (locations is { Count: 1 })
{
result.Success = true;
result.ErrorMessage = string.Empty;
result.Latitude = double.Parse(locations[0].Latitude, CultureInfo.InvariantCulture);
result.Longitude = double.Parse(locations[0].Longitude, CultureInfo.InvariantCulture);
result.Address.AddressLine1 = $"{locations[0].Address.AddressLine1} {locations[0].Address.HouseNumber}";
result.Address.AddressLine2 = string.Empty;
result.Address.City = locations[0].Address.City;
result.Address.Zip = locations[0].Address.Zip;
result.Address.State = locations[0].Address.StateCode;
result.Address.CountryCode = locations[0].Address.CountryIso;
}
else
{
result.Success = false;
result.ErrorMessage = "No matching address was found.";
}
}
else
{
result.Success = false;
result.ErrorMessage = "Error in communication with geo service. Code: " + apiResult.StatusCode;
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
///
/// Reverse-Geocoding
///
/// Breitengrad
/// Längengrad
/// Gewünschte Sprache ISO2
/// ReverseLocation
public async Task GetReverseAsync(double lat, double lng, string language)
{
var result = new ReverseLocation()
{
Success = false,
ErrorMessage = "No matching address was found."
};
try
{
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear();
_httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
var apiResult = await _httpClient.GetAsync($"reverse?lat={lat.ToString(CultureInfo.InvariantCulture)}&lon={lng.ToString(CultureInfo.InvariantCulture)}&format=jsonv2");
if (apiResult.IsSuccessStatusCode)
{
var reverseResult = await apiResult.Content.ReadFromJsonAsync();
if (reverseResult != null)
{
result.Success = true;
result.ErrorMessage = string.Empty;
result.Address.AddressLine1 = $"{reverseResult.Address.AddressLine1} {reverseResult.Address.HouseNumber}";
result.Address.AddressLine2 = string.Empty;
result.Address.City = reverseResult.Address.City;
result.Address.Zip = reverseResult.Address.Zip;
result.Address.State = reverseResult.Address.StateCode;
result.Address.CountryCode = reverseResult.Address.CountryIso;
}
else
{
result.Success = false;
result.ErrorMessage = "No matching address was found.";
}
}
else
{
result.Success = false;
result.ErrorMessage = "Error in communication with geo service. Code: " + apiResult.StatusCode;
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
#region Helper
///
/// Erstellt aus einer Adresse einen Anfrage-String
///
/// Adresse
/// Anfrage-String
private static string GenerateQueryLine(Address address)
{
var queryLine = string.Empty;
if (!string.IsNullOrWhiteSpace(address.AddressLine1))
{
if (queryLine.Length > 0)
queryLine += ", ";
queryLine += $"{address.AddressLine1}";
}
if (!string.IsNullOrWhiteSpace(address.Zip))
{
if (queryLine.Length > 0)
queryLine += ", ";
queryLine += $"{address.Zip}";
}
if (!string.IsNullOrWhiteSpace(address.City))
{
if (queryLine.Length > 0)
queryLine += ", ";
queryLine += $"{address.City}";
}
if (queryLine.Length == 0)
queryLine += $"{address.State}";
if (queryLine.Length > 0)
queryLine += ", ";
queryLine += $"{address.CountryCode}";
return queryLine;
}
#endregion
}
///
/// Hilfsklasse für die Zuordnung von Tags
///
internal class ApiGeoResult
{
///
/// Openstreetmap Platz-ID
///
[JsonPropertyName("place_id")]
public long PlaceId { get; set; }
///
/// Breitengrad
///
[JsonPropertyName("lat")]
public string Latitude { get; set; }
///
/// Längengrad
///
[JsonPropertyName("lon")]
public string Longitude { get; set; }
///
/// Voller Name dargestellt
///
[JsonPropertyName("display_name")]
public string DisplayName { get; set; }
}
///
/// Hilfsklasse für die ERgänzung einer Adresse
///
internal class ApiGeoFillResult : ApiGeoResult
{
///
/// Adresse
///
[JsonPropertyName("address")]
public ApiReverseAddress Address { get; set; }
///
/// Erstellt eine Instanz
///
public ApiGeoFillResult()
{
Address = new ApiReverseAddress();
}
}
///
/// Hilfsklasse für die Zuordnung von Tags
///
internal class ApiReverseResult
{
///
/// Openstreetmap Platz-ID
///
[JsonPropertyName("place_id")]
public long PlaceId { get; set; }
///
/// Adresse
///
[JsonPropertyName("address")]
public ApiReverseAddress Address { get; set; }
///
/// Erstellt eine Instanz
///
public ApiReverseResult()
{
Address = new ApiReverseAddress();
}
}
///
/// Adress-Mapping
///
internal class ApiReverseAddress
{
[JsonPropertyName("road")]
public string AddressLine1 { get; set; }
[JsonPropertyName("house_number")]
public string HouseNumber { get; set; }
[JsonPropertyName("city")]
public string City { get; set; }
[JsonPropertyName("postcode")]
public string Zip { get; set; }
[JsonPropertyName("state")]
public string StateName { get; set; }
[JsonPropertyName("ISO3166-2-lvl4")]
public string StateCode { get; set; }
[JsonPropertyName("country")]
public string CountryName { get; set; }
[JsonPropertyName("country_code")]
public string CountryIso { get; set; }
}
}