Add geo://... url scheme parser.

This commit is contained in:
Kirill Zhdanovich 2013-04-08 21:21:04 +03:00 committed by Alex Zolotarev
parent 325591f08e
commit 7860b5c951
6 changed files with 455 additions and 1 deletions

140
map/ge0_parser.cpp Normal file
View file

@ -0,0 +1,140 @@
#include "ge0_parser.hpp"
#include "url_api.hpp"
#include "../api/internal/c/api-client-internals.h"
#include "../coding/url_encode.hpp"
#include "../base/math.hpp"
static const int NAME_POSITON_IN_URL = 17;
static const int ZOOM_POSITION = 6;
url_api::Ge0Parser::Ge0Parser()
{
for (size_t i = 0; i < 256; ++i)
m_base64ReverseCharTable[i] = 255;
for (uint8_t i = 0; i < 64; ++i)
{
char c = MapsWithMe_Base64Char(i);
m_base64ReverseCharTable[static_cast<uint8_t>(c)] = i;
}
}
bool url_api::Ge0Parser::Parse(string const & url, Request & request)
{
// URL format:
//
// +------------------ 1 byte: zoom level
// |+-------+--------- 9 bytes: lat,lon
// || | +--+---- Variable number of bytes: point name
// || | | |
// ge0://ZCoordba64/Name
request.Clear();
if (url.size() < 16 || url.substr(0, 6) != "ge0://")
return false;
uint8_t const zoomI = DecodeBase64Char(url[ZOOM_POSITION]);
if (zoomI > 63)
return false;
request.m_viewportZoomLevel = DecodeZoom(zoomI);
request.m_points.push_back(url_api::Point());
url_api::Point & newPt = request.m_points.back();
DecodeLatLon(url.substr(7,9), newPt.m_lat, newPt.m_lon);
request.m_viewportLat = newPt.m_lat;
request.m_viewportLon = newPt.m_lon;
if (url.size() >= NAME_POSITON_IN_URL)
newPt.m_name = DecodeName(url.substr(NAME_POSITON_IN_URL, url.size() - NAME_POSITON_IN_URL));
return true;
}
uint8_t url_api::Ge0Parser::DecodeBase64Char(char const c)
{
return m_base64ReverseCharTable[static_cast<uint8_t>(c)];
}
double url_api::Ge0Parser::DecodeZoom(uint8_t const zoomByte)
{
//Coding zoom - int newZoom = ((oldZoom - 4) * 4)
return static_cast<double>(zoomByte) / 4 + 4;
}
void url_api::Ge0Parser::DecodeLatLon(string const & url, double & lat, double & lon)
{
int latInt = 0, lonInt = 0;
DecodeLatLonToInt(url, latInt, lonInt, url.size());
lat = DecodeLatFromInt(latInt, (1 << MAPSWITHME_MAX_COORD_BITS) - 1);
lon = DecodeLonFromInt(lonInt, (1 << MAPSWITHME_MAX_COORD_BITS) - 1);
}
void url_api::Ge0Parser::DecodeLatLonToInt(string const & url, int & lat, int & lon, int const bytes)
{
for(int i = 0, shift = MAPSWITHME_MAX_COORD_BITS - 3; i < bytes; ++i, shift -= 3)
{
const uint8_t a = DecodeBase64Char(url[i]);
const int lat1 = (((a >> 5) & 1) << 2 |
((a >> 3) & 1) << 1 |
((a >> 1) & 1));
const int lon1 = (((a >> 4) & 1) << 2 |
((a >> 2) & 1) << 1 |
(a & 1));
lat |= lat1 << shift;
lon |= lon1 << shift;
}
const double middleOfSquare = 1 << (3 * (MAPSWITHME_MAX_POINT_BYTES - bytes) - 1);
lat += middleOfSquare;
lon += middleOfSquare;
}
double url_api::Ge0Parser::DecodeLatFromInt(int const lat, int const maxValue)
{
return static_cast<double>(lat) / maxValue * 180 - 90;
}
double url_api::Ge0Parser::DecodeLonFromInt(int const lon, int const maxValue)
{
return static_cast<double>(lon) / (maxValue + 1.0) * 360.0 - 180;
}
string url_api::Ge0Parser::DecodeName(string const & name)
{
string resultName = name;
ValidateName(resultName);
resultName = UrlDecode(resultName);
SpacesToUnderscore(resultName);
return resultName;
}
void url_api::Ge0Parser::SpacesToUnderscore(string & name)
{
for (size_t i = 0; i < name.size(); ++i)
if (name[i] == ' ')
name[i] = '_';
else if (name[i] == '_')
name[i] = ' ';
}
void url_api::Ge0Parser::ValidateName(string & name)
{
if (name.empty())
return;
for (size_t i = 0; i + 2 < name.size(); ++i)
{
if (name[i] == '%' && (!IsHexChar(name[i + 1]) || !IsHexChar(name[i + 2])))
{
name.resize(i);
return;
}
}
if (name[name.size() - 1] == '%')
name.resize(name.size() - 1);
else if (name.size() > 1 && name[name.size() - 2] == '%')
name.resize(name.size() - 2);
}
bool url_api::Ge0Parser::IsHexChar(char const a)
{
return ((a >= '0' && a <= '9') || (a >= 'A' && a <= 'F') || (a >= 'a' && a <= 'f'));
}

33
map/ge0_parser.hpp Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "../base/base.hpp"
#include "../std/string.hpp"
namespace url_api
{
class Request;
class Ge0Parser
{
public:
Ge0Parser();
bool Parse(string const & url, Request & request);
protected:
uint8_t DecodeBase64Char(char const c);
static double DecodeZoom(uint8_t const zoomByte);
void DecodeLatLon(string const & url, double & lat, double & lon);
void DecodeLatLonToInt(string const & url, int & lat, int & lon, int const bytes);
double DecodeLatFromInt(int const lat, int const maxValue);
double DecodeLonFromInt(int const lon, int const maxValue);
string DecodeName(string const & name);
void SpacesToUnderscore(string & name);
void ValidateName(string & name);
static bool IsHexChar(char const a);
private:
uint8_t m_base64ReverseCharTable[256];
};
} // namespace url_api

View file

@ -56,6 +56,8 @@ HEADERS += \
area_info.hpp \
geometry_processors.hpp \
bookmark_manager.hpp \
ge0_parser.hpp \
url_api.hpp \
SOURCES += \
feature_vec_model.cpp \
@ -102,6 +104,8 @@ SOURCES += \
feature_info.cpp \
geometry_processors.cpp \
bookmark_manager.cpp \
ge0_parser.cpp \
../api/src/c/api-client.c \
!iphone*:!bada*:!android* {
HEADERS += qgl_render_context.hpp

View file

@ -0,0 +1,248 @@
#include "../../testing/testing.hpp"
#include "../ge0_parser.hpp"
#include "../url_api.hpp"
#include "../../api/internal/c/api-client-internals.h"
#include "../../api/src/c/api-client.h"
#include "../../base/macros.hpp"
using url_api::Ge0Parser;
using url_api::Request;
namespace
{
class Ge0ParserForTest : public Ge0Parser
{
public:
using Ge0Parser::DecodeBase64Char;
using Ge0Parser::DecodeZoom;
using Ge0Parser::DecodeLatLon;
};
double GetLatEpsilon(int coordBytes)
{
// Should be / 2.0 but probably because of accumulates loss of precision, 1.77 works but 2.0 doesn't.
double infelicity = 1 << ((MAPSWITHME_MAX_POINT_BYTES - coordBytes) * 3);
return infelicity / ((1 << MAPSWITHME_MAX_COORD_BITS) - 1) * 180 / 1.77;
}
double GetLonEpsilon(int coordBytes)
{
// Should be / 2.0 but probably because of accumulates loss of precision, 1.77 works but 2.0 doesn't.
double infelicity = 1 << ((MAPSWITHME_MAX_POINT_BYTES - coordBytes) * 3);
return (infelicity / ((1 << MAPSWITHME_MAX_COORD_BITS) - 1)) * 360 / 1.77;
}
void TestSuccess(char const * s, double lat, double lon, double zoom, char const * name)
{
Ge0Parser parser;
Request request;
bool const result = parser.Parse(s, request);
TEST(result, (s, zoom, lat, lon, name));
TEST_EQUAL(request.m_points.size(), 1, (s, zoom, lat, lon, name));
TEST_EQUAL(request.m_points[0].m_name, string(name), (s));
TEST_EQUAL(request.m_points[0].m_id, string(), (s));
double const latEps = GetLatEpsilon(9);
double const lonEps = GetLonEpsilon(9);
TEST(fabs(request.m_points[0].m_lat - lat) <= latEps, (s, zoom, lat, lon, name));
TEST(fabs(request.m_points[0].m_lon - lon) <= lonEps, (s, zoom, lat, lon, name));
TEST(fabs(request.m_viewportLat - lat) <= latEps, (s, zoom, lat, lon, name));
TEST(fabs(request.m_viewportLon - lon) <= lonEps, (s, zoom, lat, lon, name));
TEST_ALMOST_EQUAL(request.m_viewportZoomLevel, zoom, (s, zoom, lat, lon, name));
}
void TestFailure(char const * s)
{
Ge0Parser parser;
Request request;
bool const result = parser.Parse(s, request);
TEST_EQUAL(result, false, (s));
}
bool ConvergenceTest(double lat, double lon, double latEps, double lonEps)
{
double tmpLat = lat, tmpLon = lon;
Ge0ParserForTest parser;
for (size_t i = 0; i < 100000; ++i)
{
char urlPrefix[] = "Coord6789";
MapsWithMe_LatLonToString(tmpLat, tmpLon, urlPrefix + 0, 9);
parser.DecodeLatLon(urlPrefix, tmpLat, tmpLon);
}
if (abs(lat - tmpLat) <= latEps && abs(lon - tmpLon) <= lonEps)
return true;
return false;
}
} // unnamed namespace
UNIT_TEST(Base64DecodingWorksForAValidChar)
{
Ge0ParserForTest parser;
for (size_t i = 0; i < 64; ++i)
{
char c = MapsWithMe_Base64Char(i);
int i1 = parser.DecodeBase64Char(c);
TEST_EQUAL(i, i1, (c));
}
}
UNIT_TEST(Base64DecodingReturns255ForInvalidChar)
{
Ge0ParserForTest parser;
TEST_EQUAL(parser.DecodeBase64Char(' '), 255, ());
}
UNIT_TEST(Base64DecodingDoesNotCrashForAllChars)
{
Ge0ParserForTest parser;
for (size_t i = 0; i < 256; ++i)
parser.DecodeBase64Char(static_cast<char>(i));
}
UNIT_TEST(Base64DecodingCharFrequency)
{
vector<int> charCounts(256, 0);
Ge0ParserForTest parser;
for (size_t i = 0; i < 256; ++i)
++charCounts[parser.DecodeBase64Char(static_cast<char>(i))];
sort(charCounts.begin(), charCounts.end());
TEST_EQUAL(charCounts[255], 256 - 64, ());
TEST_EQUAL(charCounts[254], 1, ());
TEST_EQUAL(charCounts[254 - 63], 1, ());
TEST_EQUAL(charCounts[254 - 64], 0, ());
TEST_EQUAL(charCounts[0], 0, ());
}
UNIT_TEST(UrlSchemaValidationFailed)
{
TestFailure("trali vali");
TestFailure("trali vali tili tili eto my prohodili");
}
UNIT_TEST(DecodeZoomLevel)
{
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(0), 4, ());
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(4), 5, ());
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(6), 5.5, ());
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(53), 17.25, ());
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(60), 19, ());
TEST_EQUAL(Ge0ParserForTest::DecodeZoom(63), 19.75, ());
TestFailure("ge0://!wAAAAAAAA/Name");
TestFailure("ge0:///wAAAAAAAA/Name");
}
UNIT_TEST(LatLonConvergence)
{
double const latEps = GetLatEpsilon(9);
double const lonEps = GetLonEpsilon(9);
TEST(ConvergenceTest(0, 0, latEps, lonEps), ());
TEST(ConvergenceTest(1.111111, 2.11111, latEps, lonEps), ());
TEST(ConvergenceTest(-1.111111, -2.11111, latEps, lonEps), ());
TEST(ConvergenceTest(-90, -179.999999, latEps, lonEps), ());
TEST(ConvergenceTest(-88.12313, 80.4532999999, latEps, lonEps), ());
}
UNIT_TEST(ZoomDecoding)
{
TestSuccess("ge0://8wAAAAAAAA/Name", 0, 0, 19, "Name");
TestSuccess("ge0://AwAAAAAAAA/Name", 0, 0, 4, "Name");
TestSuccess("ge0://BwAAAAAAAA/Name", 0, 0, 4.25, "Name");
}
UNIT_TEST(LatLonDecoding)
{
TestSuccess("ge0://Byqqqqqqqq/Name", 45, 0, 4.25, "Name");
TestSuccess("ge0://B6qqqqqqqq/Name", 90, 0, 4.25, "Name");
TestSuccess("ge0://BVVVVVVVVV/Name", -90, 179.999999, 4.25, "Name");
TestSuccess("ge0://BP________/Name", -0.000001, -0.000001, 4.25, "Name");
TestSuccess("ge0://B_________/Name", 90, 179.999999, 4.25, "Name");
TestSuccess("ge0://Bqqqqqqqqq/Name", 90, -180, 4.25, "Name");
TestSuccess("ge0://BAAAAAAAAA/Name", -90, -180, 4.25, "Name");
}
UNIT_TEST(NameDecoding)
{
TestSuccess("ge0://AwAAAAAAAA/Super_Poi", 0, 0, 4, "Super Poi");
TestSuccess("ge0://AwAAAAAAAA/Super%5FPoi", 0, 0, 4, "Super Poi");
TestSuccess("ge0://AwAAAAAAAA/Super%5fPoi", 0, 0, 4, "Super Poi");
TestSuccess("ge0://AwAAAAAAAA/Super Poi", 0, 0, 4, "Super_Poi");
TestSuccess("ge0://AwAAAAAAAA/Super%20Poi", 0, 0, 4, "Super_Poi");
TestSuccess("ge0://AwAAAAAAAA/Name%", 0, 0, 4, "Name");
TestSuccess("ge0://AwAAAAAAAA/Name%2", 0, 0, 4, "Name");
TestSuccess("ge0://AwAAAAAAAA/Hello%09World%0A", 0, 0, 4, "Hello\tWorld\n");
TestSuccess("ge0://AwAAAAAAAA/Hello%%%%%%%%%", 0, 0, 4, "Hello");
TestSuccess("ge0://AwAAAAAAAA/Hello%%%%%%%%%World", 0, 0, 4, "Hello");
TestSuccess("ge0://AwAAAAAAAA/%d0%9c%d0%b8%d0%bd%d1%81%d0%ba_%d1%83%d0%bb._%d0%9b%d0%b5%d0%bd%d0%b8%d0%bd%d0%b0_9", 0, 0, 4, "Минск ул. Ленина 9");
TestSuccess("ge0://AwAAAAAAAA/z%c3%bcrich_bahnhofstrasse", 0, 0, 4, "zürich bahnhofstrasse");
TestSuccess("ge0://AwAAAAAAAA/%e5%8c%97%e4%ba%ac_or_B%c4%9bij%c4%abng%3F", 0, 0, 4, "北京 or Běijīng?");
TestSuccess("ge0://AwAAAAAAAA/%d0%9a%d0%b0%d0%ba_%d0%b2%d1%8b_%d1%81%d1%87%d0%b8%d1%82%d0%b0%d0%b5%d1%82%d0%b5%2C_%d0%bd%d0%b0%d0%b4%d0%be_%d0%bb%d0%b8_%d0%bf%d0%b8%d1%81%d0%b0%d1%82%d1%8c_const_%d0%b4%d0%bb%d1%8f_%d0%bf%d0%b0%d1%80%d0%b0%d0%bc%d0%b5%d1%82%d1%80%d0%be%d0%b2%2C_%d0%ba%d0%be%d1%82%d0%be%d1%80%d1%8b%d0%b5_%d0%bf%d0%b5%d1%80%d0%b5%d0%b4%d0%b0%d1%8e%d1%82%d1%81%d1%8f_%d0%b2_%d1%84%d1%83%d0%bd%d0%ba%d1%86%d0%b8%d1%8e_%d0%bf%d0%be_%d0%b7%d0%bd%d0%b0%d1%87%d0%b5%d0%bd%d0%b8%d1%8e%3F",
0, 0, 4, "Как вы считаете, надо ли писать const для параметров, которые передаются в функцию по значению?");
TestSuccess("ge0://AwAAAAAAAA/\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xba\xd0\xb0_\xd0\xb2_\xd1\x8e\xd1\x82\xd1\x84-8", 0, 0, 4, "строка в ютф-8");
}
UNIT_TEST(LatLonFullAndClippedCoordinates)
{
double maxLatDiffForCoordSize[10] = { 0 };
double maxLonDiffForCoordSize[10] = { 0 };
for (double lat = -90; lat <= 90; lat += 0.7)
{
for (double lon = -180; lon < 180; lon += 0.7)
{
char buf[20] = { 0 };
MapsWithMe_GenShortShowMapUrl(lat, lon, 4, "", buf, ARRAY_SIZE(buf));
for (int i = 9; i >= 1; --i)
{
string const str = string(buf).substr(7, i);
int const coordSize = str.size();
Ge0ParserForTest parser;
double latTmp, lonTmp;
parser.DecodeLatLon(str, latTmp, lonTmp);
double const epsLat = GetLatEpsilon(coordSize);
double const epsLon = GetLonEpsilon(coordSize);
double const difLat = fabs(lat - latTmp);
double const difLon = fabs(lon - lonTmp);
TEST(difLat <= epsLat, (str, lat, latTmp, lon, lonTmp, difLat, epsLat));
TEST(difLon <= epsLon, (str, lat, latTmp, lon, lonTmp, difLon, epsLon));
maxLatDiffForCoordSize[coordSize] = max(maxLatDiffForCoordSize[coordSize], difLat);
maxLonDiffForCoordSize[coordSize] = max(maxLonDiffForCoordSize[coordSize], difLon);
}
}
}
for (int coordSize = 1; coordSize <= 8; ++coordSize)
{
TEST(maxLatDiffForCoordSize[coordSize] > maxLatDiffForCoordSize[coordSize + 1], (coordSize));
TEST(maxLonDiffForCoordSize[coordSize] > maxLonDiffForCoordSize[coordSize + 1], (coordSize));
TEST(maxLatDiffForCoordSize[coordSize] <= GetLatEpsilon(coordSize), (coordSize));
TEST(maxLonDiffForCoordSize[coordSize] <= GetLonEpsilon(coordSize), (coordSize));
TEST(maxLatDiffForCoordSize[coordSize] > GetLatEpsilon(coordSize + 1), (coordSize));
TEST(maxLonDiffForCoordSize[coordSize] > GetLonEpsilon(coordSize + 1), (coordSize));
}
}
UNIT_TEST(ClippedName)
{
TestSuccess("ge0://AwAAAAAAAA/Super%5fPoi", 0, 0, 4, "Super Poi");
TestSuccess("ge0://AwAAAAAAAA/Super%5fPo" , 0, 0, 4, "Super Po");
TestSuccess("ge0://AwAAAAAAAA/Super%5fP" , 0, 0, 4, "Super P");
TestSuccess("ge0://AwAAAAAAAA/Super%5f" , 0, 0, 4, "Super ");
TestSuccess("ge0://AwAAAAAAAA/Super%5" , 0, 0, 4, "Super");
TestSuccess("ge0://AwAAAAAAAA/Super%" , 0, 0, 4, "Super");
TestSuccess("ge0://AwAAAAAAAA/Super" , 0, 0, 4, "Super");
TestSuccess("ge0://AwAAAAAAAA/Supe" , 0, 0, 4, "Supe");
TestSuccess("ge0://AwAAAAAAAA/Sup" , 0, 0, 4, "Sup");
TestSuccess("ge0://AwAAAAAAAA/Su" , 0, 0, 4, "Su");
TestSuccess("ge0://AwAAAAAAAA/S" , 0, 0, 4, "S");
TestSuccess("ge0://AwAAAAAAAA/" , 0, 0, 4, "");
TestSuccess("ge0://AwAAAAAAAA" , 0, 0, 4, "");
}

View file

@ -29,4 +29,5 @@ SOURCES += \
geourl_test.cpp \
measurement_tests.cpp \
mwm_url_tests.cpp \
feature_processor_test.cpp
feature_processor_test.cpp \
ge0_parser_tests.cpp \

28
map/url_api.hpp Normal file
View file

@ -0,0 +1,28 @@
#include "../std/vector.hpp"
namespace url_api
{
struct Point
{
Point() : m_lat(0), m_lon(0) {}
double m_lat;
double m_lon;
string m_name;
string m_id;
};
struct Request
{
vector<Point> m_points;
double m_viewportLat, m_viewportLon, m_viewportZoomLevel;
void Clear()
{
m_points.clear();
m_viewportLat = m_viewportLon = m_viewportZoomLevel = 0;
}
};
} // namespace url_api