Skip to content
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