diff --git a/Data/Entities/Address.cs b/Data/Entities/Address.cs index fd156065c..ce709453e 100644 --- a/Data/Entities/Address.cs +++ b/Data/Entities/Address.cs @@ -1,7 +1,9 @@ using BS.Shared; +using System.Diagnostics; namespace BeWo.Data.Entities { + [DebuggerDisplay("{Oid}: {Street}, {Town}")] public class Address : BeWoEntityBase { public static string PropertyName_AddressLine1 = "AddressLine1"; @@ -30,6 +32,12 @@ namespace BeWo.Data.Entities private string _Town; + private decimal? _Latitude; + + private decimal? _Longitude; + + private long? _L_Version; + public Address() { this._Tid = TableID.Address; @@ -114,7 +122,7 @@ namespace BeWo.Data.Entities } } } - + public virtual string Street { get @@ -147,6 +155,54 @@ namespace BeWo.Data.Entities } } + public virtual decimal? Latitude + { + get + { + return this._Latitude; + } + + set + { + if (this.AreDifferent(this._Latitude, value)) + { + this._Latitude = value; + } + } + } + + public virtual decimal? Longitude + { + get + { + return this._Longitude; + } + + set + { + if (this.AreDifferent(this._Longitude, value)) + { + this._Longitude = value; + } + } + } + + public virtual long? L_Version + { + get + { + return this._L_Version; + } + + set + { + if (this.AreDifferent(this._L_Version, value)) + { + this._L_Version = value; + } + } + } + public virtual Address CopyToNew() { return new Address @@ -161,5 +217,13 @@ namespace BeWo.Data.Entities Town = this.Town }; } + + public override int GetHashCode() + { + if (this is null) + return 0; + + return Oid.Value.GetHashCode(); + } } } \ No newline at end of file diff --git a/Data/Entities/AddressRoute.cs b/Data/Entities/AddressRoute.cs index d5c55c453..b5f2337ee 100644 --- a/Data/Entities/AddressRoute.cs +++ b/Data/Entities/AddressRoute.cs @@ -1,18 +1,20 @@ using BS.Shared; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BeWo.Data.Entities { + [DebuggerDisplay("{Oid}({Origin.Oid},{Destination.Oid}): {Distance_in_meter}m, {Time_in_seconds}s")] public class AddressRoute : BeWoEntityBase { - private long address1_oid; - private long address1_version; - private long address2_oid; - private long address2_version; + private Address _Origin; + private long _Origin_Version; + private Address _Destination; + private long _Destination_Version; private int distance_in_meter; private string distance_in_meter_txt; @@ -24,62 +26,62 @@ namespace BeWo.Data.Entities _Tid = TableID.AddressRoute; } - public virtual long Address1_Oid + public virtual Address Origin { get { - return address1_oid; + return _Origin; } set { - if (AreDifferent(address1_oid, value)) + if (AreDifferent(_Origin, value)) { - address1_oid = value; + _Origin = value; } } } - public virtual long Address1_Version + public virtual long Origin_Version { get { - return address1_version; + return _Origin_Version; } set { - if (AreDifferent(address1_version, value)) + if (AreDifferent(_Origin_Version, value)) { - address1_version = value; + _Origin_Version = value; } } } - public virtual long Address2_Oid + public virtual Address Destination { get { - return address2_oid; + return _Destination; } set { - if (AreDifferent(address2_oid, value)) + if (AreDifferent(_Destination, value)) { - address2_oid = value; + _Destination = value; } } } - public virtual long Address2_Version + public virtual long Destination_Version { get { - return address2_version; + return _Destination_Version; } set { - if (AreDifferent(address2_version, value)) + if (AreDifferent(_Destination_Version, value)) { - address2_version = value; + _Destination_Version = value; } } } diff --git a/Data/Mappings/Address.hbm.xml b/Data/Mappings/Address.hbm.xml index bc2beb473..3aaee5e42 100644 --- a/Data/Mappings/Address.hbm.xml +++ b/Data/Mappings/Address.hbm.xml @@ -6,19 +6,22 @@ - + - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Data/Mappings/AddressRoute.hbm.xml b/Data/Mappings/AddressRoute.hbm.xml index cf0a9649b..13ea6c719 100644 --- a/Data/Mappings/AddressRoute.hbm.xml +++ b/Data/Mappings/AddressRoute.hbm.xml @@ -15,11 +15,12 @@ - - - - + + + + + \ No newline at end of file diff --git a/DownloadServer/Web.config b/DownloadServer/Web.config index 97488bee4..ddb1139ee 100644 --- a/DownloadServer/Web.config +++ b/DownloadServer/Web.config @@ -104,6 +104,9 @@ + + + diff --git a/Model/Changes_2023_03_20_Address_Längengrad.txt b/Model/Changes_2023_03_20_Address_Längengrad.txt new file mode 100644 index 000000000..8a738cba0 --- /dev/null +++ b/Model/Changes_2023_03_20_Address_Längengrad.txt @@ -0,0 +1,4 @@ +ALTER TABLE `bewodemo`.`address` +ADD COLUMN `Latitude` DECIMAL(8,6) NULL DEFAULT NULL AFTER `AddressLine2`, +ADD COLUMN `Longitude` DECIMAL(9,6) NULL DEFAULT NULL AFTER `Latitude`, +ADD COLUMN `L_Version` BIGINT(19) NULL DEFAULT NULL AFTER `Longitude`; \ No newline at end of file diff --git a/Service/DCEntityMapper/AddressRouteDC_AddressRoute.cs b/Service/DCEntityMapper/AddressRouteDC_AddressRoute.cs index 90e6601d8..fd2dbf175 100644 --- a/Service/DCEntityMapper/AddressRouteDC_AddressRoute.cs +++ b/Service/DCEntityMapper/AddressRouteDC_AddressRoute.cs @@ -21,10 +21,10 @@ namespace BeWo.Service.DCEntityMapper pDataContract.UdpUser = pEntity.UdpUser; pDataContract.SystemEntryID = pEntity.SystemEntryID; - pDataContract.Address1_Oid = pEntity.Address1_Oid; - pDataContract.Address1_Version = pEntity.Address1_Version; - pDataContract.Address2_Oid = pEntity.Address2_Oid; - pDataContract.Address2_Version = pEntity.Address2_Version; + pDataContract.Origin_Address_Oid = pEntity.Origin.Oid.Value; + pDataContract.Origin_Address_Version = pEntity.Origin_Version; + pDataContract.Destination_Address_Oid = pEntity.Destination.Oid.Value; + pDataContract.Destination_Address_Version = pEntity.Destination_Version; pDataContract.Distance_in_meter = pEntity.Distance_in_meter; pDataContract.Distance_in_meter_txt = pEntity.Distance_in_meter_txt; @@ -36,21 +36,23 @@ namespace BeWo.Service.DCEntityMapper public override AddressRoute MergeWithEntity(AddressRouteDC pDataContract, AddressRoute pEntity) { - this.ConcurrencyCheck(pDataContract.Version, pEntity); + throw new NotImplementedException("#84971983047130984"); - pEntity.Oid = pDataContract.Oid; - pEntity.Notice = pDataContract.Notice; - pEntity.SystemEntryID = pDataContract.SystemEntryID; + //this.ConcurrencyCheck(pDataContract.Version, pEntity); - pEntity.Address1_Oid = pDataContract.Address1_Oid; - pEntity.Address1_Version = pDataContract.Address1_Version; - pEntity.Address2_Oid = pDataContract.Address2_Oid; - pEntity.Address2_Version = pDataContract.Address2_Version; + //pEntity.Oid = pDataContract.Oid; + //pEntity.Notice = pDataContract.Notice; + //pEntity.SystemEntryID = pDataContract.SystemEntryID; - pEntity.Distance_in_meter = pDataContract.Distance_in_meter; - pEntity.Distance_in_meter_txt = pDataContract.Distance_in_meter_txt; - pEntity.Time_in_seconds = pDataContract.Time_in_sec; - pEntity.Time_in_seconds_txt = pDataContract.Time_in_sec_txt; + //pEntity.Origin = pDataContract.Origin_Address_Oid; + //pEntity.Address1_Version = pDataContract.Origin_Address_Version; + //pEntity.Address2_Oid = pDataContract.Destination_Address_Oid; + //pEntity.Address2_Version = pDataContract.Destination_Address_Version; + + //pEntity.Distance_in_meter = pDataContract.Distance_in_meter; + //pEntity.Distance_in_meter_txt = pDataContract.Distance_in_meter_txt; + //pEntity.Time_in_seconds = pDataContract.Time_in_sec; + //pEntity.Time_in_seconds_txt = pDataContract.Time_in_sec_txt; return pEntity; } diff --git a/Service/Service.csproj b/Service/Service.csproj index a155a263d..eaaf9dd74 100644 --- a/Service/Service.csproj +++ b/Service/Service.csproj @@ -484,8 +484,10 @@ + + diff --git a/Service/ServiceImplementations/DownloadBeWoServiceImp.cs b/Service/ServiceImplementations/DownloadBeWoServiceImp.cs index 90ade9f6c..633b62267 100644 --- a/Service/ServiceImplementations/DownloadBeWoServiceImp.cs +++ b/Service/ServiceImplementations/DownloadBeWoServiceImp.cs @@ -42,19 +42,28 @@ namespace BeWo.Service.ServiceImplementations public object CallMeForTesting(string token, object input) { - if (token != "dafslihjkriouuorgha3431342..") - return "Nö"; + try + { + if (token != "dafslihjkriouuorgha3431342..") + return "Nö"; - var address1 = DAOFactory.GenericDAO.LoadByID
(3000); - var address2 = DAOFactory.GenericDAO.LoadByID
(3001); - var address3 = DAOFactory.GenericDAO.LoadByID
(3002); - var address4 = DAOFactory.GenericDAO.LoadByID
(3003); - var address5 = DAOFactory.GenericDAO.LoadByID
(3004); + return null; - var add1 = new Address[] { address1, address2, address5 }; - var add2 = new Address[] { address3, address4 }; + var address1 = DAOFactory.GenericDAO.LoadByID
(3000); + var address2 = DAOFactory.GenericDAO.LoadByID
(3001); + var address3 = DAOFactory.GenericDAO.LoadByID
(3002); + var address4 = DAOFactory.GenericDAO.LoadByID
(3003); + var address5 = DAOFactory.GenericDAO.LoadByID
(3004); - AddressRouteManager.GetAddressRoute(add1, add2); + var add1 = new Address[] { address1, address2, address5 }; + var add2 = new Address[] { address3, address4 }; + + AddressRouteManager.GetMatrix(add1, add2); + } + catch(Exception e) + { + + } return null; } diff --git a/Service/ServiceImplementations/EmployeeServiceImp.cs b/Service/ServiceImplementations/EmployeeServiceImp.cs index 5fd1cf5a1..9be9ae2be 100644 --- a/Service/ServiceImplementations/EmployeeServiceImp.cs +++ b/Service/ServiceImplementations/EmployeeServiceImp.cs @@ -1500,7 +1500,7 @@ namespace BeWo.Service.ServiceImplementations } } - private List GetDistanceForVertreterEmployees(long customerOid, ref List list, List employees) + private void GetDistanceForVertreterEmployees(long customerOid, ref List list, List employees) { var customer = DAOFactory.GenericDAO.LoadByID(customerOid); var school = customer.Customer2OrganisationList.FirstOrDefault(f => f.ValueList.Any(b => b.Entry.Type == ValueListEntryType.EnvironmentOrganisationType && b.Entry.Value.Equals("Schule"))); @@ -1508,14 +1508,15 @@ namespace BeWo.Service.ServiceImplementations var origin = new Address[] { school.Person.Address }; var destinations = employees.Select(x => x.Person.Address).ToArray(); - var routes = AddressRouteManager.GetAddressRoute(origin, destinations); + var matrix = AddressRouteManager.GetMatrix(origin, destinations); - for (int i = 0; i < destinations.Length; i++) + Employee empl; AddressRoute route; + foreach (var employee in list) { - list[i].AddressRoute = MapperFactory.AddressRouteDC_AddressRoute.MapToNewDC(routes[0, i]); + empl = employees.FirstOrDefault(e => e.Person.Oid.Value == employee.PersonOid); + route = matrix.GetRoute(school.Person.Address, empl.Person.Address); + employee.AddressRoute = MapperFactory.AddressRouteDC_AddressRoute.MapToNewDC(route); } - - return null; } public List LoadEmployeeTokenRelationsByOid(IEnumerable pOids) diff --git a/Service/ServiceUtils/DistanceCalculator/AddressRouteManager.cs b/Service/ServiceUtils/DistanceCalculator/AddressRouteManager.cs index ce2bdcf37..aa52f5ff3 100644 --- a/Service/ServiceUtils/DistanceCalculator/AddressRouteManager.cs +++ b/Service/ServiceUtils/DistanceCalculator/AddressRouteManager.cs @@ -12,196 +12,83 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator static AddressRouteManager() { - DistanceAPI = new GoogleDistanceMatrixApi(); + DistanceAPI = + new GoogleDistanceMatrixApi(); + //new OpenrouteServiceAPI(); } - public static AddressRoute[,] GetAddressRoute(Address[] origins, Address[] destinations) + public static AddressRouteMatrix GetMatrix(Address[] origins, Address[] destinations) { var routes = DAOFactory.GenericDAO.GetAll(); - var res = new AddressRoute[origins.Length, destinations.Length]; + var matrix = new AddressRouteMatrix(origins, destinations); - //var toRequest = new List>(); - var toRequest = new Dictionary, Tuple>(); + LoadExistingData(ref matrix, routes, out var needUpdate); - Address origin, destination; - for (int i = 0; i < origins.Length; i++) + var requests = needUpdate.Union(matrix.GetUnkownRoutes()).ToList(); + + // Alles aktuell + if (!requests.Any()) + return matrix; + + var ori2req = requests.Select(x => x.Origin).ToHashSet(); + var des2req = requests.Select(x => x.Destination).ToHashSet(); + + DistanceAPI.RequestMissingData(ref matrix, ori2req, des2req); + + foreach (var req in requests) { - origin = origins[i]; + var route = matrix.GetRoute(req.Origin, req.Destination); - for (int j = 0; j < destinations.Length; j++) + if (route is null) + throw new NotImplementedException("Unerwartetes Verhalten #4314513094871"); + + route.Origin_Version = route.Origin.Version.Value; + route.Destination_Version = route.Destination.Version.Value; + + if (!route.Version.HasValue) { - destination = destinations[j]; - - var search = routes.FirstOrDefault(route => route.Address1_Oid == origin.Oid && route.Address2_Oid == destination.Oid); - - if (search is AddressRoute) - { - if (search.Address1_Version == origin.Version && search.Address2_Version == destination.Version) - { - // Gefunden - res[i, j] = search; - } - else - { - toRequest.Add(new Tuple(i, j), new Tuple(origin, destination, false, search)); - // Braucht Update - } - } - else - { - toRequest.Add(new Tuple(i, j), new Tuple(origin, destination, true, null)); - // Neu laden - } + // Neu + DAOFactory.GenericDAO.Insert(req); + } + else + { + // Update + DAOFactory.GenericDAO.Update(req); } } - // Falls keine Update benötigt werden - if (!toRequest.Any()) - return res; - - var origins2req = toRequest.Select(keyvalue => keyvalue.Value.Item1).ToHashSet().ToArray(); - var destinations2req = toRequest.Select(keyvalue => keyvalue.Value.Item2).ToHashSet().ToArray(); - - var newRoutes = DistanceAPI.GetAddressRoutes(origins2req, destinations2req); - - for (int i = 0; i < origins.Length; i++) - { - origin = origins[i]; - - for (int j = 0; j < destinations.Length; j++) - { - destination = destinations[j]; - - // bereits gefunden - if (res[i, j] is AddressRoute) - continue; - - var req = toRequest[new Tuple(i, j)]; - - var newRoute = FindRoute(newRoutes, req.Item1, req.Item2); - - if (req.Item3) - { - // Neu - DAOFactory.GenericDAO.Insert(newRoute); - } - else - { - // Alt - var oldRoute = req.Item4; - - oldRoute.Address1_Version = newRoute.Address1_Version; - oldRoute.Address2_Version = newRoute.Address2_Version; - - oldRoute.Distance_in_meter = newRoute.Distance_in_meter; - oldRoute.Distance_in_meter_txt = newRoute.Distance_in_meter_txt; - oldRoute.Time_in_seconds = newRoute.Time_in_seconds; - oldRoute.Time_in_seconds_txt = newRoute.Time_in_seconds_txt; - - DAOFactory.GenericDAO.Update(oldRoute); - } - } - } - - return res; + return matrix; } - public static AddressRoute FindRoute(AddressRoute[,] routes, Address origin, Address destination) + public static void LoadExistingData(ref AddressRouteMatrix matrix, IList routes, out List needUpdate) { - for (int i = 0; i < routes.GetLength(0); i++) - { - for (int j = 0; j < routes.GetLength(1); j++) - { - var route = routes[i, j]; + needUpdate = new List(); - if (origin.Oid == route.Address1_Oid && destination.Oid == route.Address2_Oid) - return route; + foreach (var route in routes) + { + var origin = route.Origin; + var destination = route.Destination; + + if (!matrix.CanUseRoute(origin, destination)) + continue; + + if (origin.Version == route.Origin_Version && destination.Version == route.Destination_Version) + { + // Ist aktuell + matrix.SetRoute(origin, destination, route); + } + else + { + // Braucht Update + matrix.SetRoute(origin, destination, route); + needUpdate.Add(route); } } - - throw new NotImplementedException("#974289"); } - //public static AddressRoute GetAddressRoute(Address address1, Address address2) - //{ - // var routes = DAOFactory.GenericDAO.GetAll(); - - // foreach (var route in routes) - // { - // if (route.Address1_Oid == address1.Oid && route.Address2_Oid == address2.Oid) - // { - // if (route.Address1_Version == address1.Version && route.Address2_Version == address2.Version) - // { - // // Route korrekt vorhanden - // return route; - // } - // else - // { - // // Route vorhanden, aber Addresse hat update - // return UpdateAddressRoute(route, address1, address2); - // } - // } - // } - - // // Route nicht vorhanden - // return InsertNewAddressRoute(address1, address2); - //} - - //public static void DeleteAddressRoute(long oid) => DeleteAddressRoute(DAOFactory.GenericDAO.LoadByID(oid)); - - //public static void DeleteAddressRoute(AddressRoute route) - //{ - // if (route is object) - // DAOFactory.GenericDAO.Delete(route); - //} - - //public static AddressRoute InsertNewAddressRoute(Address address1, Address address2) - //{ - // var newRoute = new AddressRoute - // { - // Address1_Oid = address1.Oid.Value, - // Address2_Oid = address2.Oid.Value - // }; - - // RequestAddressRoute(ref newRoute, address1, address2); - - // DAOFactory.GenericDAO.Insert(newRoute); - - // return newRoute; - //} - - //public static AddressRoute UpdateAddressRoute(AddressRoute route, Address address1, Address address2) - //{ - // RequestAddressRoute(ref route, address1, address2); - - // DAOFactory.GenericDAO.Update(route); - - // return route; - //} - - //public static AddressRoute RequestAddressRoute(ref AddressRoute route, Address address1, Address address2) - //{ - // route.Address1_Version = address1.Version.Value; - // route.Address2_Version = address2.Version.Value; - - // // Calculate Distance - - // var api = new GoogleDistanceMatrixApi(address1, address2); - // var task = api.GetResponse(); - - // task.Wait(); - - // if (task.Exception is object) - // { - // throw task.Exception; - // } - - // route.Distance_in_meter = task.Result.Rows[0].Elements[0].Distance.Value; - // route.Distance_in_meter_txt = task.Result.Rows[0].Elements[0].Distance.Text; - // route.Time_in_seconds = task.Result.Rows[0].Elements[0].Duration.Value; - // route.Time_in_seconds_txt = task.Result.Rows[0].Elements[0].Duration.Text; - - // return route; - //} + public static string AddressToString(Address address) + { + return $"{address.Street}, {address.PostalCode}, {address.Town}, {address.State}, {address.Country}"; + } } } diff --git a/Service/ServiceUtils/DistanceCalculator/AddressRouteMatrix.cs b/Service/ServiceUtils/DistanceCalculator/AddressRouteMatrix.cs new file mode 100644 index 000000000..99c0c4628 --- /dev/null +++ b/Service/ServiceUtils/DistanceCalculator/AddressRouteMatrix.cs @@ -0,0 +1,84 @@ +using BeWo.Data.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BeWo.Service.ServiceUtils.DistanceCalculator +{ + public class AddressRouteMatrix + { + public int OriginsLength { get; set; } + public int DestinationsLength { get; set; } + + public Address[] OriginAddresses { get; set; } + public Address[] DestinationAddresses { get; set; } + public Dictionary Origins { get; set; } + public Dictionary Destinations { get; set; } + public AddressRoute[,] Routes { get; set; } + + public AddressRouteMatrix(Address[] originAddresses, Address[] destinationAddresses) + { + OriginAddresses = originAddresses; + Origins = new Dictionary(); + OriginsLength = originAddresses.Length; + + DestinationAddresses = destinationAddresses; + Destinations = new Dictionary(); + DestinationsLength = destinationAddresses.Length; + + int i; + for (i = 0; i < OriginsLength; i++) + { + Origins.Add(originAddresses[i], i); + } + + for (i = 0; i < DestinationsLength; i++) + { + Destinations.Add(destinationAddresses[i], i); + } + + Routes = new AddressRoute[OriginsLength, DestinationsLength]; + } + + public void SetRoute(Address origin, Address destination, AddressRoute value) => Routes[Origins[origin], Destinations[destination]] = value; + public AddressRoute GetRoute(Address origin, Address destination) + { + return Routes[Origins[origin], Destinations[destination]]; + } + + public IEnumerable GetUnkownRoutes() + { + for (int i = 0; i < OriginsLength; i++) + { + for (int j = 0; j < DestinationsLength; j++) + { + var route = Routes[i, j]; + + if (route is null) + { + route = new AddressRoute + { + Origin = OriginAddresses[i], + Destination = DestinationAddresses[j] + }; + + Routes[i, j] = route; + + yield return route; + } + } + } + } + + public bool CanUseRoute(Address origin, Address destination) + { + if (!Origins.ContainsKey(origin)) + return false; + if (!Destinations.ContainsKey(destination)) + return false; + return true; + } + } +} diff --git a/Service/ServiceUtils/DistanceCalculator/GoogleDistanceMatrixAPI.cs b/Service/ServiceUtils/DistanceCalculator/GoogleDistanceMatrixAPI.cs index 13e61c228..f5d493ac2 100644 --- a/Service/ServiceUtils/DistanceCalculator/GoogleDistanceMatrixAPI.cs +++ b/Service/ServiceUtils/DistanceCalculator/GoogleDistanceMatrixAPI.cs @@ -48,6 +48,8 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator private string Key { get; set; } private string Url { get; set; } + private AddressRouteMatrix Matrix { get; set; } + private Address[] OriginAddresses { get; set; } private Address[] DestinationAddresses { get; set; } @@ -68,12 +70,13 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator Key = appSettings["GoogleDistanceMatrixApiKey"]; } - public AddressRoute[,] GetAddressRoutes(Address[] originAddresses, Address[] destinationAddresses) + public void RequestMissingData(ref AddressRouteMatrix matrix, IEnumerable
origins, IEnumerable
destinations) { - OriginAddresses = originAddresses; - DestinationAddresses = destinationAddresses; + Matrix = matrix; + OriginAddresses = origins.ToArray(); + DestinationAddresses = destinations.ToArray(); - var task = GetResponse(); + var task = GetMatrix(); task.Wait(); @@ -81,11 +84,9 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator { throw task.Exception; } - - return task.Result; } - public async Task GetResponse() + public async Task GetMatrix() { using (var client = new HttpClient()) { @@ -100,33 +101,33 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator { var content = await response.Content.ReadAsStringAsync(); - return ToAddressRouteArray(JsonConvert.DeserializeObject(content)); + return FillMatrix(JsonConvert.DeserializeObject(content)); } } } - public AddressRoute[,] ToAddressRouteArray(Response response) + public AddressRouteMatrix FillMatrix(Response response) { - var res = new AddressRoute[OriginAddresses.Length, DestinationAddresses.Length]; - for (int i = 0; i < response.Rows.Length; i++) { var row = response.Rows[i]; + var origin = OriginAddresses[i]; for (int j = 0; j < row.Elements.Length; j++) { var element = row.Elements[j]; + var destination = DestinationAddresses[j]; - res[i, j] = ToAddressRoute(element); + var route = Matrix.GetRoute(origin, destination); - res[i, j].Address1_Oid = OriginAddresses[i].Oid.Value; - res[i, j].Address1_Version = OriginAddresses[i].Version.Value; - res[i, j].Address2_Oid = DestinationAddresses[j].Oid.Value; - res[i, j].Address2_Version = DestinationAddresses[j].Version.Value; + route.Distance_in_meter = element.Distance.Value; + route.Distance_in_meter_txt = element.Distance.Text; + route.Time_in_seconds = element.Duration.Value; + route.Time_in_seconds_txt = element.Duration.Text; } } - return res; + return Matrix; } public AddressRoute ToAddressRoute(Response.Element element) @@ -143,16 +144,11 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator private string GetRequestUrl() { - var originAddresses = OriginAddresses.Select(str => ToGoogle(str)).Select(HttpUtility.UrlEncode).ToArray(); + var originAddresses = OriginAddresses.Select(str => AddressRouteManager.AddressToString(str)).Select(HttpUtility.UrlEncode).ToArray(); var origins = string.Join("|", originAddresses); - var destinationAddresses = DestinationAddresses.Select(str => ToGoogle(str)).Select(HttpUtility.UrlEncode).ToArray(); + var destinationAddresses = DestinationAddresses.Select(str => AddressRouteManager.AddressToString(str)).Select(HttpUtility.UrlEncode).ToArray(); var destinations = string.Join("|", destinationAddresses); return $"{Url}?origins={origins}&destinations={destinations}&key={Key}"; } - - public static string ToGoogle(Address add) - { - return $"{add.Street}, {add.PostalCode}, {add.Town}, {add.State}, {add.Country}"; - } } } diff --git a/Service/ServiceUtils/DistanceCalculator/IDistanceAPI.cs b/Service/ServiceUtils/DistanceCalculator/IDistanceAPI.cs index 8d4723f50..9204b8712 100644 --- a/Service/ServiceUtils/DistanceCalculator/IDistanceAPI.cs +++ b/Service/ServiceUtils/DistanceCalculator/IDistanceAPI.cs @@ -10,6 +10,10 @@ namespace BeWo.Service.ServiceUtils.DistanceCalculator { public interface IDistanceAPI { - AddressRoute[,] GetAddressRoutes(Address[] originAddresses, Address[] destinationAddresses); + //AddressRoute[,] GetAddressRoutes(Address[] originAddresses, Address[] destinationAddresses); + + //AddressRouteMatrix GetAddressRouteMatrix(Address[] originAddresses, Address[] destinationAddresses); + + void RequestMissingData(ref AddressRouteMatrix matrix, IEnumerable
origins, IEnumerable
destinations); } } diff --git a/Service/ServiceUtils/DistanceCalculator/OpenrouteServiceAPI.cs b/Service/ServiceUtils/DistanceCalculator/OpenrouteServiceAPI.cs new file mode 100644 index 000000000..3c55f0025 --- /dev/null +++ b/Service/ServiceUtils/DistanceCalculator/OpenrouteServiceAPI.cs @@ -0,0 +1,259 @@ +using BeWo.Data.Access; +using BeWo.Data.Entities; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Task = System.Threading.Tasks.Task; + +namespace BeWo.Service.ServiceUtils.DistanceCalculator +{ + public class OpenrouteServiceAPI : IDistanceAPI + { + public class ResponseGeo + { + public Feature[] Features { get; set; } + + public class Feature + { + public string Type { get; set; } + public Geometry Geometry { get; set; } + } + + public class Geometry + { + public string Type { get; set; } + public decimal[] Coordinates { get; set; } + } + } + + public class ResponseMatrix + { + public decimal[,] Durations { get; set; } + public decimal[,] Distances { get; set; } + } + + private string Key { get; set; } + private string GeoKey { get; set; } + private string Url { get; set; } + + private AddressRouteMatrix Matrix { get; set; } + + private Address[] OriginAddresses { get; set; } + private Address[] DestinationAddresses { get; set; } + + public OpenrouteServiceAPI() + { + var appSettings = ConfigurationManager.AppSettings; + + if (string.IsNullOrEmpty(appSettings["OpenrouteServiceApiUrl"])) + { + throw new Exception("OpenrouteServiceApiUrl is not set in AppSettings."); + } + Url = appSettings["OpenrouteServiceApiUrl"]; + + if (string.IsNullOrEmpty(appSettings["OpenrouteServiceApiKey"])) + { + throw new Exception("OpenrouteServiceApiKey is not set in AppSettings."); + } + Key = appSettings["OpenrouteServiceApiKey"]; + + if (string.IsNullOrEmpty(appSettings["OpenrouteServiceApiUrlGeocode"])) + { + throw new Exception("OpenrouteServiceApiUrlGeocode is not set in AppSettings."); + } + GeoKey = appSettings["OpenrouteServiceApiUrlGeocode"]; + } + + public void RequestMissingData(ref AddressRouteMatrix matrix, IEnumerable
origins, IEnumerable
destinations) + { + Matrix = matrix; + OriginAddresses = origins.ToArray(); + DestinationAddresses = destinations.ToArray(); + + if (!(origins.Any() && destinations.Any())) + throw new NotImplementedException("Keine Addresse angefragt"); + + LoadCoordinates().Wait(); + + var task = GetMatrix(); + + task.Wait(); + + if (task.Exception is object) + { + throw task.Exception; + } + } + + public async Task LoadCoordinates() + { + // Update Koordinaten + var items = OriginAddresses.Union(DestinationAddresses).ToHashSet(); + foreach (var item in items) + { + if (item.L_Version is object && item.L_Version == item.Version) + continue; + + await LoadCoordinate(item); + + DAOFactory.GenericDAO.Update(item); + } + } + + public async Task LoadCoordinate(Address address) + { + using (var client = new HttpClient()) + { + var uri = new Uri(GetGeocodeUrl(address)); + + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json; charset=utf-8"); + client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8"); + + using (var response = await client.GetAsync(uri)) + { + string responseData = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + try + { + var data = JsonConvert.DeserializeObject(responseData); + + address.Longitude = data.Features[0].Geometry.Coordinates[0]; + address.Latitude = data.Features[0].Geometry.Coordinates[1]; + address.L_Version = address.Version.Value + 1; + + return; + } + catch (Exception e) + { + throw e; + } + + } + else + { + throw new Exception("OpenrouteServiceAPI failed with status code: " + response.StatusCode + " |||Data: " + responseData); + } + } + } + } + + public async Task GetMatrix() + { + using (var client = new HttpClient()) + { + var uri = new Uri(Url); + + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json; charset=utf-8"); + client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8"); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Key); + + using (var content = new StringContent(GetMatrixBodyContentString())) + //using (var content = new StringContent("{\"locations\":[[48.477473,9.70093],[49.153868,9.207916]]}")) + { + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + using (var response = await client.PostAsync(uri, content)) + { + string responseData = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + var data = JsonConvert.DeserializeObject(responseData); + + return FillMatrix(data); + } + else + { + throw new Exception("OpenrouteServiceAPI failed with status code: " + response.StatusCode + " |||Data: " + responseData); + } + } + } + } + } + + public AddressRouteMatrix FillMatrix(ResponseMatrix response) + { + for (int i = 0; i < response.Durations.GetLength(0); i++) + { + for (int j = 0; j < response.Durations.GetLength(1); j++) + { + var distance = response.Distances[i, j]; + var duration = response.Durations[i, j]; + + var origin = OriginAddresses[i]; + var destination = DestinationAddresses[j]; + + var route = Matrix.GetRoute(origin, destination); + + route.Distance_in_meter = (int) (distance * 1000); + route.Time_in_seconds = (int) duration; + } + } + + return Matrix; + } + + private string GetGeocodeUrl(Address address) + { + var html = HttpUtility.UrlEncode(AddressRouteManager.AddressToString(address)); + return $"{GeoKey}?api_key={Key}&text={html}&sources=openstreetmap&size=1"; + } + + private string GetMatrixBodyContentString() + { + var sb = new StringBuilder(); + + var origins = OriginAddresses.Select(GetCoordinates); + var olen = OriginAddresses.Count(); + var destination = DestinationAddresses.Select(GetCoordinates); + var dlen = DestinationAddresses.Count(); + + sb.Append("{\"locations\":"); + sb.Append("["); + sb.Append(string.Join(",", origins.Union(destination))); + sb.Append("]"); + + sb.Append(",\"metrics\":[\"distance\",\"duration\"]"); + sb.Append(",\"units\":\"km\""); + + sb.Append(",\"sources\":"); + sb.Append(GetList(0, olen - 1)); + + sb.Append(",\"destinations\":"); + sb.Append(GetList(olen, olen + dlen - 1)); + + sb.Append("}"); + + return sb.ToString(); + } + + private string GetCoordinates(Address add) => $"[{add.Longitude.Value.ToString(new CultureInfo("en-US"))},{add.Latitude.Value.ToString(new CultureInfo("en-US"))}]"; + + private string GetList(int x, int y) + { + if (y < x) + throw new ArgumentException($"y({y}) darf nicht kleiner als x({x}) sein"); + + var diff = y - x; + var list = new int[diff + 1]; + + for (int i = 0; i <= diff; i++) + { + list[i] = x + i; + } + + return "[" + string.Join(",", list) + "]"; + } + } +} diff --git a/Shared/DataContracts/AddressRouteDC.cs b/Shared/DataContracts/AddressRouteDC.cs index aeb129359..8b46ced92 100644 --- a/Shared/DataContracts/AddressRouteDC.cs +++ b/Shared/DataContracts/AddressRouteDC.cs @@ -34,16 +34,16 @@ namespace BS.Shared.DataContracts public SystemEntryID? SystemEntryID { get; set; } [DataMember] - public long Address1_Oid { get; set; } + public long Origin_Address_Oid { get; set; } [DataMember] - public long Address1_Version { get; set; } + public long Origin_Address_Version { get; set; } [DataMember] - public long Address2_Oid { get; set; } + public long Destination_Address_Oid { get; set; } [DataMember] - public long Address2_Version { get; set; } + public long Destination_Address_Version { get; set; } [DataMember] public int Distance_in_meter { get; set; }