[search] Refactor MatchLatLonDegree function #3301

Open
trueTatar wants to merge 1 commit from trueTatar/latlon-refactoring into master
2 changed files with 705 additions and 296 deletions

View file

@ -1,278 +1,590 @@
#include "search/latlon_match.hpp"
#include "base/macros.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <optional>
#include <list>
#include <array>
#include <cstring>
#include <functional>
using namespace std;
namespace
{
string const kSpaces = " \t";
string const kCharsToSkip = " \n\t,;:.()";
string const kDecimalMarks = ".,";
bool IsDecimalMark(char c)
{
return kDecimalMarks.find(c) != string::npos;
}
template <typename Char>
void SkipSpaces(Char *& s)
{
while (kSpaces.find(*s) != string::npos)
++s;
}
template <typename Char>
void Skip(Char *& s)
{
while (kCharsToSkip.find(*s) != string::npos)
++s;
}
bool MatchDMSArray(char const * & s, char const * arr[], size_t count)
{
for (size_t i = 0; i < count; ++i)
namespace {
// The Check class is an auxiliary class, the purpose of which is to perform
// various kinds of checks during the operation of the module.
class Check {
public:
static std::size_t IsDegree(char const * pos)
{
size_t const len = strlen(arr[i]);
if (strncmp(s, arr[i], len) == 0)
return CheckSymbol(m_degrees, pos);
}
static std::size_t IsMinute(char const * pos)
{
return CheckSymbol(m_minutes, pos);
}
static std::size_t IsSecond(char const * pos)
{
return CheckSymbol(m_seconds, pos);
}
// The IsCardDir function checks whether the character, pointed to by the |pos|,
// is symbol of cardinal direction. If so, the function returns true,
// otherwise false.
static bool IsCardDir(char const * pos)
{
return m_cardinalDirections.find(*pos) != std::string_view::npos;
}
// The IsNegative function checks whether the character, pointed to by the |pos|,
// is the beginning of a negative number. If so, the function returns true,
// otherwise false.
static bool IsNegative(char const * pos)
{
return *pos == '-' && std::isdigit(*(pos + 1));
}
// The IsInteger function checks whether the |value| has a fractional part.
// If so, the function returns false, otherwise true.
static bool IsInteger(double value)
{
return value == static_cast<int>(value);
}
// The Latitude function checks whether the |lat| variable meets the latitude
// requirements. If so, the function returns true, otherwise false.
static bool Latitude(double lat)
{
return (lat < -90.0 || lat > 90.0) ? false : true;
}
// The Longitude function checks whether the |lon| variable meets the longitude
// requirements. If so, the function returns true, otherwise false.
static bool Longitude(double lon)
{
return (lon < -180.0 || lon > 180.0) ? false : true;
}
private:
template<std::size_t Size>
using Array = std::array<char const *, Size>;
// The CheckSymbol function checks whether character, pointed to by |pos|, belongs
// to the set of characters, contained in the array |symbols|. The function returns
// the size of the character, if it is present, otherwise zero.
template<std::size_t Size>
static std::size_t CheckSymbol(Array<Size> const & symbols, char const * pos)
{
for (char const * symb : symbols)
{
s += len;
size_t const len = strlen(symb);
if (strncmp(pos, symb, len) == 0)
return len;
}
return 0;
}
static constexpr Array<2> m_degrees = { "*", "°" };
static constexpr Array<3> m_minutes = { "\'", "", "" };
static constexpr Array<6> m_seconds = { "\"", "", "", "\'\'", "", "" };
static constexpr std::string_view m_cardinalDirections = "NnSsEeWw";
};
enum class Index
{
kDegree,
kMinute,
kSecond,
kCardDir
};
// The CoordinatesFormat class is an abstract class that provides an interface for
// interacting with its successors as well as functionality common to them. In order
// to be successfully integrated, each new coordinate format must inherit this class.
class CoordinatesFormat {
using CoordinatePair = std::optional<std::pair<double, double>>;
public:
CoordinatesFormat() : m_start(nullptr) { }
virtual ~CoordinatesFormat() = default;
// The SetStart function sets the starting point in the query for coordinate parser.
void SetStart(char const * start)
{
m_start = start;
}
// The InterpretNumber function interprets a floating point value in a byte string
// pointed to by |start|. The function returns two values: floating point value,
// corresponding to the contents of |start| and the number of characters that was
// interpreted.
std::pair<double, std::size_t> InterpretNumber(char const * const start)
{
char * end;
double number = std::strtod(start, &end);
if (*end == ',' && std::isdigit(*(end + 1)))
{
char* another_start = ++end;
double fractional_part = std::strtod(another_start, &end);
while (fractional_part >= 1)
fractional_part /= 10;
number += number > 0 ? fractional_part : -fractional_part;
}
return {number, end - start};
}
// The Parse function is pure virtual method, whose implementation in subclasses
// performs query analysis.
virtual void Parse() = 0;
// The Format function is pure virtual method, whose implementation in subclasses
// checks whether it is possible to analyze the byte string pointed to by the |pos|.
// The function returns true if the byte string pointed to by the |pos| corresponds
// to the characteristic features of the coordinate format, otherwise false.
virtual bool Format(char const * pos) = 0;
// The Results The function returns the results of the analysis in case of its
// successful completion, otherwise uninitialized std::optional object.
virtual CoordinatePair Results() = 0;
protected:
CoordinatePair Results(std::optional<double> lat, std::optional<double> lon)
{
if (lat && lon && Check::Latitude(lat.value()) && Check::Longitude(lon.value()))
return std::make_pair(lat.value(), lon.value());
else
return std::nullopt;
}
char const * StartPos()
{
return m_start;
}
private:
// The |m_start| field holds the position in the query with starting from which
// the analysis is performed.
char const * m_start;
};
// The DDFormat class provides functionality for parsing coordinates in Decimal
// Degrees (DD) format. The implemented format has the following form:
//
// dd, DD
//
// -> dd is a latitude in a range [-90,90] (real number);
// -> DD is a longitude in a range [-180,180] or [0,360] (real number).
// The longitude ranges are correlated as follows:
// [ 0, 360]: 180, 181, ..., 359, 360/0, 1, ..., 179, 180
// [-180, 180]: -180, -179, ..., -1, 0, 1, ..., 179, 180
class DDFormat : public CoordinatesFormat {
public:
DDFormat() : m_lat(std::nullopt), m_lon(std::nullopt), m_in_process(&m_lat) { }
void Parse() override
{
for (auto iter = StartPos(); *iter != '\0';)
{
std::size_t shift = 1;
if (m_in_process)
{
if (std::isdigit(*iter) || Check::IsNegative(iter))
std::tie(*m_in_process, shift) = InterpretNumber(iter);
else if (*iter == ' ' || *iter == ',')
m_in_process = m_lon ? nullptr : &m_lon;
else if (std::size_t size = Check::IsDegree(iter); size != 0)
shift = size;
else if (!m_lat || !m_lon || Check::IsCardDir(iter) || Check::IsMinute(iter) || Check::IsSecond(iter))
return Reset();
} else if (std::isdigit(*iter))
return Reset();
iter += shift;
}
if (m_lon > 180.0)
m_lon.value() -= 360.0;
}
std::optional<std::pair<double, double>> Results() override
{
return CoordinatesFormat::Results(m_lat, m_lon);
}
bool Format(char const * pos) override
{
return Check::IsNegative(pos) || std::isdigit(*pos);
}
private:
void Reset()
{
std::tie(m_lat, m_lon) = std::tie(std::nullopt, std::nullopt);
}
std::optional<double> m_lat; // The |m_lat| field holds the latitude value during parsing.
std::optional<double> m_lon; // The |m_lon| field holds the longitude value dirung parsing.
std::optional<double> * m_in_process; // The |m_in_process| points to a coordinate that is
// in the process of parsing.
};
// The AbstractDmsDdm class is the abstract base class, providing functionality common to
// parsers of Degrees, Minutes, Seconds (DMS) and Degrees, Decimal Minutes (DDM) coordinate
// formates.
class AbstractDmsDdm : public CoordinatesFormat {
protected:
// The Component class represents a separate component from which the DMS or DDM notations
// are built.
class Component {
public:
Component(Index index, int ratio, std::function<bool(double)>&& check)
: m_index(index), m_ratio(ratio), m_check(check) { }
virtual ~Component() = default;
bool HasValue() const
{
return m_value.has_value();
}
bool SetValue(double value)
{
if (!m_check(value))
return false;
m_value = std::make_optional(value);
return true;
}
double Compute() const
{
return m_value.value() / m_ratio;
}
bool operator==(Index const index) const
{
return index == m_index;
}
private:
// The |m_index| field holds the identifier of the component that is used to address
// this component during runtime.
Index const m_index;
// The |m_value| field holds the value of the component.
std::optional<double> m_value;
// The |m_ratio| field holds the value of the multiplier that is used in the calculation
// of the coordinate.
int const m_ratio;
// The |m_check| field holds a functor which checks whether component corresponds to
// a specific notation. If so, the functor returns true, otherwise false.
std::function<bool(double)> const m_check;
};
private:
// The Coordinate class builds individual instances of the Component class into
// a structure typical of DMS or DDM notation.
class Coordinate {
using CompPtr = std::unique_ptr<Component>;
using CompList = std::list<CompPtr>;
public:
template<typename...Ts>
Coordinate(Ts... ts)
{
(m_list.push_back(std::make_unique<Ts>(ts)), ...);
}
template<typename T>
bool SetValue(T value, Index index)
{
if constexpr (std::is_floating_point_v<T>) {
if (auto iter = Find(index); iter != m_list.end())
{
Complete(iter);
return (*iter)->SetValue(value);
}
} else
card_dir = std::make_optional(value);
return true;
}
// The HasValue function checks whether a component with the index |index| contains
// value. The function returns true if the component contains a value, otherwise
// false.
bool HasValue(Index index)
{
if (auto iter = Find(index); iter != m_list.end())
return (*iter)->HasValue();
return card_dir.has_value();
}
bool IsLatitude() const
{
return card_dir == 'N' || card_dir == 'S';
}
bool IsLongitude() const
{
return card_dir == 'W' || card_dir == 'E';
}
// The Calculate function, basing on the information obtained during the parsing,
// translates the coordinate into Decimal degrees format. The result is recorded
// in the |m_result| field.
void Calculate()
{
for (auto& ptr : m_list) {
if (ptr->HasValue())
{
if (m_result)
m_result.value() += m_result < 0 ? -ptr->Compute() : ptr->Compute();
else
m_result = ptr->Compute();
}
}
if (m_result && (card_dir == 'S' || card_dir == 'W'))
m_result.value() = -m_result.value();
}
std::optional<double> Result() const
{
return m_result;
}
// The |card_dir| field holds a symbol indicating the cardinal directions.
std::optional<char> card_dir;
private:
// The Complete function verifies that the components preceding the |comp|
// have a value. If the previous component does not have a value, then it is
// assigned zero. The function stops when a component that has a value is
// reached or if the first component of the coordinate is reached.
void Complete(CompList::iterator comp)
{
if (comp != m_list.begin())
{
for (auto iter = std::prev(comp); !(*iter)->HasValue();)
{
(*iter)->SetValue(0);
if (iter != m_list.begin())
--iter;
}
}
}
// The Find function searches for a coordinate component with an index |index|.
// If a component with the specified index is found, the function returns
// an iterator to it, otherwise an iterator, pointing to the end of the |m_list|.
CompList::iterator Find(Index const index)
{
auto predicate = [index] (CompPtr const & c) { return *c == index; };
return std::find_if(m_list.begin(), m_list.end(), std::move(predicate));
}
// The |m_list| field holds instances of successors of the Component class.
std::list<std::unique_ptr<Component>> m_list;
std::optional<double> m_result;
};
public:
template<typename... Ts>
AbstractDmsDdm(Ts... components) : m_number(0), m_first(Coordinate(components...)),
m_second(Coordinate(components...)), m_inProcess(&m_first) { }
void Parse() override
{
char const * iter = StartPos();
while (*iter != '\0') {
std::size_t shift = 1;
if (std::isdigit(*iter) || Check::IsNegative(iter))
std::tie(m_number, shift) = InterpretNumber(iter);
else if (*iter == ',' && *(iter + 1) == ' ' && m_inProcess == &m_first)
m_inProcess = &m_second;
else if ((*iter == ' ' || *iter == ',') && m_number)
return;
else if (Check::IsCardDir(iter)) {
if (!AssignValue(std::toupper(*iter), Index::kCardDir))
return;
} else if (auto is_format = FormatCheck(iter); is_format) {
Index index;
std::tie(shift, index) = is_format.value();
if (!AssignValue(m_number, index))
return;
} else if (Interrupt(iter))
return;
iter += shift;
}
if (m_number != 0)
return;
Process();
}
bool Format(char const * pos) override
{
return Check::IsCardDir(pos) || Check::IsNegative(pos) || std::isdigit(*pos);
}
std::optional<std::pair<double, double>> Results() override
{
return CoordinatesFormat::Results(m_first.Result(), m_second.Result());
}
void Process()
{
if (!m_first.card_dir && !m_second.card_dir) {
m_first.card_dir = 'N';
m_second.card_dir = 'E';
}
if (m_first.card_dir && m_second.card_dir) {
if (m_first.IsLongitude() && m_second.IsLatitude())
std::swap(m_first, m_second);
if (m_first.IsLatitude() && m_second.IsLongitude()) {
m_first.Calculate();
m_second.Calculate();
}
}
}
private:
// The AssignValue function template assigns value |value| to the component |index|
// of the coordinate. The function returns true if the value was assigned successfully,
// otherwise false.
template<typename T>
bool AssignValue(T value, Index index)
{
if (m_inProcess->HasValue(index))
{
if (m_inProcess == &m_first)
m_inProcess = &m_second;
else if (m_inProcess == &m_second) {
if constexpr (std::is_integral_v<T>)
return true;
return false;
}
}
if (!m_inProcess->SetValue(value, index))
return false;
if constexpr (std::is_floating_point_v<T>)
m_number = 0;
return true;
}
// The Interrupt function defines the conditions (if any) under which the parsing process
// should be terminated. Parsing stops if the function returns true, otherwise the operation
// continues.
virtual bool Interrupt(char const * pos) = 0;
// The FormatCheck function checks whether the character in the byte string pointed to by
// the |pos| corresponds to characters of the DMS or DDM notations. The function returns
// an uninitialized std::optional object if the symbol does not correspond to characters
// of the DMS or DDM notations, otherwise the function returns the size of the symbol and
// its index.
virtual std::optional<std::pair<std::size_t, Index>> FormatCheck(char const * pos) = 0;
// The |m_number| field holds the value of the coordinate component that is being processed.
double m_number;
// The |m_first| and |m_second| fields hold the information that is obtained during parsing.
Coordinate m_first;
Coordinate m_second;
Coordinate* m_inProcess; // The |m_in_process| field holds an address of the Coordinate
// object, to which the information is written.
};
// The DMSFormat class provides functionality for parsing coordinates in Degrees, Minutes,
// Seconds (DMS) format. The DMS format has the following form:
//
// dd* MM' SS" N, DD* MM' SS" E
//
// -> dd is in a range [0,90] and DD is in a range [0,180] (both whole numbers);
// -> MM is in a range [0,59] (whole number);
// -> SS is in a range [0,60) (real number).
class DMSFormat : public AbstractDmsDdm {
struct Degree : Component {
Degree() : Component(Index::kDegree, 1, [] (double v) { return v >= 0 && v <= 180 && Check::IsInteger(v); }) { }
};
struct Minute : Component {
Minute() : Component(Index::kMinute, 60, [] (double v) { return v >= 0 && v <= 59 && Check::IsInteger(v); }) { }
};
struct Second : Component {
Second() : Component(Index::kSecond, 3600, [] (double v) { return v >= 0 && v < 60; }) { }
};
public:
DMSFormat() : AbstractDmsDdm(Degree(), Minute(), Second()) { }
private:
bool Interrupt(char const *) override
{
return false;
}
std::optional<std::pair<std::size_t, Index>> FormatCheck(char const * pos) override
{
if (std::size_t length = Check::IsDegree(pos); length)
return std::make_pair(length, Index::kDegree);
else if (std::size_t length = Check::IsSecond(pos); length)
return std::make_pair(length, Index::kSecond);
else if (std::size_t length = Check::IsMinute(pos); length)
return std::make_pair(length, Index::kMinute);
else
return std::nullopt;
}
};
// The DDMFormat class provides functionality for parsing coordinates in Degrees, Decimal
// Minutes (DMM) format. The DMM format has the following form:
//
// dd* MM' N, DD* MM' E
//
// -> dd is in a range [0,90] and DD is in a range [0,180] (both whole numbers);
// -> MM is in a range [0,60) (real number).
class DDMFormat : public AbstractDmsDdm {
struct Degree : Component {
Degree() : Component(Index::kDegree, 1, [] (double v) { return v >= 0 && v <= 180 && Check::IsInteger(v); }) { }
};
struct Minute : Component {
Minute() : Component(Index::kMinute, 60, [] (double v) { return v >= 0 && v <= 60; }) { }
};
public:
DDMFormat() : AbstractDmsDdm(Degree(), Minute()) { }
private:
bool Interrupt(char const * pos) override
{
return Check::IsSecond(pos);
}
std::optional<std::pair<std::size_t, Index>> FormatCheck(char const * pos) override
{
if (std::size_t length = Check::IsDegree(pos); length)
return std::make_pair(length, Index::kDegree);
else if (std::size_t length = Check::IsMinute(pos); length)
return std::make_pair(length, Index::kMinute);
else
return std::nullopt;
}
};
// The Parser class selects the most appropriate coordinate format and parses the query
// according to it. A class, extending the list of supported coordinate formats, should
// be inherited from the CoordinateFormat class and added as an argument to the invocation
// of CreateList funtion template.
class Parser {
using FormatPtr = std::unique_ptr<CoordinatesFormat>;
using FormatsList = std::list<FormatPtr>;
template<typename... Fs>
FormatsList CreateList()
{
FormatsList lst;
(lst.push_back(std::make_unique<Fs>()), ...);
return lst;
}
public:
Parser(std::string const & query) : m_pos(query.c_str()), m_parser(nullptr),
m_formats(CreateList<DDFormat, DMSFormat, DDMFormat>()) { }
bool AssumeFormat()
{
if (m_parser && m_parser->Results())
return false;
while (!m_formats.empty() && IsAllowed(m_pos))
{
for (auto iter = m_formats.begin(); iter != m_formats.end(); ++iter)
{
if ((*iter)->Format(m_pos))
{
m_parser = std::move(*iter);
m_parser->SetStart(m_pos);
m_formats.erase(iter);
return true;
}
}
++m_pos;
}
return false;
}
void Run()
{
m_parser->Parse();
}
std::optional<std::pair<double, double>> Results() const
{
return m_parser ? m_parser->Results() : std::nullopt;
}
private:
// The IsAllowed function checks whether the character pointed to by the |pos|
// can be part of the coordinate. If so, the function returns true, otherwise false.
static bool IsAllowed(char const * pos)
{
return std::ispunct(*pos) || std::isspace(*pos) || std::isdigit(*pos) || Check::IsCardDir(pos);
}
// The |m_pos| field holds an address of the beginning of the chunk of the query
// that should be parsed.
char const * m_pos;
// The |m_parser| field holds a pointer to the coordinate parser, using at the moment.
FormatPtr m_parser;
// The variable holds a list of coordinate parsers, using for the query processing.
FormatsList m_formats;
};
std::optional<std::pair<double, double>> MatchLatLonDegree(std::string const & query)
{
Parser parser(query);
while (parser.AssumeFormat())
parser.Run();
return parser.Results();
}
} // end of unnamed namespace
namespace search {
bool MatchLatLonDegree(std::string const & query, double & lat, double & lon)
{
if (auto success = ::MatchLatLonDegree(query); success)
{
std::tie(lat, lon) = success.value();
return true;
}
return false;
}
int GetDMSIndex(char const * & s)
{
char const * arrDegree[] = { "*", "°" };
char const * arrMinutes[] = { "\'", "", "" };
char const * arrSeconds[] = { "\"", "", "", "\'\'", "", "" };
if (MatchDMSArray(s, arrDegree, ARRAY_SIZE(arrDegree)))
return 0;
if (MatchDMSArray(s, arrSeconds, ARRAY_SIZE(arrSeconds)))
return 2;
if (MatchDMSArray(s, arrMinutes, ARRAY_SIZE(arrMinutes)))
return 1;
return -1;
}
void SkipNSEW(char const * & s, char const * (&arrPos) [4])
{
Skip(s);
int ind;
switch (*s)
{
case 'N': case 'n': ind = 0; break;
case 'S': case 's': ind = 1; break;
case 'E': case 'e': ind = 2; break;
case 'W': case 'w': ind = 3; break;
default: return;
}
arrPos[ind] = s++;
Skip(s);
}
// Attempts to read a double from the start of |str|
// in one of what we assume are two most common forms
// for lat/lon: decimal digits separated either
// by a dot or by a comma, with digits on both sides
// of the separator.
// If the attempt fails, falls back to std::strtod.
double EatDouble(char const * str, char ** strEnd)
{
bool gotDigitBeforeMark = false;
bool gotMark = false;
bool gotDigitAfterMark = false;
char const * markPos = nullptr;
char const * p = str;
while (true)
{
if (IsDecimalMark(*p))
{
if (gotMark)
break;
gotMark = true;
markPos = p;
}
else if (isdigit(*p))
{
if (gotMark)
gotDigitAfterMark = true;
else
gotDigitBeforeMark = true;
}
else
{
break;
}
++p;
}
if (gotDigitBeforeMark && gotMark && gotDigitAfterMark)
{
string const part1(str, markPos);
string const part2(markPos + 1, p);
*strEnd = const_cast<char *>(p);
auto const x1 = atof(part1.c_str());
auto const x2 = atof(part2.c_str());
return x1 + x2 * pow(10.0, -static_cast<double>(part2.size()));
}
return strtod(str, strEnd);
}
} // namespace
namespace search
{
bool MatchLatLonDegree(string const & query, double & lat, double & lon)
{
// should be default initialization (0, false)
array<pair<double, bool>, 6> v;
int base = 0;
// Positions of N, S, E, W symbols
char const * arrPos[] = {nullptr, nullptr, nullptr, nullptr};
bool arrDegreeSymbol[] = { false, false };
char const * const startQuery = query.c_str();
char const * s = startQuery;
while (true)
{
char const * s1 = s;
SkipNSEW(s, arrPos);
if (!*s)
{
// End of the string - check matching.
break;
}
SkipSpaces(s);
char * s2;
double const x = EatDouble(s, &s2);
if (s == s2)
{
// invalid token
if (s == s1)
{
// Return error if there are no any delimiters.
return false;
}
else
{
// Check matching if token is delimited.
break;
}
}
else if (x < 0 && s == s1 && !(s == startQuery || kSpaces.find(*(s-1)) != string::npos))
{
// Skip input like "3-8"
return false;
}
s = s2;
SkipSpaces(s);
int i = GetDMSIndex(s);
bool degreeSymbol = true;
if (i == -1)
{
// try to assign next possible value mark
if (arrDegreeSymbol[base / 3])
{
if (!v[base + 1].second)
i = 1;
else
i = 2;
}
else
{
i = 0;
degreeSymbol = false;
}
}
if (i == 0) // degrees
{
if (v[base].second)
{
if (base == 0)
{
base += 3;
}
else
{
// too many degree values
return false;
}
}
arrDegreeSymbol[base / 3] = degreeSymbol;
}
else // minutes or seconds
{
if (x < 0.0 || x > 60.0 || // minutes or seconds should be in [0, 60] range
v[base + i].second || // value already exists
!v[base].second || // no degrees found for value
(i == 2 && !v[base + 1].second)) // no minutes for seconds
{
return false;
}
}
v[base + i].first = x;
v[base + i].second = true;
}
if (!v[0].second || !v[3].second)
{
// degree should exist for both coordinates
return false;
}
if ((arrPos[0] && arrPos[1]) || (arrPos[2] && arrPos[3]))
{
// control symbols should match only once
return false;
}
// Calculate Lat, Lon with correct sign.
lat = fabs(v[0].first) + v[1].first / 60.0 + v[2].first / 3600.0;
if (v[0].first < 0.0) lat = -lat;
lon = fabs(v[3].first) + v[4].first / 60.0 + v[5].first / 3600.0;
if (v[3].first < 0.0) lon = -lon;
if (max(arrPos[0], arrPos[1]) > max(arrPos[2], arrPos[3]))
swap(lat, lon);
if (arrPos[1] != nullptr)
lat = -lat;
if (arrPos[3] != nullptr)
lon = -lon;
// Valid input ranges for longitude are: [0, 360] or [-180, 180].
// We normalize it to [-180, 180].
if (lon < -180.0 || lon > 360.0)
return false;
if (lon > 180.0)
lon -= 360.0;
return fabs(lat) <= 90.0;
}
} // namespace search

View file

@ -60,17 +60,10 @@ UNIT_TEST(LatLon_Degree_Match)
TEST(!MatchLatLonDegree("34/31", lat, lon), ());
TEST(!MatchLatLonDegree("34,31", lat, lon), ());
/// @todo 5E-5 eats as full double here. This is a very fancy case, but anyway ...
TEST(!MatchLatLonDegree("N5E-5", lat, lon), ());
TEST(!MatchLatLonDegree("5E-5", lat, lon), ());
TEST(MatchLatLonDegree("N5W-5", lat, lon), ());
TestAlmostEqual(lat, 5);
TestAlmostEqual(lon, 5);
// Same as "N5 E-5"
TEST(MatchLatLonDegree("5 E-5", lat, lon), ());
TestAlmostEqual(lat, 5);
TestAlmostEqual(lon, -5);
TEST(!MatchLatLonDegree("N5W-5", lat, lon), ());
TEST(!MatchLatLonDegree("5 E-5", lat, lon), ());
TEST(!MatchLatLonDegree("., .", lat, lon), ());
TEST(!MatchLatLonDegree("10, .", lat, lon), ());
@ -85,33 +78,83 @@ UNIT_TEST(LatLon_Degree_Match)
TEST(!MatchLatLonDegree("50* 40*, 30*", lat, lon), ());
TEST(MatchLatLonDegree("(-50°30\'30\" -49°59\'59\"", lat, lon), ());
TEST(MatchLatLonDegree("(S50°30\'30\" W49°59\'59\"", lat, lon), ());
TestAlmostEqual(lat, -50.50833333333333);
TestAlmostEqual(lon, -49.99972222222222);
TEST(!MatchLatLonDegree("50°, 30\"", lat, lon), ());
TEST(!MatchLatLonDegree("50\', -50°", lat, lon), ());
TEST(MatchLatLonDegree("60* 0\' 0\" N, 0* 0\' 30\" E", lat, lon), ()); // full form, case #1
TestAlmostEqual(lat, 60);
TestAlmostEqual(lon, 0.0083333333333333332);
TEST(MatchLatLonDegree("60°, 30\"", lat, lon), ()); // short form, case #1
TestAlmostEqual(lat, 60);
TestAlmostEqual(lon, 0.0083333333333333332);
TEST(MatchLatLonDegree("0* 50\' 0\" N, 50° 0\' 0\" E", lat, lon), ()); // full form, case #2
TestAlmostEqual(lat, 0.83333333333333337);
TestAlmostEqual(lon, 50);
TEST(MatchLatLonDegree("50\', 50°", lat, lon), ()); // short form, case #2
TestAlmostEqual(lat, 0.83333333333333337);
TestAlmostEqual(lon, 50);
TEST(!MatchLatLonDegree("60° 30\"", lat, lon), ()); // if short form is ambiguous, latitude and longitude
// should be comma separated
TEST(!MatchLatLonDegree("-90*50\'50\", -50°", lat, lon), ());
TEST(MatchLatLonDegree("(-89*, 360*)", lat, lon), ());
TEST(MatchLatLonDegree("-10,12 -20,12", lat, lon), ());
TestAlmostEqual(lat, -10.12);
TestAlmostEqual(lon, -20.12);
TEST(MatchLatLonDegree("-89, 180.5555", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, -179.4445);
TEST(MatchLatLonDegree("-89, 181", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, -179.0);
TEST(MatchLatLonDegree("-89, 359", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, -1.0);
TEST(MatchLatLonDegree("-89, 359.5555", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, -0.4445);
TEST(MatchLatLonDegree("-89, 359.5555", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, -0.4445);
TEST(MatchLatLonDegree("-89, 360", lat, lon), ());
TestAlmostEqual(lat, -89.0);
TestAlmostEqual(lon, 0.0);
TEST(MatchLatLonDegree("-89*15.5\' N; 120*30\'50.5\" e", lat, lon), ());
TestAlmostEqual(lat, -89.25833333333333);
TestAlmostEqual(lon, 120.51402777777778);
// DMS edge cases
TEST(MatchLatLonDegree("N 89* 59\' 59.99990\", E 179* 59\' 59.99990\"", lat, lon), ());
TestAlmostEqual(lat, 89.9999999722222);
TestAlmostEqual(lon, 179.999999972222);
TEST(MatchLatLonDegree("N 89* 59\' 59.99999\", E 179* 59\' 59.99999\"", lat, lon), ());
TestAlmostEqual(lat, 89.9999999972222);
TestAlmostEqual(lon, 179.999999997222);
TEST(MatchLatLonDegree("N 90* 0\' 0\", E 180* 0\' 0\"", lat, lon), ());
TestAlmostEqual(lat, 90.0);
TestAlmostEqual(lon, 180.0);
// Fail if degree is negative and minute is floating point
TEST(!MatchLatLonDegree("-89*15.5\' N; 120*30\'50.5\" e", lat, lon), ());
TEST(MatchLatLonDegree("N55°4520.99″ E37°3703.62″", lat, lon), ());
TestAlmostEqual(lat, 55.755830555555556);
TestAlmostEqual(lon, 37.617672222222222);
{
TEST(MatchLatLonDegree("N-55°4520.99″ E-37°3703.62″", lat, lon), ());
double lat1, lon1;
TEST(MatchLatLonDegree("S55°4520.99″ W37°3703.62″", lat1, lon1), ());
TestAlmostEqual(lat, lat1);
TestAlmostEqual(lon, lon1);
}
TEST(MatchLatLonDegree("S55°4520.99″ W37°3703.62″", lat, lon), ());
TestAlmostEqual(lat, -55.755830555555556);
TestAlmostEqual(lon, -37.617672222222222);
TEST(MatchLatLonDegree("55°4520.9916\"N, 37°373.6228\"E hsdfjgkdsjbv", lat, lon), ());
TestAlmostEqual(lat, 55.755831);
@ -137,21 +180,75 @@ UNIT_TEST(LatLon_Degree_Match)
TEST(!MatchLatLonDegree("55°4520.9916″W 37°373.6228″E", lat, lon), ());
TEST(!MatchLatLonDegree("N55°4520.9916″ S37°373.6228″", lat, lon), ());
TEST(MatchLatLonDegree("54° 25' 0N 1° 53' 46W", lat, lon), ());
TestAlmostEqual(lat, 54.41666666666667);
TestAlmostEqual(lon, -1.89611111111111);
TEST(!MatchLatLonDegree("54° 25' 0N 1° 53' 46W", lat, lon), ()); // incomplete form
TEST(MatchLatLonDegree("47.33471°N 8.53112°E", lat, lon), ());
TEST(MatchLatLonDegree("47.33471° 8.53112°", lat, lon), ());
TestAlmostEqual(lat, 47.33471);
TestAlmostEqual(lon, 8.53112);
TEST(MatchLatLonDegree("N 51* 33.217 E 11* 10.113", lat, lon), ());
TestAlmostEqual(lat, 51.55361666666667);
TestAlmostEqual(lon, 11.16855);
TEST(MatchLatLonDegree("N 51* 0\' 33.217\" E 11* 0\' 10.113\"", lat, lon), ());
TestAlmostEqual(lat, 51.009226944444443);
TestAlmostEqual(lon, 11.002809166666667);
TEST(!MatchLatLonDegree("N 51* 33.217 E 11* 60.113", lat, lon), ());
TEST(!MatchLatLonDegree("N 51* -33.217 E 11* 10.113", lat, lon), ());
TEST(!MatchLatLonDegree("N 33.217\' E 11* 10.113", lat, lon), ());
TEST(!MatchLatLonDegree("N 51* 33.217 E 11* 10.113\"", lat, lon), ());
// Degrees Decimal Minutes (DDM) tests
TEST(MatchLatLonDegree("N 51* 33.217\' E 11* 10.113\'", lat, lon), ());
TestAlmostEqual(lat, 51.55361666666667);
TestAlmostEqual(lon, 11.16855);
TEST(MatchLatLonDegree("57* 53.9748\' N, 59* 56.8122\' E", lat, lon), ());
TestAlmostEqual(lat, 57.89958);
TestAlmostEqual(lon, 59.94687);
TEST(MatchLatLonDegree("N 57* 53.9748\', E 59* 56.8122\'", lat, lon), ());
TestAlmostEqual(lat, 57.89958);
TestAlmostEqual(lon, 59.94687);
TEST(MatchLatLonDegree("S 57* 53.9748\', E 59* 56.8122\'", lat, lon), ());
TestAlmostEqual(lat, -57.89958);
TestAlmostEqual(lon, 59.94687);
TEST(MatchLatLonDegree("N 57* 53.9748\', W 59* 56.8122\'", lat, lon), ());
TestAlmostEqual(lat, 57.89958);
TestAlmostEqual(lon, -59.94687);
// DDM edge cases
TEST(MatchLatLonDegree("N 89* 59,9999990\', E 179* 59,9999990\'", lat, lon), ());
TestAlmostEqual(lat, 89.999999983333);
TestAlmostEqual(lon, 179.999999983333);
TEST(MatchLatLonDegree("N 89* 59,9999999\', E 179* 59,9999999\'", lat, lon), ());
TestAlmostEqual(lat, 89.999999998333);
TestAlmostEqual(lon, 179.999999998333);
TEST(MatchLatLonDegree("N 90* 0\', E 180* 0\'", lat, lon), ());
TestAlmostEqual(lat, 90.0);
TestAlmostEqual(lon, 180.0);
// Fail if cardinal direction marks is same
TEST(!MatchLatLonDegree("57* 53\' 58.488\"N, 59* 56\' 48.732\"N", lat, lon), ());
TEST(!MatchLatLonDegree("57* 53\' 58.488\"S, 59* 56\' 48.732\"S", lat, lon), ());
TEST(!MatchLatLonDegree("57* 53\' 58.488\"E, 59* 56\' 48.732\"E", lat, lon), ());
TEST(!MatchLatLonDegree("57* 53\' 58.488\"W, 59* 56\' 48.732\"W", lat, lon), ());
// Fail if degree value is floating point (DMS and DDM notation)
TEST(!MatchLatLonDegree("57.123* 53\' 58.488\"N, 59* 56\' 48.732\"E", lat, lon), ());
TEST(!MatchLatLonDegree("57.123* 58.488\'N, 59* 48.732\'E", lat, lon), ());
// Fail if minute value is floating point (for DMS notation)
TEST(!MatchLatLonDegree("57* 53.123\' 58.488\"N, 59* 56.123\' 48.732\"E", lat, lon), ());
// Fail if minute value is greater than 59 (for DMS notation)
TEST(!MatchLatLonDegree("57* 60\' 58.488\" N, 59* 56\' 48.732\" E", lat, lon), ());
// Fail if degree value is greater than 180 (DMS and DDM notation)
TEST(!MatchLatLonDegree("N 89* 10\' 3\", E 183* 3\' 3\"", lat, lon), ());
TEST(!MatchLatLonDegree("N 89* 10\', E 183* 3\'", lat, lon), ());
}
} // namespace