Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature Us Census Bureau #118

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
7 changes: 7 additions & 0 deletions Geocoding.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
37 changes: 37 additions & 0 deletions src/Geocoding.UsCensusBureau/Geocoding.UsCensusBureau.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>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.</Description>
<AssemblyTitle>Geocoding.net UsCensusBureau</AssemblyTitle>
<VersionPrefix>4.0.0-beta1</VersionPrefix>
<Authors>chadly</Authors>
<TargetFrameworks>netstandard1.3;net46</TargetFrameworks>
<AssemblyName>Geocoding.UsCensusBureau</AssemblyName>
<PackageId>Geocoding.UsCensusBureau</PackageId>
<PackageTags>geocoding;geocode;geocoder;maps;address;validation;normalization;google-maps;bing-maps;yahoo-placefinder;mapquest</PackageTags>
<PackageReleaseNotes>https://github.com/chadly/Geocoding.net/releases/latest</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/chadly/Geocoding.net</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/chadly/Geocoding.net/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryUrl>https://github.com/chadly/Geocoding.net.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>4.0.0</Version>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Geocoding.Core\Geocoding.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Net.Http" Version="4.3.1" />
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net46'">
<Reference Include="System.Net.Http" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions src/Geocoding.UsCensusBureau/UsCensusBureauAddress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Geocoding.UsCensusBureau
{
public class UsCensusBureauAddress : Address
{
public UsCensusBureauAddress(string formattedAddress, Location coordinates)
: base(formattedAddress, coordinates, UsCensusBureauConstants.Provider)
{

}
}
}
19 changes: 19 additions & 0 deletions src/Geocoding.UsCensusBureau/UsCensusBureauConstants.cs
Original file line number Diff line number Diff line change
@@ -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";
}
}
98 changes: 98 additions & 0 deletions src/Geocoding.UsCensusBureau/UsCensusBureauGeocoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting in this file could be improved - Visual Studio autoformat should be sufficient.

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
{
/// <summary>
/// refs:
/// - https://geocoding.geo.census.gov/
/// - https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf
/// </summary>
public class UsCensusBureauGeocoder : IGeocoder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding an IBatchGeocoder, as this looks to be supported by the API.

{
private readonly int _benchmark;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API defines benchmark as a string, and the example use, for example, "Public_AR_Census2020". Should this be a string? It might be worth referencing https://geocoding.geo.census.gov/geocoder/benchmarks for the list of available benchmarks.

private readonly string _format;
private readonly HttpClient _client;

public UsCensusBureauGeocoder(int benchmark = 4, string format = "json")
Copy link
Contributor

@RodneyRichardson RodneyRichardson Mar 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the magic number "4" (benchmark)? Can this be made into a (string) constant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the format parameter - the code only supports JSON, so hardcode it.

{
_benchmark = benchmark;
_format = format;
_client = new HttpClient { BaseAddress = new Uri(UsCensusBureauConstants.BaseUrl) };
}

public async Task<IEnumerable<Address>> 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<IEnumerable<Address>> 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work if any parameters are omitted? Perhaps add a test for partial 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 Task<IEnumerable<Address>> ReverseGeocodeAsync(Location location, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotSupportedException();
}

public Task<IEnumerable<Address>> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotSupportedException();
}

private static IEnumerable<UsCensusBureauAddress> GetAddresses(string response)
{
var json = JObject.Parse(response);

var errors = json[UsCensusBureauConstants.ErrorsKey];
if (errors != null)
return new UsCensusBureauAddress[] {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't swallow errors. Create and throw a UsCensusBureauGeocodingException (wow, that's a long name - how about Ucb?) to wrap and expose the error.


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();
}
}
}
9 changes: 3 additions & 6 deletions test/Geocoding.Tests/GeocoderTest.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -106,4 +103,4 @@ public virtual async Task CanGeocodeInvalidZipCodes(string address)
Assert.NotEmpty(addresses);
}
}
}
}
1 change: 1 addition & 0 deletions test/Geocoding.Tests/Geocoding.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ProjectReference Include="..\..\src\Geocoding.Here\Geocoding.Here.csproj" />
<ProjectReference Include="..\..\src\Geocoding.MapQuest\Geocoding.MapQuest.csproj" />
<ProjectReference Include="..\..\src\Geocoding.Microsoft\Geocoding.Microsoft.csproj" />
<ProjectReference Include="..\..\src\Geocoding.UsCensusBureau\Geocoding.UsCensusBureau.csproj" />
<ProjectReference Include="..\..\src\Geocoding.Yahoo\Geocoding.Yahoo.csproj" />
</ItemGroup>

Expand Down
67 changes: 67 additions & 0 deletions test/Geocoding.Tests/UsCensusBureauTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Geocoding.UsCensusBureau;
using Xunit;

namespace Geocoding.Tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't skip tests. Assert that they behave as expected (such as throwing NotSupportedException, or returning/throwing an appropriate error).

{
[Collection("Settings")]
public class UsCensusBureauTest : GeocoderTest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests for other "benchmark" values.

{
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;
}
}
}