Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/Nominatim.API.Tests/DictionaryExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using NUnit.Framework;
using Nominatim.API.Extensions;

namespace Nominatim.API.Tests
{
public class DictionaryExtensionsTests
{
[Test]
[TestCase(true, "1")]
[TestCase(false, "0")]
public void DictionaryExtensionsTest_TestBool(bool value, string expect)
{
var dict = new Dictionary<string, string>();

var key = "key";

dict.AddIfSet(key, value);

Assert.IsTrue(dict.ContainsKey(key));
Assert.AreEqual(expect, dict[key]);
}

[Test]
[TestCase(true, "1")]
[TestCase(false, "0")]
public void DictionaryExtensionsTest_TestNullableBoolWithValue(bool? value, string expect)
{
var dict = new Dictionary<string, string>();

var key = "key";

dict.AddIfSet(key, value);

Assert.IsTrue(dict.ContainsKey(key));
Assert.AreEqual(expect, dict[key]);
}

public void DictionaryExtensionsTest_TestNullableBoolEqualsNull()
{
var dict = new Dictionary<string, string>();

var key = "key";
bool? value = null;

dict.AddIfSet(key, value);

Assert.IsFalse(dict.ContainsKey(key));
}
}
}
89 changes: 89 additions & 0 deletions src/Nominatim.API.Tests/PrivateContractResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Newtonsoft.Json;

using Nominatim.API.Contracts;
using Nominatim.API.Models;

using NUnit.Framework;

using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Nominatim.API.Tests
{
public class PrivateContractResolverTests
{
[Test]
public void PrivateContractResolverTest_ParallelismShouldWork()
{
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new PrivateContractResolver()
};

var random = new Random(69);

AddressResult[] addresses = new AddressResult[10000];

for (int i = 0; i < 10000; i++)
{
addresses[i] = new AddressResult
{
City = random.Next(100000).ToString(),
Country = random.Next(100000).ToString(),
CountryCode = random.Next(100000).ToString(),
County = random.Next(100000).ToString(),
District = random.Next(100000).ToString(),
Hamlet = random.Next(100000).ToString(),
HouseNumber = random.Next(100000).ToString(),
Name = random.Next(100000).ToString(),
Neighborhood = random.Next(100000).ToString(),
Pedestrian = random.Next(100000).ToString(),
PostCode = random.Next(100000).ToString(),
Region = random.Next(100000).ToString(),
Road = random.Next(100000).ToString(),
State = random.Next(100000).ToString(),
Suburb = random.Next(100000).ToString(),
Town = random.Next(100000).ToString(),
Village = random.Next(100000).ToString(),
};
}

var jsons = addresses.Select(JsonConvert.SerializeObject).ToArray();

AddressResult[] result = new AddressResult[addresses.Length];

Parallel.For(0, addresses.Length, index =>
{
result[index] = JsonConvert.DeserializeObject<AddressResult>(jsons[index], jsonSerializerSettings)!;
});

Assert.AreEqual(addresses.Length, result.Length);

for (int i = 0; i < addresses.Length; i++)
{
Assert.AreEqual(addresses[i].City, result[i].City);
Assert.AreEqual(addresses[i].Country, result[i].Country);
Assert.AreEqual(addresses[i].CountryCode, result[i].CountryCode);
Assert.AreEqual(addresses[i].County, result[i].County);
Assert.AreEqual(addresses[i].District, result[i].District);
Assert.AreEqual(addresses[i].Hamlet, result[i].Hamlet);
Assert.AreEqual(addresses[i].HouseNumber, result[i].HouseNumber);
Assert.AreEqual(addresses[i].Name, result[i].Name);
Assert.AreEqual(addresses[i].Neighborhood, result[i].Neighborhood);
Assert.AreEqual(addresses[i].Pedestrian, result[i].Pedestrian);
Assert.AreEqual(addresses[i].PostCode, result[i].PostCode);
Assert.AreEqual(addresses[i].Region, result[i].Region);
Assert.AreEqual(addresses[i].Road, result[i].Road);
Assert.AreEqual(addresses[i].State, result[i].State);
Assert.AreEqual(addresses[i].Suburb, result[i].Suburb);
Assert.AreEqual(addresses[i].Town, result[i].Town);
Assert.AreEqual(addresses[i].Village, result[i].Village);
}
}
}
}
21 changes: 12 additions & 9 deletions src/Nominatim.API/Address/AddressSearcher.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nominatim.API.Extensions;
using Nominatim.API.Interfaces;
using Nominatim.API.Models;
using Nominatim.API.Web;

namespace Nominatim.API.Address {
/// <summary>
/// Lookup the address of one or multiple OSM objects like node, way or relation.
/// </summary>
public class AddressSearcher : IAddressSearcher {
private readonly INominatimWebInterface _nominatimWebInterface;

public string url;
//jsonv2 not supported for lookup
private readonly string format = "json";

public readonly string url;
/// <summary>
/// API Key, if you are using an Nominatim service that requires one.
/// </summary>
public string key;
public readonly string key;

//jsonv2 not supported for lookup
private readonly string format = "json";

/// <summary>
/// Constructur
/// </summary>
/// <param name="nominatimWebInterface">Injected instance of INominatimWebInterface</param>
/// <param name="URL">URL to Nominatim service. Defaults to OSM demo site.</param>
public AddressSearcher(INominatimWebInterface nominatimWebInterface, string URL = null) {
/// <param name="apiKey">API Key, if you are using an Nominatim service that requires one.</param>
public AddressSearcher(INominatimWebInterface nominatimWebInterface, string URL = @"https://nominatim.openstreetmap.org/lookup", string apiKey = null) {
_nominatimWebInterface = nominatimWebInterface;
url = URL ?? @"https://nominatim.openstreetmap.org/lookup";
url = URL;
key = apiKey;
}

/// <summary>
Expand All @@ -50,7 +53,7 @@ private Dictionary<string, string> buildQueryString(AddressSearchRequest r) {
c.AddIfSet("namedetails", r.ShowAlternativeNames);
c.AddIfSet("extratags", r.ShowExtraTags);
c.AddIfSet("email", r.EmailAddress);
c.AddIfSet("osm_ids", string.Join(",", r.OSMIDs ?? new List<string>()));
c.AddIfSet("osm_ids", r.OSMIDs?.Any() == true ? string.Join(",", r.OSMIDs) : null);

return c;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Nominatim.API/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Nominatim.API.Tests")]
14 changes: 13 additions & 1 deletion src/Nominatim.API/Contracts/PrivateContractResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@

namespace Nominatim.API.Contracts {
internal class PrivateContractResolver : DefaultContractResolver {
private static object _locker = new object();

private readonly static Dictionary<Type, List<MemberInfo>> _serializableMembers = new Dictionary<Type, List<MemberInfo>>();

protected override List<MemberInfo> GetSerializableMembers(Type objectType) {
if (_serializableMembers.TryGetValue(objectType, out var members))
return members;

var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
MemberInfo[] fields = objectType.GetFields(flags);
return fields
var result = fields
.Concat(objectType.GetProperties(flags).Where(propInfo => propInfo.CanWrite))
.ToList();

lock (_locker)
_serializableMembers[objectType] = result;

return result;
}

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
Expand Down
31 changes: 20 additions & 11 deletions src/Nominatim.API/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
using System.Collections.Generic;

namespace Nominatim.API.Extensions {
namespace Nominatim.API.Extensions
{
internal static class DictionaryExtensions {
public static void AddIfSet<T>(this Dictionary<string, string> d, string key, T value) {
if (value == null) {
return;
}

var type = typeof(T);
d.Add(key, value.ToString());
}

if (type == typeof(string)) {
var s = value as string;
if (!s.hasValue()) {
return;
}
public static void AddIfSet(this Dictionary<string, string> d, string key, string value)
{
if (!value.hasValue()) {
return;
}

if (type == typeof(bool) || type == typeof(bool?)) {
var b = value as bool?;
d.Add(key, b.HasValue && b.Value ? "1" : "0");
d.Add(key, value.ToString());
}

public static void AddIfSet(this Dictionary<string, string> d, string key, bool? value)
{
if (value == null) {
return;
}

d.Add(key, $"{value}");
d.AddIfSet(key, value.Value);
}

public static void AddIfSet(this Dictionary<string, string> d, string key, bool value)
{
d.Add(key, value ? "1" : "0");
}
}
}
2 changes: 1 addition & 1 deletion src/Nominatim.API/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Nominatim.API.Extensions {
public static class StringExtensions {
internal static bool hasValue(this string str) {
return !string.IsNullOrEmpty(str);
return !string.IsNullOrWhiteSpace(str);
}
}
}
12 changes: 6 additions & 6 deletions src/Nominatim.API/Geocoders/ForwardGeocoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
using Nominatim.API.Extensions;
using Nominatim.API.Interfaces;
using Nominatim.API.Models;
using Nominatim.API.Web;

namespace Nominatim.API.Geocoders {
/// <summary>
/// Class to enable forward geocoding (e.g. address to latitude and longitude)
/// </summary>
public class ForwardGeocoder : GeocoderBase, IForwardGeocoder {
private readonly INominatimWebInterface _nominatimWebInterface;


/// <summary>
/// Constructor
/// </summary>
/// <param name="nominatimWebInterface">Injected instance of INominatimWebInterface</param>
/// <param name="URL">URL to Nominatim service. Defaults to OSM demo site.</param>
public ForwardGeocoder(INominatimWebInterface nominatimWebInterface, string URL = null) : base(URL ?? @"https://nominatim.openstreetmap.org/search") {
_nominatimWebInterface = nominatimWebInterface;
}
/// <param name="apiKey">API Key, if you are using an Nominatim service that requires one.</param>
public ForwardGeocoder(
INominatimWebInterface nominatimWebInterface,
string URL = @"https://nominatim.openstreetmap.org/search",
string apiKey = "") : base(nominatimWebInterface, URL, apiKey) { }

/// <summary>
/// Attempt to get coordinates for a specified query or address.
Expand Down
14 changes: 10 additions & 4 deletions src/Nominatim.API/Geocoders/GeocoderBase.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
namespace Nominatim.API.Geocoders {
using Nominatim.API.Interfaces;

namespace Nominatim.API.Geocoders {
public abstract class GeocoderBase {
protected GeocoderBase(string URL) {
protected GeocoderBase(INominatimWebInterface nominatimWebInterface, string URL, string apiKey = "") {
_nominatimWebInterface = nominatimWebInterface;
url = URL;
key = apiKey;
}

protected readonly INominatimWebInterface _nominatimWebInterface;

/// <summary>
/// URL to Nominatim service
/// </summary>
public string url;
/// <summary>
/// API Key, if you are using an Nominatim service that requires one.
/// </summary>
public string key = string.Empty;
public string key;
/// <summary>
/// Format of response objects. This library only supports JSON/JSONV2.
/// </summary>
public readonly string format = "jsonv2";
public const string format = "jsonv2";
}
}
13 changes: 6 additions & 7 deletions src/Nominatim.API/Geocoders/ReverseGeocoder.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@

using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Nominatim.API.Extensions;
using Nominatim.API.Interfaces;
using Nominatim.API.Models;
using Nominatim.API.Web;

namespace Nominatim.API.Geocoders {
/// <summary>
/// Class to enable reverse geocoding (e.g. latitude and longitude to address)
/// </summary>
public class ReverseGeocoder : GeocoderBase, IReverseGeocoder {
private readonly INominatimWebInterface _nominatimWebInterface;


/// <summary>
/// Constructor
/// </summary>
/// <param name="nominatimWebInterface">Injected instance of INominatimWebInterface</param>
/// <param name="URL">URL to Nominatim service. Defaults to OSM demo site.</param>
public ReverseGeocoder(INominatimWebInterface nominatimWebInterface, string URL = null) : base(URL ?? @"https://nominatim.openstreetmap.org/reverse") {
_nominatimWebInterface = nominatimWebInterface;
}
/// <param name="apiKey">API Key, if you are using an Nominatim service that requires one.</param>
public ReverseGeocoder(
INominatimWebInterface nominatimWebInterface,
string URL = @"https://nominatim.openstreetmap.org/reverse",
string apiKey = "") : base(nominatimWebInterface, URL, apiKey) { }

/// <summary>
/// Attempt to get an address or location from a set of coordinates
Expand Down
Loading