diff --git a/Geocoding.sln b/Geocoding.sln index 6cbe577..29f7a5f 100644 --- a/Geocoding.sln +++ b/Geocoding.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.Tests", "test\Geo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.Here", "src\Geocoding.Here\Geocoding.Here.csproj", "{41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geocoding.UsCensusBureau", "src\Geocoding.UsCensusBureau\Geocoding.UsCensusBureau.csproj", "{45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,10 @@ Global {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Debug|Any CPU.Build.0 = Debug|Any CPU {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Release|Any CPU.ActiveCfg = Release|Any CPU {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048}.Release|Any CPU.Build.0 = Release|Any CPU + {45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -63,6 +69,7 @@ Global {27F58640-D424-40F5-945E-42BF8BC872A0} = {F742864D-9400-4CE2-957A-DBD0A0237277} {9773C7DA-DD1A-490D-B4D8-CEA18804AD1E} = {F742864D-9400-4CE2-957A-DBD0A0237277} {41F9E0D3-2094-4CE7-A2CD-F3CAF585A048} = {F742864D-9400-4CE2-957A-DBD0A0237277} + {45E82D63-BEC0-4DF2-AC0E-AEB2D235ED5B} = {F742864D-9400-4CE2-957A-DBD0A0237277} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {085E97E6-56E0-4099-94E9-10F9080F2DD1} diff --git a/README.md b/README.md index 3c515f2..93796b1 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Includes a model and interface for communicating with five popular Geocoding pro * :warning: MapQuest [(Commercial API)](http://www.mapquestapi.com/) - [docs](http://www.mapquestapi.com/geocoding/) * :warning: MapQuest [(OpenStreetMap)](http://open.mapquestapi.com/) - [docs](http://open.mapquestapi.com/geocoding/) * [HERE](https://www.here.com/) - [docs](https://developer.here.com/documentation) + * [U.S. Census Geocoder](https://geocoding.geo.census.gov/) - [docs](https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf) (note: US Only & reverse geocoding not supported) The API returns latitude/longitude coordinates and normalized address information. This can be used to perform address validation, real time mapping of user-entered addresses, distance calculations, and much more. diff --git a/src/Geocoding.UsCensusBureau/Geocoding.UsCensusBureau.csproj b/src/Geocoding.UsCensusBureau/Geocoding.UsCensusBureau.csproj new file mode 100644 index 0000000..e37daeb --- /dev/null +++ b/src/Geocoding.UsCensusBureau/Geocoding.UsCensusBureau.csproj @@ -0,0 +1,37 @@ + + + + Includes a model and interface for communicating with four popular Geocoding providers. Current implementations include: Google Maps, Yahoo! PlaceFinder, Bing Maps (aka Virtual Earth), and Mapquest. The API returns latitude/longitude coordinates and normalized address information. This can be used to perform address validation, real time mapping of user-entered addresses, distance calculations, and much more. + Geocoding.net UsCensusBureau + 4.0.0-beta1 + chadly + netstandard1.3;net46 + Geocoding.UsCensusBureau + Geocoding.UsCensusBureau + geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest + https://github.com/chadly/Geocoding.net/releases/latest + https://github.com/chadly/Geocoding.net + https://github.com/chadly/Geocoding.net/blob/master/LICENSE + https://github.com/chadly/Geocoding.net.git + git + 4.0.0 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Geocoding.UsCensusBureau/UsCensusBureauAddress.cs b/src/Geocoding.UsCensusBureau/UsCensusBureauAddress.cs new file mode 100644 index 0000000..2e90d79 --- /dev/null +++ b/src/Geocoding.UsCensusBureau/UsCensusBureauAddress.cs @@ -0,0 +1,11 @@ +namespace Geocoding.UsCensusBureau +{ + public class UsCensusBureauAddress : Address + { + public UsCensusBureauAddress(string formattedAddress, Location coordinates) + : base(formattedAddress, coordinates, UsCensusBureauConstants.Provider) + { + + } + } +} diff --git a/src/Geocoding.UsCensusBureau/UsCensusBureauConstants.cs b/src/Geocoding.UsCensusBureau/UsCensusBureauConstants.cs new file mode 100644 index 0000000..247fec5 --- /dev/null +++ b/src/Geocoding.UsCensusBureau/UsCensusBureauConstants.cs @@ -0,0 +1,19 @@ +namespace Geocoding.UsCensusBureau +{ + public class UsCensusBureauConstants + { + public const string Provider = "UsCensusBureau"; + + public const string BaseUrl = "https://geocoding.geo.census.gov/geocoder/"; + public const string OneLineAddressPath = "locations/onelineaddress?"; + public const string AddressPath = "locations/address?"; + + public const string AddressMatchesKey = "addressMatches"; + public const string MatchedAddressKey = "matchedAddress"; + public const string CoordinatesKey = "coordinates"; + public const string ResultKey = "result"; + public const string ErrorsKey = "errors"; + public const string XKey = "x"; + public const string YKey = "y"; + } +} diff --git a/src/Geocoding.UsCensusBureau/UsCensusBureauGeocoder.cs b/src/Geocoding.UsCensusBureau/UsCensusBureauGeocoder.cs new file mode 100644 index 0000000..84fec09 --- /dev/null +++ b/src/Geocoding.UsCensusBureau/UsCensusBureauGeocoder.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace Geocoding.UsCensusBureau +{ + /// + /// refs: + /// - https://geocoding.geo.census.gov/ + /// - https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf + /// + public class UsCensusBureauGeocoder : IGeocoder + { + private readonly int _benchmark; + private readonly string _format; + private readonly HttpClient _client; + + public UsCensusBureauGeocoder(int benchmark = 4, string format = "json") + { + _benchmark = benchmark; + _format = format; + _client = new HttpClient { BaseAddress = new Uri(UsCensusBureauConstants.BaseUrl) }; + } + + public async Task> GeocodeAsync(string address, CancellationToken cancellationToken = default(CancellationToken)) + { + // Build Query String + var sb = new StringBuilder(UsCensusBureauConstants.OneLineAddressPath); + sb.Append("address=").Append(WebUtility.UrlEncode(address)) + .Append("&benchmark=").Append(_benchmark) + .Append("&format=").Append(_format); + + // Get Request + var response = await _client.GetAsync(sb.ToString(), cancellationToken); + var content = await response.Content.ReadAsStringAsync(); + + // Read Result + return GetAddresses(content); + } + + public async Task> GeocodeAsync(string street, string city, string state, string postalCode, string country, CancellationToken cancellationToken = default(CancellationToken)) + { + // Build Query String + var sb = new StringBuilder(UsCensusBureauConstants.AddressPath); + sb.Append("street=").Append(WebUtility.UrlEncode(street)) + .Append("&city=").Append(WebUtility.UrlEncode(city)) + .Append("&state=").Append(WebUtility.UrlEncode(state)) + .Append("&zip=").Append(WebUtility.UrlEncode(postalCode)) + .Append("&benchmark=").Append(_benchmark) + .Append("&format=").Append(_format); + + // Get Request + var response = await _client.GetAsync(sb.ToString(), cancellationToken); + var content = await response.Content.ReadAsStringAsync(); + + // Read Result + return GetAddresses(content); + } + + public Task> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotSupportedException(); + } + + public Task> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotSupportedException(); + } + + private static IEnumerable GetAddresses(string response) + { + var json = JObject.Parse(response); + + var errors = json[UsCensusBureauConstants.ErrorsKey]; + if (errors != null) + return new UsCensusBureauAddress[] {}; + + var result = json[UsCensusBureauConstants.ResultKey]; + return result[UsCensusBureauConstants.AddressMatchesKey] + .Select(match => + { + var matched = match[UsCensusBureauConstants.MatchedAddressKey].ToString(); + var coordinates = match[UsCensusBureauConstants.CoordinatesKey]; + var x = double.Parse(coordinates[UsCensusBureauConstants.XKey].ToString()); + var y = double.Parse(coordinates[UsCensusBureauConstants.YKey].ToString()); + + return new UsCensusBureauAddress(matched, new Location(y, x)); + }) + .ToArray(); + } + } +} diff --git a/test/Geocoding.Tests/GeocoderTest.cs b/test/Geocoding.Tests/GeocoderTest.cs index 82be0a9..fb75d6f 100644 --- a/test/Geocoding.Tests/GeocoderTest.cs +++ b/test/Geocoding.Tests/GeocoderTest.cs @@ -1,15 +1,12 @@ -using System.Globalization; -using System.Linq; -using System.Threading; +using System.Linq; using System.Threading.Tasks; using Xunit; -using Xunit.Extensions; namespace Geocoding.Tests { public abstract class GeocoderTest { - readonly IGeocoder geocoder; + protected readonly IGeocoder geocoder; protected readonly SettingsFixture settings; public GeocoderTest(SettingsFixture settings) @@ -106,4 +103,4 @@ public virtual async Task CanGeocodeInvalidZipCodes(string address) Assert.NotEmpty(addresses); } } -} \ No newline at end of file +} diff --git a/test/Geocoding.Tests/Geocoding.Tests.csproj b/test/Geocoding.Tests/Geocoding.Tests.csproj index df58082..41db7b3 100644 --- a/test/Geocoding.Tests/Geocoding.Tests.csproj +++ b/test/Geocoding.Tests/Geocoding.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/test/Geocoding.Tests/UsCensusBureauTest.cs b/test/Geocoding.Tests/UsCensusBureauTest.cs new file mode 100644 index 0000000..6e89b0c --- /dev/null +++ b/test/Geocoding.Tests/UsCensusBureauTest.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Geocoding.UsCensusBureau; +using Xunit; + +namespace Geocoding.Tests +{ + [Collection("Settings")] + public class UsCensusBureauTest : GeocoderTest + { + public UsCensusBureauTest(SettingsFixture settings) : base(settings) + { + } + + protected override IGeocoder CreateGeocoder() + { + return new UsCensusBureauGeocoder(); + } + + [Theory(Skip = "not supported - us addresses only")] + public override Task CanGeocodeAddressUnderDifferentCultures(string cultureName) + { + return Task.CompletedTask; + } + + [Theory(Skip = "not supported - reverse geocode")] + public override Task CanReverseGeocodeAddressUnderDifferentCultures(string cultureName) + { + return Task.CompletedTask; + } + + [Theory(Skip = "using different input with CanGeocodeWithSpecialCharacters2")] + public override Task CanGeocodeWithSpecialCharacters(string address) + { + return Task.CompletedTask; + } + + [Theory] + [InlineData("12110 CLAYTON ROAD TOWN & COUNTRY,SAINT LOUIS,MO,63131,US")] + public async Task CanGeocodeWithSpecialCharacters2(string address) + { + Address[] addresses = (await geocoder.GeocodeAsync(address)).ToArray(); + + //asserting no exceptions are thrown and that we get something + Assert.NotEmpty(addresses); + } + + [Theory(Skip = "not supported - exact addresses only")] + public override Task CanHandleStreetIntersectionsByAmpersand(string address) + { + return Task.CompletedTask; + } + + [Fact(Skip = "not supported - reverse geocode")] + public override Task CanReverseGeocodeAsync() + { + return Task.CompletedTask; + } + + [Theory(Skip = "not supported")] + public override Task CanGeocodeInvalidZipCodes(string address) + { + return Task.CompletedTask; + } + } +}