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