From 26838d2556a858221e16769dd62d0e4d5d4eaf3f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 16 Jun 2026 18:23:08 -0400 Subject: [PATCH 1/3] feat(geocoding): add missing api parameters for bing, google, mapbox, and radar providers --- .../Models/Parameters/BaseParameters.cs | 17 ++ .../Models/Parameters/ResultParameters.cs | 6 + .../Parameters/ReverseGeocodingParameters.cs | 6 + src/Geo.Bing/Services/BingGeocoding.cs | 25 +++ .../Models/Parameters/DetailsParameters.cs | 12 ++ .../Models/Parameters/GeocodingParameters.cs | 7 + .../PlacesAutocompleteParameters.cs | 6 + .../Parameters/ReverseGeocodingParameters.cs | 7 + src/Geo.Google/Services/GoogleGeocoding.cs | 50 ++++++ .../Abstractions/IMapBoxGeocoding.cs | 18 ++ .../Parameters/GeocodingV6Parameters.cs | 86 ++++++++++ .../ReverseGeocodingV6Parameters.cs | 61 +++++++ src/Geo.MapBox/Services/MapBoxGeocoding.cs | 157 ++++++++++++++++++ .../Models/Parameters/GeocodingParameters.cs | 6 + src/Geo.Radar/Services/RadarGeocoding.cs | 17 +- 15 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs create mode 100644 src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs diff --git a/src/Geo.Bing/Models/Parameters/BaseParameters.cs b/src/Geo.Bing/Models/Parameters/BaseParameters.cs index 769d124..7dc577b 100644 --- a/src/Geo.Bing/Models/Parameters/BaseParameters.cs +++ b/src/Geo.Bing/Models/Parameters/BaseParameters.cs @@ -36,6 +36,23 @@ public class BaseParameters : IAdditionalParameters /// public CultureInfo Culture { get; set; } + /// + /// Gets or sets the user's current location to help determine better results. + /// Format: latitude,longitude (e.g., "47.608,-122.337"). Optional. + /// + public string UserLocation { get; set; } + + /// + /// Gets or sets the IP address of the user's device to help determine better results. Optional. + /// + public string UserIp { get; set; } + + /// + /// Gets or sets the map area currently shown to the user to help determine better results. + /// Format: southLatitude,westLongitude,northLatitude,eastLongitude. Optional. + /// + public string UserMapView { get; set; } + /// public IDictionary AdditionalParameters { get; } = new Dictionary(); } diff --git a/src/Geo.Bing/Models/Parameters/ResultParameters.cs b/src/Geo.Bing/Models/Parameters/ResultParameters.cs index 61c73c0..7895dd1 100644 --- a/src/Geo.Bing/Models/Parameters/ResultParameters.cs +++ b/src/Geo.Bing/Models/Parameters/ResultParameters.cs @@ -16,5 +16,11 @@ public class ResultParameters : BaseParameters /// If the value is 0, the default value will be used. /// public int MaximumResults { get; set; } = 5; + + /// + /// Gets or sets a value indicating whether to restrict results to the country/region and adminDistrict specified. + /// When true, results outside the specified countryRegion and adminDistrict will not be returned. Optional. + /// + public bool StrictMatch { get; set; } = false; } } diff --git a/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs b/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs index 2b28be7..f11870d 100644 --- a/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs +++ b/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs @@ -50,6 +50,12 @@ public class ReverseGeocodingParameters : BaseParameters, IKeyParameters /// public bool IncludeCountryRegion { get; set; } + /// + /// Gets or sets a value indicating whether AdminDistrict names are returned in expanded form. + /// When true, returns "Washington" instead of "WA". Defaults to false. Optional. + /// + public bool VerbosePlaceNames { get; set; } = false; + /// public string Key { get; set; } } diff --git a/src/Geo.Bing/Services/BingGeocoding.cs b/src/Geo.Bing/Services/BingGeocoding.cs index 8e67ef8..22cadfb 100644 --- a/src/Geo.Bing/Services/BingGeocoding.cs +++ b/src/Geo.Bing/Services/BingGeocoding.cs @@ -195,6 +195,11 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) _logger.BingDebug(Resources.Services.BingGeocoding.Do_Not_Include_Entity_Types); } + if (parameters.VerbosePlaceNames) + { + query = query.Add("verboseplacenames", "true"); + } + BuildBaseQuery(parameters, ref query); AddBingKey(parameters, ref query); @@ -297,6 +302,11 @@ internal void BuildLimitedResultQuery(ResultParameters parameters, ref QueryStri _logger.BingWarning(Resources.Services.BingGeocoding.Invalid_Maximum_Results); } + if (parameters.StrictMatch) + { + query = query.Add("strictMatch", "1"); + } + BuildBaseQuery(parameters, ref query); } @@ -344,6 +354,21 @@ internal void BuildBaseQuery(BaseParameters parameters, ref QueryString query) { _logger.BingDebug(Resources.Services.BingGeocoding.Invalid_Culture); } + + if (!string.IsNullOrWhiteSpace(parameters.UserLocation)) + { + query = query.Add("userLocation", parameters.UserLocation); + } + + if (!string.IsNullOrWhiteSpace(parameters.UserIp)) + { + query = query.Add("userIp", parameters.UserIp); + } + + if (!string.IsNullOrWhiteSpace(parameters.UserMapView)) + { + query = query.Add("usermapView", parameters.UserMapView); + } } /// diff --git a/src/Geo.Google/Models/Parameters/DetailsParameters.cs b/src/Geo.Google/Models/Parameters/DetailsParameters.cs index c66f796..dad27ca 100644 --- a/src/Geo.Google/Models/Parameters/DetailsParameters.cs +++ b/src/Geo.Google/Models/Parameters/DetailsParameters.cs @@ -37,6 +37,18 @@ public class DetailsParameters : BaseParameters, IKeyParameters /// public string SessionToken { get; set; } + /// + /// Gets or sets a value indicating whether to disable translation of reviews. + /// When true, the language code is included in the response along with the original-language review text. Optional. + /// + public bool? ReviewsNoTranslations { get; set; } + + /// + /// Gets or sets the sort order for reviews. + /// Accepted values: most_relevant, newest. Optional. + /// + public string ReviewsSort { get; set; } + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Models/Parameters/GeocodingParameters.cs b/src/Geo.Google/Models/Parameters/GeocodingParameters.cs index 0b32852..031a633 100644 --- a/src/Geo.Google/Models/Parameters/GeocodingParameters.cs +++ b/src/Geo.Google/Models/Parameters/GeocodingParameters.cs @@ -5,6 +5,7 @@ namespace Geo.Google.Models.Parameters { + using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; @@ -36,6 +37,12 @@ public class GeocodingParameters : BaseParameters, IKeyParameters /// public RegionInfo Region { get; set; } + /// + /// Gets the list of extra computations to apply to the geocoding request. + /// Supported values: ADDRESS_DESCRIPTORS, BUILDING_AND_ENTRANCES. Optional. + /// + public IList ExtraComputations { get; } = new List(); + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs b/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs index 7173726..637201e 100644 --- a/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs +++ b/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs @@ -41,5 +41,11 @@ public class PlacesAutocompleteParameters : QueryAutocompleteParameters /// This is a restriction, rather than a bias, meaning that results outside this region will not be returned even if they match the user input. /// public bool StrictBounds { get; set; } = false; + + /// + /// Gets or sets a hard restriction on the area in which results are returned. + /// Results outside this region will not be returned. Accepts a or value. Optional. + /// + public object LocationRestriction { get; set; } } } diff --git a/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs b/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs index 444f396..f501d46 100644 --- a/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs +++ b/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs @@ -7,6 +7,7 @@ namespace Geo.Google.Models.Parameters { using System.Collections.Generic; using System.ComponentModel.DataAnnotations; + using System.Globalization; using Geo.Google.Enums; /// @@ -39,6 +40,12 @@ public class ReverseGeocodingParameters : BaseParameters, IKeyParameters /// public IEnumerable LocationTypes { get; set; } + /// + /// Gets the list of extra computations to apply to the reverse geocoding request. + /// Supported values: ADDRESS_DESCRIPTORS, BUILDING_AND_ENTRANCES. Optional. + /// + public IList ExtraComputations { get; } = new List(); + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Services/GoogleGeocoding.cs b/src/Geo.Google/Services/GoogleGeocoding.cs index 159d050..3cbdfa5 100644 --- a/src/Geo.Google/Services/GoogleGeocoding.cs +++ b/src/Geo.Google/Services/GoogleGeocoding.cs @@ -232,6 +232,17 @@ internal Uri BuildGeocodingRequest(GeocodingParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Region); } + if (parameters.ExtraComputations != null) + { + foreach (var computation in parameters.ExtraComputations) + { + if (!string.IsNullOrWhiteSpace(computation)) + { + query = query.Add("extra_computations", computation); + } + } + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -301,6 +312,17 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Location_Types); } + if (parameters.ExtraComputations != null) + { + foreach (var computation in parameters.ExtraComputations) + { + if (!string.IsNullOrWhiteSpace(computation)) + { + query = query.Add("extra_computations", computation); + } + } + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -533,6 +555,18 @@ internal Uri BuildDetailsRequest(DetailsParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Fields); } + if (parameters.ReviewsNoTranslations.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("reviews_no_translations", parameters.ReviewsNoTranslations.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (!string.IsNullOrWhiteSpace(parameters.ReviewsSort)) + { + query = query.Add("reviews_sort", parameters.ReviewsSort); + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -600,6 +634,22 @@ internal Uri BuildPlaceAutocompleteRequest(PlacesAutocompleteParameters paramete query = query.Add("strictbounds", parameters.StrictBounds.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); #pragma warning restore CA1308 // Normalize strings to uppercase + if (parameters.LocationRestriction != null) + { + switch (parameters.LocationRestriction) + { + case Circle circle: + query = query.Add("locationrestriction", $"circle:{circle}"); + break; + case Boundaries boundary: + query = query.Add("locationrestriction", $"rectangle:{boundary}"); + break; + default: + _logger.GoogleWarning(Resources.Services.GoogleGeocoding.Invalid_Location_Bias_Type); + break; + } + } + AddAutocompleteParameters(parameters, ref query); AddGoogleKey(parameters, ref query); diff --git a/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs b/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs index aa70853..181d209 100644 --- a/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs +++ b/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs @@ -35,5 +35,23 @@ public interface IMapBoxGeocoding /// A with the response from MapBox. /// Thrown for multiple different reasons. Check the inner exception for more information. Task> ReverseGeocodingAsync(ReverseGeocodingParameters parameters, CancellationToken cancellationToken = default); + + /// + /// Calls the MapBox Geocoding API v6 (forward) and returns the results. + /// + /// A with the parameters of the request. + /// A used to cancel the request. + /// A with a of with the response from MapBox. + /// Thrown for multiple different reasons. Check the inner exception for more information. + Task>> GeocodingV6Async(GeocodingV6Parameters parameters, CancellationToken cancellationToken = default); + + /// + /// Calls the MapBox Geocoding API v6 (reverse) and returns the results. + /// + /// A with the parameters of the request. + /// A used to cancel the request. + /// A with the response from MapBox. + /// Thrown for multiple different reasons. Check the inner exception for more information. + Task> ReverseGeocodingV6Async(ReverseGeocodingV6Parameters parameters, CancellationToken cancellationToken = default); } } diff --git a/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs b/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs new file mode 100644 index 0000000..bdbbfde --- /dev/null +++ b/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) Geo.NET. +// Licensed under the MIT license. See the LICENSE file in the solution root for full license information. +// + +namespace Geo.MapBox.Models.Parameters +{ + using System.Collections.Generic; + using System.Globalization; + using Geo.MapBox.Enums; + using Geo.MapBox.Models; + + /// + /// The parameters possible to use during a Mapbox Geocoding API v6 forward geocoding request. + /// + public class GeocodingV6Parameters : IKeyParameters, IAdditionalParameters + { + /// + /// Gets or sets the search text to geocode. + /// + public string Query { get; set; } + + /// + /// Gets or sets a value indicating whether to store results permanently. + /// Must be true when using the Permanent endpoint (requires an enterprise plan). Default is false. Optional. + /// + public bool Permanent { get; set; } = false; + + /// + /// Gets or sets a value indicating whether to enable autocomplete suggestions. Optional. + /// + public bool? Autocomplete { get; set; } + + /// + /// Gets or sets a bounding box to limit results. Optional. + /// + public BoundingBox BoundingBox { get; set; } + + /// + /// Gets the list of countries to limit the request to. Optional. + /// + public IList Countries { get; } = new List(); + + /// + /// Gets or sets the response format. Accepted values: geojson, v5. Defaults to geojson. Optional. + /// + public string Format { get; set; } + + /// + /// Gets the list of languages of the text supplied in responses. Optional. + /// + public IList Languages { get; } = new List(); + + /// + /// Gets or sets the maximum number of results to return. Default is 5. Optional. + /// + public uint? Limit { get; set; } + + /// + /// Gets or sets a location to bias results toward. Format: longitude,latitude or "ip". Optional. + /// + public string Proximity { get; set; } + + /// + /// Gets a list used to filter results to include only a subset of available feature types. Optional. + /// + public IList Types { get; } = new List(); + + /// + /// Gets or sets the worldview to use. + /// Available values: ar, cn, in, jp, ma, rs, ru, tr, us. Defaults to us. Optional. + /// + public string Worldview { get; set; } + + /// + /// Gets or sets a value indicating whether to include building entrance data (beta feature). Optional. + /// + public bool? Entrances { get; set; } + + /// + public string Key { get; set; } + + /// + public IDictionary AdditionalParameters { get; } = new Dictionary(); + } +} diff --git a/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs b/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs new file mode 100644 index 0000000..448c274 --- /dev/null +++ b/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Geo.NET. +// Licensed under the MIT license. See the LICENSE file in the solution root for full license information. +// + +namespace Geo.MapBox.Models.Parameters +{ + using System.Collections.Generic; + using System.Globalization; + using Geo.MapBox.Enums; + using Geo.MapBox.Models; + + /// + /// The parameters possible to use during a Mapbox Geocoding API v6 reverse geocoding request. + /// + public class ReverseGeocodingV6Parameters : IKeyParameters, IAdditionalParameters + { + /// + /// Gets or sets the coordinate to reverse geocode. + /// + public Coordinate Coordinate { get; set; } + + /// + /// Gets or sets a value indicating whether to store results permanently. + /// Must be true when using the Permanent endpoint (requires an enterprise plan). Default is false. Optional. + /// + public bool Permanent { get; set; } = false; + + /// + /// Gets the list of countries to limit the request to. Optional. + /// + public IList Countries { get; } = new List(); + + /// + /// Gets the list of languages of the text supplied in responses. Optional. + /// + public IList Languages { get; } = new List(); + + /// + /// Gets or sets the maximum number of results to return. Default is 1. Optional. + /// + public uint? Limit { get; set; } + + /// + /// Gets a list used to filter results to include only a subset of available feature types. Optional. + /// + public IList Types { get; } = new List(); + + /// + /// Gets or sets the worldview to use. + /// Available values: ar, cn, in, jp, ma, rs, ru, tr, us. Defaults to us. Optional. + /// + public string Worldview { get; set; } + + /// + public string Key { get; set; } + + /// + public IDictionary AdditionalParameters { get; } = new Dictionary(); + } +} diff --git a/src/Geo.MapBox/Services/MapBoxGeocoding.cs b/src/Geo.MapBox/Services/MapBoxGeocoding.cs index 1e356b9..d2e5912 100644 --- a/src/Geo.MapBox/Services/MapBoxGeocoding.cs +++ b/src/Geo.MapBox/Services/MapBoxGeocoding.cs @@ -33,6 +33,8 @@ public class MapBoxGeocoding : GeoClient, IMapBoxGeocoding private const string ReverseGeocodeUri = "https://api.mapbox.com/geocoding/v5/{0}/{1}.json"; private const string PlacesEndpoint = "mapbox.places"; private const string PermanentEndpoint = "mapbox.places-permanent"; + private const string GeocodeV6Uri = "https://api.mapbox.com/search/geocode/v6/forward"; + private const string ReverseGeocodeV6Uri = "https://api.mapbox.com/search/geocode/v6/reverse"; private readonly IOptions> _options; private readonly ILogger _logger; @@ -76,6 +78,26 @@ public async Task> ReverseGeocodingAsync( return await GetAsync>(uri, cancellationToken).ConfigureAwait(false); } + /// + public async Task>> GeocodingV6Async( + GeocodingV6Parameters parameters, + CancellationToken cancellationToken = default) + { + var uri = ValidateAndBuildUri(parameters, BuildGeocodingV6Request); + + return await GetAsync>>(uri, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task> ReverseGeocodingV6Async( + ReverseGeocodingV6Parameters parameters, + CancellationToken cancellationToken = default) + { + var uri = ValidateAndBuildUri(parameters, BuildReverseGeocodingV6Request); + + return await GetAsync>(uri, cancellationToken).ConfigureAwait(false); + } + /// /// Validates the uri and builds it based on the parameter type. /// @@ -187,6 +209,141 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) return uriBuilder.Uri; } + /// + /// Builds the Geocoding API v6 forward geocoding uri based on the passed parameters. + /// + /// A with the v6 geocoding parameters to build the uri with. + /// A with the completed MapBox v6 geocoding uri. + /// Thrown when the 'Query' parameter is null or invalid. + internal Uri BuildGeocodingV6Request(GeocodingV6Parameters parameters) + { + if (string.IsNullOrWhiteSpace(parameters.Query)) + { + _logger.MapBoxError(Resources.Services.MapBoxGeocoding.Invalid_Query); + throw new ArgumentException(Resources.Services.MapBoxGeocoding.Invalid_Query, nameof(parameters.Query)); + } + + var uriBuilder = new UriBuilder(GeocodeV6Uri); + var query = QueryString.Empty; + + query = query.Add("q", parameters.Query); + +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("permanent", parameters.Permanent.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + + if (parameters.Autocomplete.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("autocomplete", parameters.Autocomplete.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (parameters.BoundingBox != null) + { + query = query.Add("bbox", parameters.BoundingBox.ToString()); + } + + if (!string.IsNullOrWhiteSpace(parameters.Format)) + { + query = query.Add("format", parameters.Format); + } + + if (!string.IsNullOrWhiteSpace(parameters.Proximity)) + { + query = query.Add("proximity", parameters.Proximity); + } + + if (parameters.Entrances.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("entrances", parameters.Entrances.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + AddBaseV6Parameters(parameters.Countries, parameters.Languages, parameters.Limit, parameters.Types, parameters.Worldview, ref query); + + AddMapBoxKey(parameters, ref query); + query = query.AddAdditionalParameters(parameters); + + uriBuilder.AddQuery(query); + + return uriBuilder.Uri; + } + + /// + /// Builds the Geocoding API v6 reverse geocoding uri based on the passed parameters. + /// + /// A with the v6 reverse geocoding parameters to build the uri with. + /// A with the completed MapBox v6 reverse geocoding uri. + /// Thrown when the 'Coordinate' parameter is null or invalid. + internal Uri BuildReverseGeocodingV6Request(ReverseGeocodingV6Parameters parameters) + { + if (parameters.Coordinate is null) + { + _logger.MapBoxError(Resources.Services.MapBoxGeocoding.Invalid_Coordinate); + throw new ArgumentException(Resources.Services.MapBoxGeocoding.Invalid_Coordinate, nameof(parameters.Coordinate)); + } + + var uriBuilder = new UriBuilder(ReverseGeocodeV6Uri); + var query = QueryString.Empty; + + query = query.Add("longitude", parameters.Coordinate.Longitude.ToString(CultureInfo.InvariantCulture)); + query = query.Add("latitude", parameters.Coordinate.Latitude.ToString(CultureInfo.InvariantCulture)); + +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("permanent", parameters.Permanent.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + + AddBaseV6Parameters(parameters.Countries, parameters.Languages, parameters.Limit, parameters.Types, parameters.Worldview, ref query); + + AddMapBoxKey(parameters, ref query); + query = query.AddAdditionalParameters(parameters); + + uriBuilder.AddQuery(query); + + return uriBuilder.Uri; + } + + /// + /// Adds shared v6 query parameters (countries, languages, limit, types, worldview). + /// + internal void AddBaseV6Parameters( + IList countries, + IList languages, + uint? limit, + IList types, + string worldview, + ref QueryString query) + { + if (countries != null && countries.Count > 0) + { + query = query.Add("country", string.Join(",", countries.Select(x => x.TwoLetterISORegionName))); + } + + if (languages != null && languages.Count > 0) + { + query = query.Add("language", string.Join(",", languages.Select(x => x.Name))); + } + + if (limit.HasValue && limit.Value > 0) + { + query = query.Add("limit", limit.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (types != null && types.Count > 0) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("types", string.Join(",", types.Select(x => x.ToString().ToLowerInvariant()))); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (!string.IsNullOrWhiteSpace(worldview)) + { + query = query.Add("worldview", worldview); + } + } + /// /// Adds the base query parameters based on the allowed logic. /// diff --git a/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs b/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs index 198bb78..1fb7fab 100644 --- a/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs +++ b/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs @@ -18,6 +18,12 @@ public class GeocodingParameters : ICountryParameter, ILayersParameter, IKeyPara /// public string Query { get; set; } + /// + /// Gets or sets the language for the results. + /// Supported values: ar, de, en, es, fr, ja, ko, pt, ru, zh. Defaults to en. Optional. + /// + public string Language { get; set; } + /// public IList Countries { get; } = new List(); diff --git a/src/Geo.Radar/Services/RadarGeocoding.cs b/src/Geo.Radar/Services/RadarGeocoding.cs index 4c08f05..defd7d8 100644 --- a/src/Geo.Radar/Services/RadarGeocoding.cs +++ b/src/Geo.Radar/Services/RadarGeocoding.cs @@ -130,6 +130,12 @@ internal Uri BuildGeocodingRequest(GeocodingParameters parameters) AddCountry(parameters, ref query); AddLayers(parameters, ref query); + + if (!string.IsNullOrWhiteSpace(parameters.Language)) + { + query = query.Add("lang", parameters.Language); + } + AddRadarKey(parameters); query = query.AddAdditionalParameters(parameters); @@ -205,7 +211,16 @@ internal Uri BuildAutocompleteRequest(AutocompleteParameters parameters) query = query.Add("mailable", parameters.Mailable.ToString().ToLowerInvariant()); - AddCountry(parameters, ref query); + var autocompleteCountries = string.Join(",", parameters.Countries ?? Array.Empty()); + if (!string.IsNullOrWhiteSpace(autocompleteCountries)) + { + query = query.Add("countryCode", autocompleteCountries); + } + else + { + _logger.RadarDebug(Resources.Services.RadarGeocoding.Invalid_Country); + } + AddLayers(parameters, ref query); AddRadarKey(parameters); query = query.AddAdditionalParameters(parameters); From f6eef8811c113b1532ce7f9deda9a59e83ea26a4 Mon Sep 17 00:00:00 2001 From: JustinCanton <67930245+JustinCanton@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:23:08 -0400 Subject: [PATCH 2/3] feat(geocoding): add missing api parameters for bing, google, mapbox, and radar providers --- .../Models/Parameters/BaseParameters.cs | 17 ++ .../Models/Parameters/ResultParameters.cs | 6 + .../Parameters/ReverseGeocodingParameters.cs | 6 + src/Geo.Bing/Services/BingGeocoding.cs | 25 +++ .../Models/Parameters/DetailsParameters.cs | 12 ++ .../Models/Parameters/GeocodingParameters.cs | 7 + .../PlacesAutocompleteParameters.cs | 6 + .../Parameters/ReverseGeocodingParameters.cs | 7 + src/Geo.Google/Services/GoogleGeocoding.cs | 50 ++++++ .../Abstractions/IMapBoxGeocoding.cs | 18 ++ .../Parameters/GeocodingV6Parameters.cs | 86 ++++++++++ .../ReverseGeocodingV6Parameters.cs | 61 +++++++ src/Geo.MapBox/Services/MapBoxGeocoding.cs | 157 ++++++++++++++++++ .../Models/Parameters/GeocodingParameters.cs | 6 + src/Geo.Radar/Services/RadarGeocoding.cs | 17 +- 15 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs create mode 100644 src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs diff --git a/src/Geo.Bing/Models/Parameters/BaseParameters.cs b/src/Geo.Bing/Models/Parameters/BaseParameters.cs index 769d124..7dc577b 100644 --- a/src/Geo.Bing/Models/Parameters/BaseParameters.cs +++ b/src/Geo.Bing/Models/Parameters/BaseParameters.cs @@ -36,6 +36,23 @@ public class BaseParameters : IAdditionalParameters /// public CultureInfo Culture { get; set; } + /// + /// Gets or sets the user's current location to help determine better results. + /// Format: latitude,longitude (e.g., "47.608,-122.337"). Optional. + /// + public string UserLocation { get; set; } + + /// + /// Gets or sets the IP address of the user's device to help determine better results. Optional. + /// + public string UserIp { get; set; } + + /// + /// Gets or sets the map area currently shown to the user to help determine better results. + /// Format: southLatitude,westLongitude,northLatitude,eastLongitude. Optional. + /// + public string UserMapView { get; set; } + /// public IDictionary AdditionalParameters { get; } = new Dictionary(); } diff --git a/src/Geo.Bing/Models/Parameters/ResultParameters.cs b/src/Geo.Bing/Models/Parameters/ResultParameters.cs index 61c73c0..7895dd1 100644 --- a/src/Geo.Bing/Models/Parameters/ResultParameters.cs +++ b/src/Geo.Bing/Models/Parameters/ResultParameters.cs @@ -16,5 +16,11 @@ public class ResultParameters : BaseParameters /// If the value is 0, the default value will be used. /// public int MaximumResults { get; set; } = 5; + + /// + /// Gets or sets a value indicating whether to restrict results to the country/region and adminDistrict specified. + /// When true, results outside the specified countryRegion and adminDistrict will not be returned. Optional. + /// + public bool StrictMatch { get; set; } = false; } } diff --git a/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs b/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs index 2b28be7..f11870d 100644 --- a/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs +++ b/src/Geo.Bing/Models/Parameters/ReverseGeocodingParameters.cs @@ -50,6 +50,12 @@ public class ReverseGeocodingParameters : BaseParameters, IKeyParameters /// public bool IncludeCountryRegion { get; set; } + /// + /// Gets or sets a value indicating whether AdminDistrict names are returned in expanded form. + /// When true, returns "Washington" instead of "WA". Defaults to false. Optional. + /// + public bool VerbosePlaceNames { get; set; } = false; + /// public string Key { get; set; } } diff --git a/src/Geo.Bing/Services/BingGeocoding.cs b/src/Geo.Bing/Services/BingGeocoding.cs index 8e67ef8..22cadfb 100644 --- a/src/Geo.Bing/Services/BingGeocoding.cs +++ b/src/Geo.Bing/Services/BingGeocoding.cs @@ -195,6 +195,11 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) _logger.BingDebug(Resources.Services.BingGeocoding.Do_Not_Include_Entity_Types); } + if (parameters.VerbosePlaceNames) + { + query = query.Add("verboseplacenames", "true"); + } + BuildBaseQuery(parameters, ref query); AddBingKey(parameters, ref query); @@ -297,6 +302,11 @@ internal void BuildLimitedResultQuery(ResultParameters parameters, ref QueryStri _logger.BingWarning(Resources.Services.BingGeocoding.Invalid_Maximum_Results); } + if (parameters.StrictMatch) + { + query = query.Add("strictMatch", "1"); + } + BuildBaseQuery(parameters, ref query); } @@ -344,6 +354,21 @@ internal void BuildBaseQuery(BaseParameters parameters, ref QueryString query) { _logger.BingDebug(Resources.Services.BingGeocoding.Invalid_Culture); } + + if (!string.IsNullOrWhiteSpace(parameters.UserLocation)) + { + query = query.Add("userLocation", parameters.UserLocation); + } + + if (!string.IsNullOrWhiteSpace(parameters.UserIp)) + { + query = query.Add("userIp", parameters.UserIp); + } + + if (!string.IsNullOrWhiteSpace(parameters.UserMapView)) + { + query = query.Add("usermapView", parameters.UserMapView); + } } /// diff --git a/src/Geo.Google/Models/Parameters/DetailsParameters.cs b/src/Geo.Google/Models/Parameters/DetailsParameters.cs index c66f796..dad27ca 100644 --- a/src/Geo.Google/Models/Parameters/DetailsParameters.cs +++ b/src/Geo.Google/Models/Parameters/DetailsParameters.cs @@ -37,6 +37,18 @@ public class DetailsParameters : BaseParameters, IKeyParameters /// public string SessionToken { get; set; } + /// + /// Gets or sets a value indicating whether to disable translation of reviews. + /// When true, the language code is included in the response along with the original-language review text. Optional. + /// + public bool? ReviewsNoTranslations { get; set; } + + /// + /// Gets or sets the sort order for reviews. + /// Accepted values: most_relevant, newest. Optional. + /// + public string ReviewsSort { get; set; } + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Models/Parameters/GeocodingParameters.cs b/src/Geo.Google/Models/Parameters/GeocodingParameters.cs index 0b32852..031a633 100644 --- a/src/Geo.Google/Models/Parameters/GeocodingParameters.cs +++ b/src/Geo.Google/Models/Parameters/GeocodingParameters.cs @@ -5,6 +5,7 @@ namespace Geo.Google.Models.Parameters { + using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; @@ -36,6 +37,12 @@ public class GeocodingParameters : BaseParameters, IKeyParameters /// public RegionInfo Region { get; set; } + /// + /// Gets the list of extra computations to apply to the geocoding request. + /// Supported values: ADDRESS_DESCRIPTORS, BUILDING_AND_ENTRANCES. Optional. + /// + public IList ExtraComputations { get; } = new List(); + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs b/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs index 7173726..637201e 100644 --- a/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs +++ b/src/Geo.Google/Models/Parameters/PlacesAutocompleteParameters.cs @@ -41,5 +41,11 @@ public class PlacesAutocompleteParameters : QueryAutocompleteParameters /// This is a restriction, rather than a bias, meaning that results outside this region will not be returned even if they match the user input. /// public bool StrictBounds { get; set; } = false; + + /// + /// Gets or sets a hard restriction on the area in which results are returned. + /// Results outside this region will not be returned. Accepts a or value. Optional. + /// + public object LocationRestriction { get; set; } } } diff --git a/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs b/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs index 444f396..f501d46 100644 --- a/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs +++ b/src/Geo.Google/Models/Parameters/ReverseGeocodingParameters.cs @@ -7,6 +7,7 @@ namespace Geo.Google.Models.Parameters { using System.Collections.Generic; using System.ComponentModel.DataAnnotations; + using System.Globalization; using Geo.Google.Enums; /// @@ -39,6 +40,12 @@ public class ReverseGeocodingParameters : BaseParameters, IKeyParameters /// public IEnumerable LocationTypes { get; set; } + /// + /// Gets the list of extra computations to apply to the reverse geocoding request. + /// Supported values: ADDRESS_DESCRIPTORS, BUILDING_AND_ENTRANCES. Optional. + /// + public IList ExtraComputations { get; } = new List(); + /// public string Key { get; set; } } diff --git a/src/Geo.Google/Services/GoogleGeocoding.cs b/src/Geo.Google/Services/GoogleGeocoding.cs index 159d050..3cbdfa5 100644 --- a/src/Geo.Google/Services/GoogleGeocoding.cs +++ b/src/Geo.Google/Services/GoogleGeocoding.cs @@ -232,6 +232,17 @@ internal Uri BuildGeocodingRequest(GeocodingParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Region); } + if (parameters.ExtraComputations != null) + { + foreach (var computation in parameters.ExtraComputations) + { + if (!string.IsNullOrWhiteSpace(computation)) + { + query = query.Add("extra_computations", computation); + } + } + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -301,6 +312,17 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Location_Types); } + if (parameters.ExtraComputations != null) + { + foreach (var computation in parameters.ExtraComputations) + { + if (!string.IsNullOrWhiteSpace(computation)) + { + query = query.Add("extra_computations", computation); + } + } + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -533,6 +555,18 @@ internal Uri BuildDetailsRequest(DetailsParameters parameters) _logger.GoogleDebug(Resources.Services.GoogleGeocoding.Invalid_Fields); } + if (parameters.ReviewsNoTranslations.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("reviews_no_translations", parameters.ReviewsNoTranslations.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (!string.IsNullOrWhiteSpace(parameters.ReviewsSort)) + { + query = query.Add("reviews_sort", parameters.ReviewsSort); + } + AddBaseParameters(parameters, ref query); AddGoogleKey(parameters, ref query); @@ -600,6 +634,22 @@ internal Uri BuildPlaceAutocompleteRequest(PlacesAutocompleteParameters paramete query = query.Add("strictbounds", parameters.StrictBounds.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); #pragma warning restore CA1308 // Normalize strings to uppercase + if (parameters.LocationRestriction != null) + { + switch (parameters.LocationRestriction) + { + case Circle circle: + query = query.Add("locationrestriction", $"circle:{circle}"); + break; + case Boundaries boundary: + query = query.Add("locationrestriction", $"rectangle:{boundary}"); + break; + default: + _logger.GoogleWarning(Resources.Services.GoogleGeocoding.Invalid_Location_Bias_Type); + break; + } + } + AddAutocompleteParameters(parameters, ref query); AddGoogleKey(parameters, ref query); diff --git a/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs b/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs index aa70853..181d209 100644 --- a/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs +++ b/src/Geo.MapBox/Abstractions/IMapBoxGeocoding.cs @@ -35,5 +35,23 @@ public interface IMapBoxGeocoding /// A with the response from MapBox. /// Thrown for multiple different reasons. Check the inner exception for more information. Task> ReverseGeocodingAsync(ReverseGeocodingParameters parameters, CancellationToken cancellationToken = default); + + /// + /// Calls the MapBox Geocoding API v6 (forward) and returns the results. + /// + /// A with the parameters of the request. + /// A used to cancel the request. + /// A with a of with the response from MapBox. + /// Thrown for multiple different reasons. Check the inner exception for more information. + Task>> GeocodingV6Async(GeocodingV6Parameters parameters, CancellationToken cancellationToken = default); + + /// + /// Calls the MapBox Geocoding API v6 (reverse) and returns the results. + /// + /// A with the parameters of the request. + /// A used to cancel the request. + /// A with the response from MapBox. + /// Thrown for multiple different reasons. Check the inner exception for more information. + Task> ReverseGeocodingV6Async(ReverseGeocodingV6Parameters parameters, CancellationToken cancellationToken = default); } } diff --git a/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs b/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs new file mode 100644 index 0000000..bdbbfde --- /dev/null +++ b/src/Geo.MapBox/Models/Parameters/GeocodingV6Parameters.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) Geo.NET. +// Licensed under the MIT license. See the LICENSE file in the solution root for full license information. +// + +namespace Geo.MapBox.Models.Parameters +{ + using System.Collections.Generic; + using System.Globalization; + using Geo.MapBox.Enums; + using Geo.MapBox.Models; + + /// + /// The parameters possible to use during a Mapbox Geocoding API v6 forward geocoding request. + /// + public class GeocodingV6Parameters : IKeyParameters, IAdditionalParameters + { + /// + /// Gets or sets the search text to geocode. + /// + public string Query { get; set; } + + /// + /// Gets or sets a value indicating whether to store results permanently. + /// Must be true when using the Permanent endpoint (requires an enterprise plan). Default is false. Optional. + /// + public bool Permanent { get; set; } = false; + + /// + /// Gets or sets a value indicating whether to enable autocomplete suggestions. Optional. + /// + public bool? Autocomplete { get; set; } + + /// + /// Gets or sets a bounding box to limit results. Optional. + /// + public BoundingBox BoundingBox { get; set; } + + /// + /// Gets the list of countries to limit the request to. Optional. + /// + public IList Countries { get; } = new List(); + + /// + /// Gets or sets the response format. Accepted values: geojson, v5. Defaults to geojson. Optional. + /// + public string Format { get; set; } + + /// + /// Gets the list of languages of the text supplied in responses. Optional. + /// + public IList Languages { get; } = new List(); + + /// + /// Gets or sets the maximum number of results to return. Default is 5. Optional. + /// + public uint? Limit { get; set; } + + /// + /// Gets or sets a location to bias results toward. Format: longitude,latitude or "ip". Optional. + /// + public string Proximity { get; set; } + + /// + /// Gets a list used to filter results to include only a subset of available feature types. Optional. + /// + public IList Types { get; } = new List(); + + /// + /// Gets or sets the worldview to use. + /// Available values: ar, cn, in, jp, ma, rs, ru, tr, us. Defaults to us. Optional. + /// + public string Worldview { get; set; } + + /// + /// Gets or sets a value indicating whether to include building entrance data (beta feature). Optional. + /// + public bool? Entrances { get; set; } + + /// + public string Key { get; set; } + + /// + public IDictionary AdditionalParameters { get; } = new Dictionary(); + } +} diff --git a/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs b/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs new file mode 100644 index 0000000..448c274 --- /dev/null +++ b/src/Geo.MapBox/Models/Parameters/ReverseGeocodingV6Parameters.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Geo.NET. +// Licensed under the MIT license. See the LICENSE file in the solution root for full license information. +// + +namespace Geo.MapBox.Models.Parameters +{ + using System.Collections.Generic; + using System.Globalization; + using Geo.MapBox.Enums; + using Geo.MapBox.Models; + + /// + /// The parameters possible to use during a Mapbox Geocoding API v6 reverse geocoding request. + /// + public class ReverseGeocodingV6Parameters : IKeyParameters, IAdditionalParameters + { + /// + /// Gets or sets the coordinate to reverse geocode. + /// + public Coordinate Coordinate { get; set; } + + /// + /// Gets or sets a value indicating whether to store results permanently. + /// Must be true when using the Permanent endpoint (requires an enterprise plan). Default is false. Optional. + /// + public bool Permanent { get; set; } = false; + + /// + /// Gets the list of countries to limit the request to. Optional. + /// + public IList Countries { get; } = new List(); + + /// + /// Gets the list of languages of the text supplied in responses. Optional. + /// + public IList Languages { get; } = new List(); + + /// + /// Gets or sets the maximum number of results to return. Default is 1. Optional. + /// + public uint? Limit { get; set; } + + /// + /// Gets a list used to filter results to include only a subset of available feature types. Optional. + /// + public IList Types { get; } = new List(); + + /// + /// Gets or sets the worldview to use. + /// Available values: ar, cn, in, jp, ma, rs, ru, tr, us. Defaults to us. Optional. + /// + public string Worldview { get; set; } + + /// + public string Key { get; set; } + + /// + public IDictionary AdditionalParameters { get; } = new Dictionary(); + } +} diff --git a/src/Geo.MapBox/Services/MapBoxGeocoding.cs b/src/Geo.MapBox/Services/MapBoxGeocoding.cs index 1e356b9..d2e5912 100644 --- a/src/Geo.MapBox/Services/MapBoxGeocoding.cs +++ b/src/Geo.MapBox/Services/MapBoxGeocoding.cs @@ -33,6 +33,8 @@ public class MapBoxGeocoding : GeoClient, IMapBoxGeocoding private const string ReverseGeocodeUri = "https://api.mapbox.com/geocoding/v5/{0}/{1}.json"; private const string PlacesEndpoint = "mapbox.places"; private const string PermanentEndpoint = "mapbox.places-permanent"; + private const string GeocodeV6Uri = "https://api.mapbox.com/search/geocode/v6/forward"; + private const string ReverseGeocodeV6Uri = "https://api.mapbox.com/search/geocode/v6/reverse"; private readonly IOptions> _options; private readonly ILogger _logger; @@ -76,6 +78,26 @@ public async Task> ReverseGeocodingAsync( return await GetAsync>(uri, cancellationToken).ConfigureAwait(false); } + /// + public async Task>> GeocodingV6Async( + GeocodingV6Parameters parameters, + CancellationToken cancellationToken = default) + { + var uri = ValidateAndBuildUri(parameters, BuildGeocodingV6Request); + + return await GetAsync>>(uri, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task> ReverseGeocodingV6Async( + ReverseGeocodingV6Parameters parameters, + CancellationToken cancellationToken = default) + { + var uri = ValidateAndBuildUri(parameters, BuildReverseGeocodingV6Request); + + return await GetAsync>(uri, cancellationToken).ConfigureAwait(false); + } + /// /// Validates the uri and builds it based on the parameter type. /// @@ -187,6 +209,141 @@ internal Uri BuildReverseGeocodingRequest(ReverseGeocodingParameters parameters) return uriBuilder.Uri; } + /// + /// Builds the Geocoding API v6 forward geocoding uri based on the passed parameters. + /// + /// A with the v6 geocoding parameters to build the uri with. + /// A with the completed MapBox v6 geocoding uri. + /// Thrown when the 'Query' parameter is null or invalid. + internal Uri BuildGeocodingV6Request(GeocodingV6Parameters parameters) + { + if (string.IsNullOrWhiteSpace(parameters.Query)) + { + _logger.MapBoxError(Resources.Services.MapBoxGeocoding.Invalid_Query); + throw new ArgumentException(Resources.Services.MapBoxGeocoding.Invalid_Query, nameof(parameters.Query)); + } + + var uriBuilder = new UriBuilder(GeocodeV6Uri); + var query = QueryString.Empty; + + query = query.Add("q", parameters.Query); + +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("permanent", parameters.Permanent.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + + if (parameters.Autocomplete.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("autocomplete", parameters.Autocomplete.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (parameters.BoundingBox != null) + { + query = query.Add("bbox", parameters.BoundingBox.ToString()); + } + + if (!string.IsNullOrWhiteSpace(parameters.Format)) + { + query = query.Add("format", parameters.Format); + } + + if (!string.IsNullOrWhiteSpace(parameters.Proximity)) + { + query = query.Add("proximity", parameters.Proximity); + } + + if (parameters.Entrances.HasValue) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("entrances", parameters.Entrances.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + AddBaseV6Parameters(parameters.Countries, parameters.Languages, parameters.Limit, parameters.Types, parameters.Worldview, ref query); + + AddMapBoxKey(parameters, ref query); + query = query.AddAdditionalParameters(parameters); + + uriBuilder.AddQuery(query); + + return uriBuilder.Uri; + } + + /// + /// Builds the Geocoding API v6 reverse geocoding uri based on the passed parameters. + /// + /// A with the v6 reverse geocoding parameters to build the uri with. + /// A with the completed MapBox v6 reverse geocoding uri. + /// Thrown when the 'Coordinate' parameter is null or invalid. + internal Uri BuildReverseGeocodingV6Request(ReverseGeocodingV6Parameters parameters) + { + if (parameters.Coordinate is null) + { + _logger.MapBoxError(Resources.Services.MapBoxGeocoding.Invalid_Coordinate); + throw new ArgumentException(Resources.Services.MapBoxGeocoding.Invalid_Coordinate, nameof(parameters.Coordinate)); + } + + var uriBuilder = new UriBuilder(ReverseGeocodeV6Uri); + var query = QueryString.Empty; + + query = query.Add("longitude", parameters.Coordinate.Longitude.ToString(CultureInfo.InvariantCulture)); + query = query.Add("latitude", parameters.Coordinate.Latitude.ToString(CultureInfo.InvariantCulture)); + +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("permanent", parameters.Permanent.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); +#pragma warning restore CA1308 // Normalize strings to uppercase + + AddBaseV6Parameters(parameters.Countries, parameters.Languages, parameters.Limit, parameters.Types, parameters.Worldview, ref query); + + AddMapBoxKey(parameters, ref query); + query = query.AddAdditionalParameters(parameters); + + uriBuilder.AddQuery(query); + + return uriBuilder.Uri; + } + + /// + /// Adds shared v6 query parameters (countries, languages, limit, types, worldview). + /// + internal void AddBaseV6Parameters( + IList countries, + IList languages, + uint? limit, + IList types, + string worldview, + ref QueryString query) + { + if (countries != null && countries.Count > 0) + { + query = query.Add("country", string.Join(",", countries.Select(x => x.TwoLetterISORegionName))); + } + + if (languages != null && languages.Count > 0) + { + query = query.Add("language", string.Join(",", languages.Select(x => x.Name))); + } + + if (limit.HasValue && limit.Value > 0) + { + query = query.Add("limit", limit.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (types != null && types.Count > 0) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + query = query.Add("types", string.Join(",", types.Select(x => x.ToString().ToLowerInvariant()))); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + if (!string.IsNullOrWhiteSpace(worldview)) + { + query = query.Add("worldview", worldview); + } + } + /// /// Adds the base query parameters based on the allowed logic. /// diff --git a/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs b/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs index 198bb78..1fb7fab 100644 --- a/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs +++ b/src/Geo.Radar/Models/Parameters/GeocodingParameters.cs @@ -18,6 +18,12 @@ public class GeocodingParameters : ICountryParameter, ILayersParameter, IKeyPara /// public string Query { get; set; } + /// + /// Gets or sets the language for the results. + /// Supported values: ar, de, en, es, fr, ja, ko, pt, ru, zh. Defaults to en. Optional. + /// + public string Language { get; set; } + /// public IList Countries { get; } = new List(); diff --git a/src/Geo.Radar/Services/RadarGeocoding.cs b/src/Geo.Radar/Services/RadarGeocoding.cs index 4c08f05..defd7d8 100644 --- a/src/Geo.Radar/Services/RadarGeocoding.cs +++ b/src/Geo.Radar/Services/RadarGeocoding.cs @@ -130,6 +130,12 @@ internal Uri BuildGeocodingRequest(GeocodingParameters parameters) AddCountry(parameters, ref query); AddLayers(parameters, ref query); + + if (!string.IsNullOrWhiteSpace(parameters.Language)) + { + query = query.Add("lang", parameters.Language); + } + AddRadarKey(parameters); query = query.AddAdditionalParameters(parameters); @@ -205,7 +211,16 @@ internal Uri BuildAutocompleteRequest(AutocompleteParameters parameters) query = query.Add("mailable", parameters.Mailable.ToString().ToLowerInvariant()); - AddCountry(parameters, ref query); + var autocompleteCountries = string.Join(",", parameters.Countries ?? Array.Empty()); + if (!string.IsNullOrWhiteSpace(autocompleteCountries)) + { + query = query.Add("countryCode", autocompleteCountries); + } + else + { + _logger.RadarDebug(Resources.Services.RadarGeocoding.Invalid_Country); + } + AddLayers(parameters, ref query); AddRadarKey(parameters); query = query.AddAdditionalParameters(parameters); From 8921d28ec3961e7b2a521a6f9f492487a518804e Mon Sep 17 00:00:00 2001 From: JustinCanton <67930245+JustinCanton@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:36:56 -0400 Subject: [PATCH 3/3] test(radar): fixing unit tests --- test/Geo.Radar.Tests/Services/RadarGeocodingShould.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Geo.Radar.Tests/Services/RadarGeocodingShould.cs b/test/Geo.Radar.Tests/Services/RadarGeocodingShould.cs index 136c932..bc25ab1 100644 --- a/test/Geo.Radar.Tests/Services/RadarGeocodingShould.cs +++ b/test/Geo.Radar.Tests/Services/RadarGeocodingShould.cs @@ -363,7 +363,7 @@ public void BuildAutocompleteRequest_WithValidParameters_SuccessfullyBuildsUrl(C query.Should().Contain("near=56.78,78.91"); query.Should().Contain("limit=14"); query.Should().Contain("mailable=true"); - query.Should().Contain("country=CA"); + query.Should().Contain("countryCode=CA"); query.Should().Contain("layers=postalCode,country"); _httpClient.DefaultRequestHeaders.Authorization.Scheme.Should().Be("123abc");