Skip to content

Add CodeArea validity, better error-checking in Decode(). #185

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
16 changes: 16 additions & 0 deletions cpp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ cc_test(
testonly = True,
)

# Unit test for code area library
cc_test(
name = "codearea_test",
size = "small",
srcs = ["codearea_test.cc"],
copts = [
"-pthread",
"-Iexternal/gtest/include",
],
linkopts = ["-pthread"],
deps = [
":codearea",
"@gtest//:main",
],
)

# Example binary for open location codes
cc_binary(
name = "openlocationcode_example",
Expand Down
12 changes: 12 additions & 0 deletions cpp/codearea.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace openlocationcode {

namespace {
const double kLatitudeMaxDegrees = 90;
const double kLongitudeMaxDegrees = 180;
const CodeArea kInvalidCodeArea(0.0, 0.0, 0.0, 0.0, 0);
} // anonymous namespace

CodeArea::CodeArea(double latitude_lo, double longitude_lo, double latitude_hi,
double longitude_hi, size_t code_length) {
Expand Down Expand Up @@ -36,4 +39,13 @@ LatLng CodeArea::GetCenter() const {
return center;
}

bool CodeArea::IsValid() const {
return code_length_ > 0 && latitude_lo_ < latitude_hi_ &&
longitude_lo_ < longitude_hi_ && latitude_lo_ >= -90.0 &&
latitude_hi_ <= 90.0 && longitude_lo_ >= -180.0 &&
longitude_hi_ <= 180.0;
}

const CodeArea& CodeArea::InvalidCodeArea() { return kInvalidCodeArea; }

} // namespace openlocationcode
4 changes: 4 additions & 0 deletions cpp/codearea.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class CodeArea {
double GetLongitudeHi() const;
size_t GetCodeLength() const;
LatLng GetCenter() const;
// Returns whether or not this area was a valid decode result.
bool IsValid() const;
// Returns an invalid CodeArea object.
static const CodeArea& InvalidCodeArea();

private:
double latitude_lo_;
Expand Down
59 changes: 59 additions & 0 deletions cpp/codearea_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "codearea.h"
#include "gtest/gtest.h"

namespace openlocationcode {
namespace {

TEST(CodeAreaChecks, Accessors) {
const CodeArea area(1.0, 2.0, 3.0, 4.0, 6);
// Check accessor methods return what we expect.
EXPECT_EQ(area.GetLatitudeLo(), 1.0);
EXPECT_EQ(area.GetLongitudeLo(), 2.0);
EXPECT_EQ(area.GetLatitudeHi(), 3.0);
EXPECT_EQ(area.GetLongitudeHi(), 4.0);
EXPECT_EQ(area.GetCodeLength(), 6);
}

TEST(CodeAreaChecks, GetCenter) {
// Simple case.
const CodeArea area1(0.0, 0.0, 1.0, 2.0, 8);
EXPECT_EQ(area1.GetCenter().latitude, 0.5);
EXPECT_EQ(area1.GetCenter().longitude, 1.0);
// Negative latitudes & longitudes.
const CodeArea area2(-10.0, -30.0, -24.0, -84.0, 4);
EXPECT_EQ(area2.GetCenter().latitude, -17.0);
EXPECT_EQ(area2.GetCenter().longitude, -57.0);
// Latitude & longitude ranges crossing zero.
const CodeArea area3(-30.0, -17.0, 5.0, 21.0, 4);
EXPECT_EQ(area3.GetCenter().latitude, -12.5);
EXPECT_EQ(area3.GetCenter().longitude, 2.0);
// Zero-sized area (not strictly valid, but center is still well-defined).
const CodeArea area4(-65.0, 117.0, -65.0, 117.0, 2);
EXPECT_EQ(area4.GetCenter().latitude, -65.0);
EXPECT_EQ(area4.GetCenter().longitude, 117.0);
}

TEST(CodeAreaChecks, IsValid) {
// All zeroes: invalid.
EXPECT_FALSE(CodeArea(0.0, 0.0, 0.0, 0.0, 0).IsValid());
// Whole-world area: valid.
EXPECT_TRUE(CodeArea(-90.0, -180.0, 90.0, 180.0, 1).IsValid());
// Typical area: valid.
EXPECT_TRUE(CodeArea(-1.0, -1.0, 1.0, 1.0, 10).IsValid());
// Zero code length: invalid.
EXPECT_FALSE(CodeArea(-1.0, -1.0, 1.0, 1.0, 0).IsValid());
// Low latitude >= high latitude: invalid.
EXPECT_FALSE(CodeArea(1.0, -1.0, 1.0, 1.0, 10).IsValid());
EXPECT_FALSE(CodeArea(2.0, -1.0, 1.0, 1.0, 10).IsValid());
// Low longitude >= high longitude: invalid.
EXPECT_FALSE(CodeArea(-1.0, 1.0, 1.0, 1.0, 10).IsValid());
EXPECT_FALSE(CodeArea(-1.0, 2.0, 1.0, 1.0, 10).IsValid());
}

TEST(CodeAreaChecks, InvalidCodeArea) {
// CodeArea::InvalideCodeArea() must return an invalid code area, obviously.
EXPECT_FALSE(CodeArea::InvalidCodeArea().IsValid());
}

} // namespace
} // namespace openlocationcode
12 changes: 10 additions & 2 deletions cpp/openlocationcode.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,13 @@ CodeArea Decode(const std::string &code) {
// Define the place value for the most significant pair.
int pv = pow(internal::kEncodingBase, internal::kPairCodeLength / 2 - 1);
for (size_t i = 0; i < digits - 1; i += 2) {
normal_lat += get_alphabet_position(clean_code[i]) * pv;
normal_lng += get_alphabet_position(clean_code[i + 1]) * pv;
const int lat_dval = get_alphabet_position(clean_code[i]);
const int lng_dval = get_alphabet_position(clean_code[i + 1]);
if (lat_dval < 0 || lng_dval < 0) {
return CodeArea::InvalidCodeArea();
}
normal_lat += lat_dval * pv;
normal_lng += lng_dval * pv;
if (i < digits - 2) {
pv /= internal::kEncodingBase;
}
Expand All @@ -227,6 +232,9 @@ CodeArea Decode(const std::string &code) {
digits = std::min(internal::kMaximumDigitCount, clean_code.size());
for (size_t i = internal::kPairCodeLength; i < digits; i++) {
int dval = get_alphabet_position(clean_code[i]);
if (dval < 0) {
return CodeArea::InvalidCodeArea();
}
int row = dval / internal::kGridColumns;
int col = dval % internal::kGridColumns;
extra_lat += row * row_pv;
Expand Down
9 changes: 9 additions & 0 deletions cpp/openlocationcode_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ TEST_P(EncodingChecks, Encode) {
INSTANTIATE_TEST_CASE_P(OLC_Tests, EncodingChecks,
::testing::ValuesIn(GetEncodingDataFromCsv()));

TEST(DecodingChecks, DecodeOutOfRange) {
// Leading longitude digit "W" is out of range.
EXPECT_FALSE(Decode(std::string("9W4Q0000+")).IsValid());
// Leading latitude digit "F" is out of range.
EXPECT_FALSE(Decode(std::string("F2+")).IsValid());
// Invalid digits in post-separator portion.
EXPECT_FALSE(Decode(std::string("7Q7Q7Q7Q+5Z")).IsValid());
}

struct ValidityTestData {
std::string code;
bool is_valid;
Expand Down
Loading