Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
298
libs/search/latlon_match.cpp
Normal file
298
libs/search/latlon_match.cpp
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#include "search/latlon_match.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool IsNegativeSymbol(char c)
|
||||
{
|
||||
return c == '-';
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
size_t const len = strlen(arr[i]);
|
||||
if (strncmp(s, arr[i], len) == 0)
|
||||
{
|
||||
s += len;
|
||||
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;
|
||||
}
|
||||
|
||||
bool 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 false;
|
||||
}
|
||||
|
||||
arrPos[ind] = s++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
double modifier = 1.0;
|
||||
while (true)
|
||||
{
|
||||
if (IsDecimalMark(*p))
|
||||
{
|
||||
if (gotMark)
|
||||
break;
|
||||
gotMark = true;
|
||||
markPos = p;
|
||||
}
|
||||
else if (isdigit(*p))
|
||||
{
|
||||
if (gotMark)
|
||||
gotDigitAfterMark = true;
|
||||
else
|
||||
gotDigitBeforeMark = true;
|
||||
}
|
||||
else if (IsNegativeSymbol(*p))
|
||||
{
|
||||
modifier = -1.0;
|
||||
}
|
||||
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 * modifier * 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;
|
||||
char const * s11 = s;
|
||||
if (SkipNSEW(s, arrPos))
|
||||
{
|
||||
s11 = s;
|
||||
Skip(s);
|
||||
}
|
||||
else
|
||||
SkipSpaces(s);
|
||||
|
||||
if (!*s)
|
||||
{
|
||||
// End of the string - check matching.
|
||||
break;
|
||||
}
|
||||
|
||||
char * s2;
|
||||
double const x = EatDouble(s, &s2);
|
||||
if (s == s2)
|
||||
{
|
||||
// invalid token
|
||||
if (s == s11)
|
||||
{
|
||||
// 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue