From 785f9adf1ca5a335ba72e10e54c33014fd756521 Mon Sep 17 00:00:00 2001 From: Dmytro M Date: Wed, 24 Aug 2022 20:23:32 +0300 Subject: [PATCH] add documentation --- .../Controllers/V1/GeocodingController.cs | 18 +- .../Models/Geocoding/GeocodingRequest.cs | 2 - .../Services/GeocodingService.cs | 162 ++++++++++-------- .../Services/IGeocodingService.cs | 17 +- 4 files changed, 122 insertions(+), 77 deletions(-) diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/GeocodingController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/GeocodingController.cs index 5065034ac4..ad1079b84e 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/GeocodingController.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/GeocodingController.cs @@ -1,3 +1,5 @@ +#nullable enable + using Microsoft.AspNetCore.Mvc; using OutOfSchool.Common.Models; using OutOfSchool.WebApi.Models.Geocoding; @@ -11,22 +13,28 @@ public class GeocodingController : Controller { private readonly IGeocodingService geocodingService; - public GeocodingController(IGeocodingService geocodingService) + public GeocodingController(IGeocodingService? geocodingService) { this.geocodingService = geocodingService ?? throw new ArgumentNullException(nameof(geocodingService)); } + /// + /// Get geocoding or reverse geocoding information. + /// + /// Coordinates query. + /// The geocoding information about address or coordinates. [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [HttpPost] public async Task Geocoding(GeocodingRequest request) { var result = await Validate(request) - .FlatMapAsync(async r => + .FlatMapAsync(r => r.IsReverse - ? await geocodingService.GetReverseGeocodingInfo(r) - : await geocodingService.GetGeocodingInfo(r)); + ? geocodingService.GetReverseGeocodingInfo(r) + : geocodingService.GetGeocodingInfo(r)); return result.Match( error => error.HttpStatusCode switch @@ -34,7 +42,7 @@ public async Task Geocoding(GeocodingRequest request) HttpStatusCode.BadRequest => BadRequest(error.Message), _ => StatusCode((int)error.HttpStatusCode), }, - r => r is null ? NoContent() : Ok(r)); + r => r is not null ? Ok(r) : NoContent()); } private Either Validate(GeocodingRequest request) diff --git a/OutOfSchool/OutOfSchool.WebApi/Models/Geocoding/GeocodingRequest.cs b/OutOfSchool/OutOfSchool.WebApi/Models/Geocoding/GeocodingRequest.cs index d6242ffa1d..5819be9f9a 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Models/Geocoding/GeocodingRequest.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Models/Geocoding/GeocodingRequest.cs @@ -1,5 +1,3 @@ -using Newtonsoft.Json; - namespace OutOfSchool.WebApi.Models.Geocoding; public class GeocodingRequest diff --git a/OutOfSchool/OutOfSchool.WebApi/Services/GeocodingService.cs b/OutOfSchool/OutOfSchool.WebApi/Services/GeocodingService.cs index a1ba6d5e5c..110c948d1f 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Services/GeocodingService.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Services/GeocodingService.cs @@ -1,10 +1,17 @@ +#nullable enable + using AutoMapper; using Microsoft.Extensions.Options; +using OutOfSchool.Common.Enums; using OutOfSchool.Common.Models; +using OutOfSchool.WebApi.Models.Codeficator; using OutOfSchool.WebApi.Models.Geocoding; namespace OutOfSchool.WebApi.Services; +/// +/// Implements the interface with geocoding functionality. +/// public class GeocodingService : CommunicationService, IGeocodingService { private const double SearchBoundsRadiusMeters = 50000.0; @@ -13,88 +20,99 @@ public class GeocodingService : CommunicationService, IGeocodingService private readonly IMapper mapper; public GeocodingService( - IOptions options, + IOptions? options, IHttpClientFactory httpClientFactory, IOptions communicationConfig, - ICodeficatorService codeficatorService, - IMapper mapper, + ICodeficatorService? codeficatorService, + IMapper? mapper, ILogger logger) : base(httpClientFactory, communicationConfig.Value, logger) { - config = options.Value; - this.mapper = mapper; - this.codeficatorService = codeficatorService; + config = options?.Value ?? throw new ArgumentNullException(nameof(options)); + this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + this.codeficatorService = codeficatorService ?? throw new ArgumentNullException(nameof(codeficatorService)); } + /// public async Task> GetGeocodingInfo(GeocodingRequest request) { + AllAddressPartsDto? address; try { // Get codeficator from DB - var address = await codeficatorService.GetAllAddressPartsById(request.CATOTTGId); - - if (address is null) + address = await codeficatorService.GetAllAddressPartsById(request.CATOTTGId); + } + catch (Exception e) + { + Logger.LogError(e, "Unable to retrieve codeficator"); + return new ErrorResponse { - return new ErrorResponse - { - HttpStatusCode = HttpStatusCode.BadRequest, - Message = "No city data available", - }; - } - - // If codeficator entry is a city's district - get it's parent (city) coordinates - // TODO: check if districts have their own coordinates - var lat = address.AddressParts.Category != "B" ? address.AddressParts.Latitude : address.AddressParts.Parent.Latitude; - var lon = address.AddressParts.Category != "B" ? address.AddressParts.Longitude : address.AddressParts.Parent.Longitude; - - // Create bounds to limit the search are to certain coordinates square - // should help minimize response data for cities & streets with equal name - // TODO: 50km is 2x time more then enough for Kyiv - var bounds = new RectangularBounds(lat, lon, SearchBoundsRadiusMeters); - - var req = new Request + Message = "Unable to retrieve codeficator data", + }; + } + + if (address is null) + { + return new ErrorResponse { - HttpMethodType = HttpMethodType.Get, - Url = new Uri(config.BaseUrl), - Query = new Dictionary - { - {"key", config.ApiKey}, - {"categories", "adr_address"}, - {"text", $"{address.AddressParts.Name}, {request.Street}, {request.BuildingNumber}"}, - { "contains", bounds.WKT }, - }, + HttpStatusCode = HttpStatusCode.BadRequest, + Message = "No city data available", }; + } - var response = await SendRequest(req); + // If codeficator entry is a city's district - get it's parent (city) coordinates + var lat = address.AddressParts.Category != CodeficatorCategory.CityDistrict.Name + ? address.AddressParts.Latitude + : address.AddressParts.Parent.Latitude; + var lon = address.AddressParts.Category != CodeficatorCategory.CityDistrict.Name + ? address.AddressParts.Longitude + : address.AddressParts.Parent.Longitude; - return response.Map(r => + // Create bounds to limit the search are to certain coordinates square + // should help minimize response data for cities & streets with equal name + // 50km is 2x time more then enough for Kyiv + var bounds = new RectangularBounds(lat, lon, SearchBoundsRadiusMeters); + + var req = new Request + { + HttpMethodType = HttpMethodType.Get, + Url = new Uri(config.BaseUrl), + Query = new Dictionary { - var result = new GeocodingResponse - { - CATOTTGId = request.CATOTTGId, - Settlement = address.AddressParts.Name, - }; + { "key", config.ApiKey }, + { "categories", "adr_address" }, + { "text", $"{address.AddressParts.Name}, {request.Street}, {request.BuildingNumber}" }, + { "contains", bounds.WKT }, + }, + }; - return r switch - { - GeocodingSingleFeatureResponse single => mapper.Map(single, result), + var response = await SendRequest(req); - // TODO: for now take the most relevant (sorted by API) address, might need to change if frontend has issues - GeocodingListFeatureResponse multi => multi.Features.Any() ? mapper.Map(multi.Features.First(), result) : null, - GeocodingEmptyResponse => null, - _ => null - }; - }); - } - catch (Exception e) + return response.Map(r => { - Logger.LogError(e, e.Message); - // TODO: normal error - return new ErrorResponse(); - } + var result = new GeocodingResponse + { + CATOTTGId = request.CATOTTGId, + Settlement = address.AddressParts.Name, + }; + + return r switch + { + GeocodingSingleFeatureResponse single => mapper.Map(single, result), + + // for now take the most relevant (sorted by API) address, + // might need to change if frontend has issues + GeocodingListFeatureResponse multi => multi.Features.Any() + ? mapper.Map(multi.Features.First(), result) + : null, + GeocodingEmptyResponse => null, + _ => null + }; + }); } - public async Task> GetReverseGeocodingInfo( + /// + public async Task> GetReverseGeocodingInfo( GeocodingRequest request) { var req = new Request @@ -103,11 +121,11 @@ public async Task> GetReverseGeocodingI Url = new Uri(config.BaseUrl), Query = new Dictionary { - {"key", config.ApiKey}, - {"categories", "adr_address"}, - {"radius", $"{config.Radius}"}, - {"near", $"{request.Lon},{request.Lat}"}, // it has to be lng first, lat second as per api - {"order", "distance"}, + { "key", config.ApiKey }, + { "categories", "adr_address" }, + { "radius", $"{config.Radius}" }, + { "near", $"{request.Lon},{request.Lat}" }, // it has to be lng first, lat second as per api + { "order", "distance" }, }, }; var response = await SendRequest(req).ConfigureAwait(false); @@ -115,11 +133,12 @@ public async Task> GetReverseGeocodingI .Map(r => r switch { GeocodingSingleFeatureResponse single => mapper.Map(single), - GeocodingListFeatureResponse multi => multi.Features.Select(mapper.Map).FirstOrDefault(), + GeocodingListFeatureResponse multi => multi.Features.Select(mapper.Map) + .FirstOrDefault(), GeocodingEmptyResponse => null, _ => null }) - .FlatMapAsync(async r => + .FlatMapAsync(async r => { if (r is null) { @@ -147,14 +166,19 @@ public async Task> GetReverseGeocodingI } catch (Exception e) { - Logger.LogError(e, e.Message); - // TODO: normal error - return new ErrorResponse(); + Logger.LogError(e, "Unable to retrieve codeficator"); + return new ErrorResponse + { + Message = "Unable to retrieve codeficator data", + }; } }); } } +/// +/// Container for well known text representation of square bound around a point. +/// public class RectangularBounds { private const double EarthRadius = 6378.137; diff --git a/OutOfSchool/OutOfSchool.WebApi/Services/IGeocodingService.cs b/OutOfSchool/OutOfSchool.WebApi/Services/IGeocodingService.cs index 3ff7378741..10b43ac95d 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Services/IGeocodingService.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Services/IGeocodingService.cs @@ -1,12 +1,27 @@ +#nullable enable + using OutOfSchool.Common.Models; using OutOfSchool.WebApi.Models.Geocoding; namespace OutOfSchool.WebApi.Services; +/// +/// Defines interface for geocoding functionality. +/// public interface IGeocodingService { + /// + /// Get coordinates by address. + /// + /// Geocoding request. + /// The task result contains the . public Task> GetGeocodingInfo(GeocodingRequest request); - public Task> GetReverseGeocodingInfo( + /// + /// Get address by coordinates. + /// + /// Geocoding request. + /// The task result contains the . + public Task> GetReverseGeocodingInfo( GeocodingRequest request); } \ No newline at end of file