Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
22
libs/ge0/CMakeLists.txt
Normal file
22
libs/ge0/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
project(ge0)
|
||||
|
||||
set(SRC
|
||||
geo_url_parser.cpp
|
||||
geo_url_parser.hpp
|
||||
parser.cpp
|
||||
parser.hpp
|
||||
url_generator.cpp
|
||||
url_generator.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
coding
|
||||
|
||||
PRIVATE
|
||||
base
|
||||
geometry
|
||||
)
|
||||
|
||||
omim_add_test_subdirectory(ge0_tests)
|
||||
106
libs/ge0/ge0.php
Normal file
106
libs/ge0/ge0.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
// @TODO add tests
|
||||
|
||||
// Returns array(lat, lon, zoom) or empty array in the case of error
|
||||
function DecodeGe0LatLonZoom($latLonZoom)
|
||||
{
|
||||
$OM_MAX_POINT_BYTES = 10;
|
||||
$OM_MAX_COORD_BITS = $OM_MAX_POINT_BYTES * 3;
|
||||
|
||||
$FAILED = array();
|
||||
|
||||
$base64ReverseArray = GetBase64ReverseArray();
|
||||
|
||||
$zoom = $base64ReverseArray[ord($latLonZoom[0])];
|
||||
if ($zoom > 63)
|
||||
return $FAILED;
|
||||
$zoom = $zoom / 4. + 4.;
|
||||
|
||||
$latLonStr = substr($latLonZoom, 1);
|
||||
$latLonBytes = strlen($latLonStr);
|
||||
$lat = 0;
|
||||
$lon = 0;
|
||||
for($i = 0, $shift = $OM_MAX_COORD_BITS - 3; $i < $latLonBytes; $i++, $shift -= 3)
|
||||
{
|
||||
$a = $base64ReverseArray[ord($latLonStr[$i])];
|
||||
$lat1 = ((($a >> 5) & 1) << 2 |
|
||||
(($a >> 3) & 1) << 1 |
|
||||
(($a >> 1) & 1));
|
||||
$lon1 = ((($a >> 4) & 1) << 2 |
|
||||
(($a >> 2) & 1) << 1 |
|
||||
($a & 1));
|
||||
$lat |= $lat1 << $shift;
|
||||
$lon |= $lon1 << $shift;
|
||||
}
|
||||
|
||||
$middleOfSquare = 1 << (3 * ($OM_MAX_POINT_BYTES - $latLonBytes) - 1);
|
||||
$lat += $middleOfSquare;
|
||||
$lon += $middleOfSquare;
|
||||
|
||||
$lat = round($lat / ((1 << $OM_MAX_COORD_BITS) - 1) * 180.0 - 90.0, 5);
|
||||
$lon = round($lon / (1 << $OM_MAX_COORD_BITS) * 360.0 - 180.0, 5);
|
||||
|
||||
if ($lat <= -90.0 || $lat >= 90.0)
|
||||
return $FAILED;
|
||||
if ($lon <= -180.0 || $lon >= 180.0)
|
||||
return $FAILED;
|
||||
|
||||
return array($lat, $lon, $zoom);
|
||||
}
|
||||
|
||||
// Returns decoded name
|
||||
function DecodeGe0Name($name)
|
||||
{
|
||||
return str_replace('_', ' ', rawurldecode($name));
|
||||
}
|
||||
|
||||
// Returns empty array in the case of error.
|
||||
// In the good case, returns array(lat, lon, zoom) or array(lat, lon, zoom, name)
|
||||
function DecodeGe0Url($url)
|
||||
{
|
||||
$OM_ZOOM_POSITION = 6;
|
||||
$NAME_POSITON_IN_URL = 17;
|
||||
|
||||
$FAILED = array();
|
||||
if (strlen($url) < 16 || strpos($url, "ge0://") != 0)
|
||||
return $FAILED;
|
||||
|
||||
$base64ReverseArray = GetBase64ReverseArray();
|
||||
|
||||
$latLonZoom = DecodeGe0LatLonZoom(substr($url, 6, 10));
|
||||
if (empty($latLonZoom))
|
||||
return $FAILED;
|
||||
|
||||
if (strlen($url) < $NAME_POSITON_IN_URL)
|
||||
return $latLonZoom;
|
||||
|
||||
$name = DecodeGe0Name(substr($url, $NAME_POSITON_IN_URL));
|
||||
array_push($latLonZoom, $name);
|
||||
return $latLonZoom;
|
||||
}
|
||||
|
||||
// Internal helper function
|
||||
function GetBase64ReverseArray()
|
||||
{
|
||||
static $base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
$base64ReverseArray = array();
|
||||
// fill array with 255's
|
||||
array_pad($base64ReverseArray, 256, 255);
|
||||
|
||||
for ($i = 0; $i < 64; $i++)
|
||||
{
|
||||
$c = $base64Alphabet[$i];
|
||||
$base64ReverseArray[ord($c)] = $i;
|
||||
}
|
||||
return $base64ReverseArray;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
// Tests are below
|
||||
//print_r(DecodeGe0Url("ge0://B4srhdHVVt/Some_Name"));
|
||||
//print_r(DecodeGe0Url("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"));
|
||||
|
||||
?>
|
||||
|
||||
11
libs/ge0/ge0_tests/CMakeLists.txt
Normal file
11
libs/ge0/ge0_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
project(ge0_tests)
|
||||
|
||||
set(SRC
|
||||
geo_url_tests.cpp
|
||||
parser_tests.cpp
|
||||
url_generator_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} ge0)
|
||||
304
libs/ge0/ge0_tests/geo_url_tests.cpp
Normal file
304
libs/ge0/ge0_tests/geo_url_tests.cpp
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "ge0/geo_url_parser.hpp"
|
||||
|
||||
namespace geo_url_tests
|
||||
{
|
||||
double const kEps = 1e-10;
|
||||
|
||||
using namespace geo;
|
||||
|
||||
UNIT_TEST(GeoUrl_Geo)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
// Bare RFC5870 URI with lat,lon.
|
||||
TEST(parser.Parse("geo:53.666,-27.666", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.666, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -27.666, kEps, ());
|
||||
|
||||
// Bare RFC5870 URI with lat,lon,altitude.
|
||||
TEST(parser.Parse("geo:53.666,-27.666,1000", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.666, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -27.666, kEps, ());
|
||||
// Altitude is ignored.
|
||||
|
||||
// OSMAnd on Android 2023-11-12.
|
||||
TEST(parser.Parse("geo:35.34156,33.32210?z=16", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 35.34156, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 33.32210, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
// Bare RFC5870 URI with a space between lat, lon.
|
||||
TEST(parser.Parse("geo:53.666, -27.666", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.666, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -27.666, kEps, ());
|
||||
|
||||
// Google's q= extension: 0,0 are treated as special values.
|
||||
TEST(parser.Parse("geo:0,0?q=Kyrenia%20Castle&z=15", info), ());
|
||||
TEST(!info.IsLatLonValid(), ());
|
||||
TEST_EQUAL(info.m_query, "Kyrenia Castle", ());
|
||||
TEST_EQUAL(info.m_zoom, 15, ());
|
||||
|
||||
// Coordinates in q= parameter.
|
||||
TEST(parser.Parse("geo:0,0?z=14&q=-54.683486138,25.289361259", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -54.683486138, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 25.289361259, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 14.0, kEps, ());
|
||||
|
||||
// Instagram on Android 2023-11-12.
|
||||
TEST(parser.Parse("geo:?q=35.20488357543945, 33.345027923583984", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 35.20488357543945, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 33.345027923583984, kEps, ());
|
||||
|
||||
// Invalid lat,lon in q= parameter is saved as a query.
|
||||
TEST(parser.Parse("geo:0,0?z=14&q=-100500.232,1213.232", info), ());
|
||||
TEST(!info.IsLatLonValid(), ());
|
||||
TEST_EQUAL(info.m_query, "-100500.232,1213.232", ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 14.0, kEps, ());
|
||||
|
||||
// RFC5870 additional parameters are ignored.
|
||||
TEST(parser.Parse("geo:48.198634,16.371648;crs=wgs84;u=40", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 48.198634, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 16.371648, kEps, ());
|
||||
// ;crs=wgs84;u=40 tail is ignored
|
||||
|
||||
// 0,0 are not treated as special values if q is empty.
|
||||
TEST(parser.Parse("geo:0,0", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 0.0, kEps, ());
|
||||
TEST_EQUAL(info.m_query, "", ());
|
||||
|
||||
// 0,0 are not treated as special values if q is empty.
|
||||
TEST(parser.Parse("geo:0,0?q=", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 0.0, kEps, ());
|
||||
TEST_EQUAL(info.m_query, "", ());
|
||||
|
||||
// Lat is 0.
|
||||
TEST(parser.Parse("geo:0,16.371648", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 16.371648, kEps, ());
|
||||
|
||||
// Lon is 0.
|
||||
TEST(parser.Parse("geo:53.666,0", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.666, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 0.0, kEps, ());
|
||||
|
||||
// URL Encoded comma (%2C) as delimiter
|
||||
TEST(parser.Parse("geo:-18.9151863%2C-48.28712359999999?q=-18.9151863%2C-48.28712359999999", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -18.9151863, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -48.28712359999999, kEps, ());
|
||||
|
||||
// URL Encoded comma (%2C) and space (%20)
|
||||
TEST(parser.Parse("geo:-18.9151863%2C%20-48.28712359999999?q=-18.9151863%2C-48.28712359999999", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -18.9151863, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -48.28712359999999, kEps, ());
|
||||
|
||||
// URL Encoded comma (%2C) and space as +
|
||||
TEST(parser.Parse("geo:-18.9151863%2C+-48.28712359999999?q=-18.9151863%2C-48.28712359999999", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -18.9151863, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -48.28712359999999, kEps, ());
|
||||
|
||||
// URL encoded with altitude
|
||||
TEST(parser.Parse("geo:53.666%2C-27.666%2C+1000", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.666, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -27.666, kEps, ());
|
||||
|
||||
TEST(parser.Parse("geo:-32.899583,139.043969&z=12", info), ("& instead of ? from a user report"));
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -32.899583, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 139.043969, kEps, ());
|
||||
TEST_EQUAL(info.m_zoom, 12, ());
|
||||
|
||||
// Invalid coordinates.
|
||||
TEST(!parser.Parse("geo:0,0garbage", info), ());
|
||||
TEST(!parser.Parse("geo:garbage0,0", info), ());
|
||||
TEST(!parser.Parse("geo:53.666", info), ());
|
||||
|
||||
// Garbage.
|
||||
TEST(!parser.Parse("geo:", info), ());
|
||||
|
||||
TEST(!parser.Parse("geo:", info), ());
|
||||
TEST(!parser.Parse("geo:random,garbage", info), ());
|
||||
TEST(!parser.Parse("geo://random,garbage", info), ());
|
||||
TEST(!parser.Parse("geo://point/?lon=27.666&lat=53.666&zoom=10", info), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_GeoLabel)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("geo:0,0?z=14&q=-54.683486138,25.289361259 (Forto%20dvaras)", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -54.683486138, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 25.289361259, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 14.0, kEps, ());
|
||||
TEST_EQUAL(info.m_label, "Forto dvaras", ());
|
||||
|
||||
TEST(parser.Parse("geo:0,0?z=14&q=-54.683486138,25.289361259(Forto%20dvaras)", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -54.683486138, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 25.289361259, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 14.0, kEps, ());
|
||||
TEST_EQUAL(info.m_label, "Forto dvaras", ());
|
||||
|
||||
TEST(parser.Parse("geo:0,0?q=-54.683486138,25.289361259&z=14 (Forto%20dvaras)", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -54.683486138, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 25.289361259, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 14.0, kEps, ());
|
||||
TEST_EQUAL(info.m_label, "Forto dvaras", ());
|
||||
|
||||
TEST(parser.Parse("geo:0,0(Forto%20dvaras)", info), ());
|
||||
TEST(info.IsLatLonValid(), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 0.0, kEps, ());
|
||||
TEST_EQUAL(info.m_label, "Forto dvaras", ());
|
||||
|
||||
TEST(parser.Parse("geo:0,0?q=Forto%20dvaras)", info), ());
|
||||
TEST(!info.IsLatLonValid(), ());
|
||||
TEST_EQUAL(info.m_query, "Forto dvaras)", ());
|
||||
|
||||
TEST(!parser.Parse("geo:(Forto%20dvaras)", info), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_GoogleMaps)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("https://maps.google.com/maps?z=16&q=Mezza9%401.3067198,103.83282", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 1.3067198, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 103.83282, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://maps.google.com/maps?z=16&q=House+of+Seafood+%40+180%40-1.356706,103.87591", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -1.356706, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 103.87591, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://www.google.com/maps/place/Falafel+M.+Sahyoun/@33.8904447,35.5044618,16z", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.8904447, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.5044618, kEps, ());
|
||||
// Sic: zoom is not parsed
|
||||
// TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://www.google.com/maps?q=55.751809,-37.6130029", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 55.751809, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -37.6130029, kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_Yandex)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("https://yandex.ru/maps/213/moscow/?ll=37.000000%2C55.000000&z=10", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 55.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 37.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 10.0, kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_2GIS)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("https://2gis.ru/moscow/firm/4504127908589159/center/37.6186,55.7601/zoom/15.9764", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 55.7601, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 37.6186, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 15.9764, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://2gis.ru/moscow/firm/4504127908589159/center/-37,55/zoom/15", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 55.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -37.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 15.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://2gis.ru/moscow/firm/4504127908589159?m=37.618632%2C55.760069%2F15.232", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 55.760069, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 37.618632, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 15.232, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://2gis.ru/moscow/firm/4504127908589159?m=37.618632%2C-55.760069%2F15", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -55.760069, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 37.618632, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 15.0, kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_LatLon)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(!parser.Parse("mapswithme:123.33,32.22/showmethemagic", info), ());
|
||||
TEST(!parser.Parse("mapswithme:32.22, 123.33/showmethemagic", info), ());
|
||||
TEST(!parser.Parse("model: iphone 7,1", info), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_OpenStreetMap)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("https://www.openstreetmap.org/#map=16/33.89041/35.50664", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.89041, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.50664, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://www.openstreetmap.org/search?query=Falafel%20Sahyoun#map=16/33.89041/35.50664", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.89041, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.50664, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://www.openstreetmap.org/#map=21/53.90323/-27.55806", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 53.90323, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, -27.55806, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 20.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("https://www.openstreetmap.org/way/45394171#map=10/34.67379/33.04422", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 34.67379, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 33.04422, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 10.0, kEps, ());
|
||||
|
||||
// Links in such format are generated by
|
||||
// https://geohack.toolforge.org/geohack.php?pagename=White_House¶ms=38_53_52_N_77_02_11_W_type:landmark_region:US-DC
|
||||
TEST(parser.Parse("https://www.openstreetmap.org/index.html?mlat=48.277222&mlon=24.152222&zoom=15", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 48.277222, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 24.152222, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 15.0, kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GeoURL_BadZoom)
|
||||
{
|
||||
UnifiedParser parser;
|
||||
GeoURLInfo info;
|
||||
|
||||
TEST(parser.Parse("geo:52.23405,21.01547?z=22", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 52.23405, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 21.01547, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 20.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("geo:-52.23405,21.01547?z=nineteen", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, -52.23405, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 21.01547, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 0.0, kEps, ());
|
||||
|
||||
TEST(parser.Parse("geo:52.23405,21.01547?z=-1", info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 52.23405, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 21.01547, kEps, ());
|
||||
TEST_GREATER_OR_EQUAL(info.m_zoom, 0.0, ());
|
||||
}
|
||||
} // namespace geo_url_tests
|
||||
314
libs/ge0/ge0_tests/parser_tests.cpp
Normal file
314
libs/ge0/ge0_tests/parser_tests.cpp
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "ge0/parser.hpp"
|
||||
#include "ge0/url_generator.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
double const kZoomEps = 1e-10;
|
||||
} // namespace
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
class Ge0ParserForTest : public Ge0Parser
|
||||
{
|
||||
public:
|
||||
using Ge0Parser::DecodeBase64Char;
|
||||
using Ge0Parser::DecodeLatLon;
|
||||
using Ge0Parser::DecodeZoom;
|
||||
};
|
||||
|
||||
double GetLatEpsilon(size_t coordBytes)
|
||||
{
|
||||
// Should be / 2.0 but probably because of accumulates loss of precision, 1.77 works but 2.0
|
||||
// doesn't.
|
||||
double infelicity = 1 << ((kMaxPointBytes - coordBytes) * 3);
|
||||
return infelicity / ((1 << kMaxCoordBits) - 1) * 180 / 1.77;
|
||||
}
|
||||
|
||||
double GetLonEpsilon(size_t coordBytes)
|
||||
{
|
||||
// Should be / 2.0 but probably because of accumulates loss of precision, 1.77 works but 2.0
|
||||
// doesn't.
|
||||
double infelicity = 1 << ((kMaxPointBytes - coordBytes) * 3);
|
||||
return (infelicity / ((1 << kMaxCoordBits) - 1)) * 360 / 1.77;
|
||||
}
|
||||
|
||||
void TestSuccess(char const * s, double lat, double lon, double zoom, char const * name)
|
||||
{
|
||||
Ge0Parser parser;
|
||||
Ge0Parser::Result parseResult;
|
||||
bool const success = parser.Parse(s, parseResult);
|
||||
|
||||
TEST(success, (s, parseResult));
|
||||
|
||||
TEST_EQUAL(parseResult.m_name, string(name), (s));
|
||||
double const latEps = GetLatEpsilon(9);
|
||||
double const lonEps = GetLonEpsilon(9);
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_lat, lat, latEps, (s, parseResult));
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_lon, lon, lonEps, (s, parseResult));
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_zoomLevel, zoom, kZoomEps, (s, parseResult));
|
||||
}
|
||||
|
||||
void TestFailure(char const * s)
|
||||
{
|
||||
Ge0Parser parser;
|
||||
Ge0Parser::Result parseResult;
|
||||
bool const success = parser.Parse(s, parseResult);
|
||||
TEST(!success, (s, parseResult));
|
||||
}
|
||||
|
||||
bool ConvergenceTest(double lat, double lon, double latEps, double lonEps)
|
||||
{
|
||||
double tmpLat = lat;
|
||||
double tmpLon = lon;
|
||||
Ge0ParserForTest parser;
|
||||
for (size_t i = 0; i < 100000; ++i)
|
||||
{
|
||||
char urlPrefix[] = "Coord6789";
|
||||
ge0::LatLonToString(tmpLat, tmpLon, urlPrefix + 0, 9);
|
||||
parser.DecodeLatLon(urlPrefix, tmpLat, tmpLon);
|
||||
}
|
||||
return AlmostEqualAbs(lat, tmpLat, latEps) && AlmostEqualAbs(lon, tmpLon, lonEps);
|
||||
}
|
||||
|
||||
UNIT_TEST(Base64DecodingWorksForAValidChar)
|
||||
{
|
||||
Ge0ParserForTest parser;
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
char c = ge0::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://Bzqqqqqqqq/Name", 45, 45, 4.25, "Name");
|
||||
TestSuccess("ge0://BaF6F6F6F6/Name", -20, 20, 4.25, "Name");
|
||||
TestSuccess("ge0://B4srhdHVVt/Name", 64.5234, 12.1234, 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");
|
||||
TestFailure("ge0://Byqqqqqqq/Name");
|
||||
TestFailure("ge0://Byqqqqqqq/");
|
||||
TestFailure("ge0://Byqqqqqqq");
|
||||
TestFailure("ge0://B");
|
||||
TestFailure("ge0://");
|
||||
}
|
||||
|
||||
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/"
|
||||
"\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");
|
||||
|
||||
TestSuccess("ge0://AwAAAAAAAA/", 0, 0, 4, "");
|
||||
TestSuccess("ge0://AwAAAAAAAA/s", 0, 0, 4, "s");
|
||||
|
||||
TestFailure("ge0://AwAAAAAAAAs");
|
||||
TestFailure("ge0://AwAAAAAAAAss");
|
||||
|
||||
{
|
||||
auto const name =
|
||||
"Как вы считаете, надо ли писать const для параметров, которые передаются в функцию по "
|
||||
"значению?";
|
||||
double lat = 0;
|
||||
double lon = 0;
|
||||
double zoom = 4;
|
||||
string const url =
|
||||
"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";
|
||||
|
||||
Ge0Parser parser;
|
||||
Ge0Parser::Result parseResult;
|
||||
bool const success = parser.Parse(url.c_str(), parseResult);
|
||||
|
||||
TEST(success, (url, parseResult));
|
||||
|
||||
// Name would be valid but is too long.
|
||||
TEST_NOT_EQUAL(parseResult.m_name, string(name), (url));
|
||||
double const latEps = GetLatEpsilon(9);
|
||||
double const lonEps = GetLonEpsilon(9);
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_lat, lat, latEps, (url, parseResult));
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_lon, lon, lonEps, (url, parseResult));
|
||||
TEST_ALMOST_EQUAL_ABS(parseResult.m_zoomLevel, zoom, kZoomEps, (url, parseResult));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
string const buf = ge0::GenerateShortShowMapUrl(lat, lon, 4, "");
|
||||
size_t const coordInd = buf.find("://") + 4;
|
||||
for (int i = 9; i >= 1; --i)
|
||||
{
|
||||
string const str = buf.substr(coordInd, i);
|
||||
size_t 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 (size_t 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, "");
|
||||
}
|
||||
|
||||
UNIT_TEST(Bad_Base64)
|
||||
{
|
||||
TestSuccess("ge0://Byqqqqqqqq", 45, 0, 4.25, "");
|
||||
TestFailure("ge0://Byqqqqqqq");
|
||||
TestFailure("ge0://Byqqqqqqq\xEE");
|
||||
}
|
||||
|
||||
UNIT_TEST(OtherPrefixes)
|
||||
{
|
||||
TestSuccess("http://comaps.app/Byqqqqqqqq/Name", 45, 0, 4.25, "Name");
|
||||
TestSuccess("https://comaps.app/Byqqqqqqqq/Name", 45, 0, 4.25, "Name");
|
||||
TestFailure("http://comapz.app/Byqqqqqqqq/Name");
|
||||
TestSuccess("http://comaps.app/AwAAAAAAAA/Super%5fPoi", 0, 0, 4, "Super Poi");
|
||||
TestSuccess("https://comaps.app/AwAAAAAAAA/Super%5fPoi", 0, 0, 4, "Super Poi");
|
||||
TestFailure("https://comapz.app/AwAAAAAAAA/Super%5fPoi");
|
||||
|
||||
TestSuccess("https://comaps.app/Byqqqqqqqq", 45, 0, 4.25, "");
|
||||
TestFailure("https://comaps.app/Byqqqqqqq");
|
||||
}
|
||||
} // namespace ge0
|
||||
362
libs/ge0/ge0_tests/url_generator_tests.cpp
Normal file
362
libs/ge0/ge0_tests/url_generator_tests.cpp
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "ge0/geo_url_parser.hpp"
|
||||
#include "ge0/url_generator.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
int const kTestCoordBytes = 9;
|
||||
double const kEps = 1e-10;
|
||||
} // namespace
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
string TestLatLonToStr(double lat, double lon)
|
||||
{
|
||||
static char s[kTestCoordBytes + 1] = {0};
|
||||
LatLonToString(lat, lon, s, kTestCoordBytes);
|
||||
return string(s);
|
||||
}
|
||||
|
||||
UNIT_TEST(Base64Char)
|
||||
{
|
||||
TEST_EQUAL('A', Base64Char(0), ());
|
||||
TEST_EQUAL('B', Base64Char(1), ());
|
||||
TEST_EQUAL('9', Base64Char(61), ());
|
||||
TEST_EQUAL('-', Base64Char(62), ());
|
||||
TEST_EQUAL('_', Base64Char(63), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatToInt_0)
|
||||
{
|
||||
TEST_EQUAL(499, LatToInt(0, 998), ());
|
||||
TEST_EQUAL(500, LatToInt(0, 999), ());
|
||||
TEST_EQUAL(500, LatToInt(0, 1000), ());
|
||||
TEST_EQUAL(501, LatToInt(0, 1001), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatToInt_NearOrGreater90)
|
||||
{
|
||||
TEST_EQUAL(999, LatToInt(89.9, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(89.999999, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(90.0, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(90.1, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(100.0, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(180.0, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(350.0, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(360.0, 1000), ());
|
||||
TEST_EQUAL(1000, LatToInt(370.0, 1000), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatToInt_NearOrLess_minus90)
|
||||
{
|
||||
TEST_EQUAL(1, LatToInt(-89.9, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-89.999999, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-90.0, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-90.1, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-100.0, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-180.0, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-350.0, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-360.0, 1000), ());
|
||||
TEST_EQUAL(0, LatToInt(-370.0, 1000), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatToInt_NearOrLess_Rounding)
|
||||
{
|
||||
TEST_EQUAL(0, LatToInt(-90.0, 2), ());
|
||||
TEST_EQUAL(0, LatToInt(-45.1, 2), ());
|
||||
TEST_EQUAL(1, LatToInt(-45.0, 2), ());
|
||||
TEST_EQUAL(1, LatToInt(0.0, 2), ());
|
||||
TEST_EQUAL(1, LatToInt(44.9, 2), ());
|
||||
TEST_EQUAL(2, LatToInt(45.0, 2), ());
|
||||
TEST_EQUAL(2, LatToInt(90.0, 2), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LonIn180180)
|
||||
{
|
||||
double const kEps = 1e-20;
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(0.0, LonIn180180(0), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(20.0, LonIn180180(20), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(90.0, LonIn180180(90), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(179.0, LonIn180180(179), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-180.0, LonIn180180(180), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-180.0, LonIn180180(-180), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-179.0, LonIn180180(-179), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-20.0, LonIn180180(-20), kEps, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(0.0, LonIn180180(360), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(0.0, LonIn180180(720), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(0.0, LonIn180180(-360), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(0.0, LonIn180180(-720), kEps, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(179.0, LonIn180180(360 + 179), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-180.0, LonIn180180(360 + 180), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-180.0, LonIn180180(360 - 180), kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(-179.0, LonIn180180(360 - 179), kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LonToInt_NearOrLess_Rounding)
|
||||
{
|
||||
/*
|
||||
135 90 45
|
||||
\ | /
|
||||
03333
|
||||
180 0\|/2
|
||||
-----0-o-2---- 0
|
||||
-180 0/|\2
|
||||
11112
|
||||
/ | \
|
||||
-135 -90 -45
|
||||
*/
|
||||
TEST_EQUAL(0, LonToInt(-180.0, 3), ());
|
||||
TEST_EQUAL(0, LonToInt(-135.1, 3), ());
|
||||
TEST_EQUAL(1, LonToInt(-135.0, 3), ());
|
||||
TEST_EQUAL(1, LonToInt(-90.0, 3), ());
|
||||
TEST_EQUAL(1, LonToInt(-60.1, 3), ());
|
||||
TEST_EQUAL(1, LonToInt(-45.1, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(-45.0, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(0.0, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(44.9, 3), ());
|
||||
TEST_EQUAL(3, LonToInt(45.0, 3), ());
|
||||
TEST_EQUAL(3, LonToInt(120.0, 3), ());
|
||||
TEST_EQUAL(3, LonToInt(134.9, 3), ());
|
||||
TEST_EQUAL(0, LonToInt(135.0, 3), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LonToInt_0)
|
||||
{
|
||||
TEST_EQUAL(499, LonToInt(0, 997), ());
|
||||
TEST_EQUAL(500, LonToInt(0, 998), ());
|
||||
TEST_EQUAL(500, LonToInt(0, 999), ());
|
||||
TEST_EQUAL(501, LonToInt(0, 1000), ());
|
||||
TEST_EQUAL(501, LonToInt(0, 1001), ());
|
||||
|
||||
TEST_EQUAL(499, LonToInt(360, 997), ());
|
||||
TEST_EQUAL(500, LonToInt(360, 998), ());
|
||||
TEST_EQUAL(500, LonToInt(360, 999), ());
|
||||
TEST_EQUAL(501, LonToInt(360, 1000), ());
|
||||
TEST_EQUAL(501, LonToInt(360, 1001), ());
|
||||
|
||||
TEST_EQUAL(499, LonToInt(-360, 997), ());
|
||||
TEST_EQUAL(500, LonToInt(-360, 998), ());
|
||||
TEST_EQUAL(500, LonToInt(-360, 999), ());
|
||||
TEST_EQUAL(501, LonToInt(-360, 1000), ());
|
||||
TEST_EQUAL(501, LonToInt(-360, 1001), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LonToInt_180)
|
||||
{
|
||||
TEST_EQUAL(0, LonToInt(-180, 1000), ());
|
||||
TEST_EQUAL(0, LonToInt(180, 1000), ());
|
||||
TEST_EQUAL(0, LonToInt(-180 - 360, 1000), ());
|
||||
TEST_EQUAL(0, LonToInt(180 + 360, 1000), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LonToInt_360)
|
||||
{
|
||||
TEST_EQUAL(2, LonToInt(0, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(0 + 360, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(0 - 360, 3), ());
|
||||
|
||||
TEST_EQUAL(2, LonToInt(1, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(1 + 360, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(1 - 360, 3), ());
|
||||
|
||||
TEST_EQUAL(2, LonToInt(-1, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(-1 + 360, 3), ());
|
||||
TEST_EQUAL(2, LonToInt(-1 - 360, 3), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatLonToString)
|
||||
{
|
||||
TEST_EQUAL("AAAAAAAAA", TestLatLonToStr(-90, -180), ());
|
||||
TEST_EQUAL("qqqqqqqqq", TestLatLonToStr(90, -180), ());
|
||||
TEST_EQUAL("_________", TestLatLonToStr(90, 179.999999), ());
|
||||
TEST_EQUAL("VVVVVVVVV", TestLatLonToStr(-90, 179.999999), ());
|
||||
TEST_EQUAL("wAAAAAAAA", TestLatLonToStr(0.0, 0.0), ());
|
||||
TEST_EQUAL("6qqqqqqqq", TestLatLonToStr(90.0, 0.0), ());
|
||||
TEST_EQUAL("P________", TestLatLonToStr(-0.000001, -0.000001), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatLonToString_PrefixIsTheSame)
|
||||
{
|
||||
for (double lat = -95; lat <= 95; lat += 0.7)
|
||||
{
|
||||
for (double lon = -190; lon < 190; lon += 0.9)
|
||||
{
|
||||
char prevStepS[kMaxPointBytes + 1] = {0};
|
||||
LatLonToString(lat, lon, prevStepS, kMaxPointBytes);
|
||||
|
||||
for (int len = kMaxPointBytes - 1; len > 0; --len)
|
||||
{
|
||||
// Test that the current string is a prefix of the previous one.
|
||||
char s[kMaxPointBytes] = {0};
|
||||
LatLonToString(lat, lon, s, len);
|
||||
prevStepS[len] = 0;
|
||||
TEST_EQUAL(s, string(prevStepS), ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(LatLonToString_StringDensity)
|
||||
{
|
||||
int b64toI[256];
|
||||
for (int i = 0; i < 256; ++i)
|
||||
b64toI[i] = -1;
|
||||
for (int i = 0; i < 64; ++i)
|
||||
b64toI[static_cast<size_t>(Base64Char(i))] = i;
|
||||
|
||||
int num1[256] = {0};
|
||||
int num2[256][256] = {{0}};
|
||||
|
||||
for (double lat = -90; lat <= 90; lat += 0.1)
|
||||
{
|
||||
for (double lon = -180; lon < 180; lon += 0.05)
|
||||
{
|
||||
char s[3] = {0};
|
||||
LatLonToString(lat, lon, s, 2);
|
||||
auto const s0 = static_cast<size_t>(s[0]);
|
||||
auto const s1 = static_cast<size_t>(s[1]);
|
||||
++num1[b64toI[s0]];
|
||||
++num2[b64toI[s0]][b64toI[s1]];
|
||||
}
|
||||
}
|
||||
|
||||
int min1 = 1 << 30;
|
||||
int min2 = 1 << 30;
|
||||
int max1 = 0;
|
||||
int max2 = 0;
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
if (num1[i] != 0 && num1[i] < min1)
|
||||
min1 = num1[i];
|
||||
if (num1[i] != 0 && num1[i] > max1)
|
||||
max1 = num1[i];
|
||||
for (int j = 0; j < 256; ++j)
|
||||
{
|
||||
if (num2[i][j] != 0 && num2[i][j] < min2)
|
||||
min2 = num2[i][j];
|
||||
if (num2[i][j] != 0 && num2[i][j] > max2)
|
||||
max2 = num2[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// printf("\n1: %i-%i 2: %i-%i\n", min1, max1, min2, max2);
|
||||
TEST((max1 - min1) * 1.0 / max1 < 0.05, ());
|
||||
TEST((max2 - min2) * 1.0 / max2 < 0.05, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_SmokeTest)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "Name");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_NameIsEmpty)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_ZoomVerySmall)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 2, "Name");
|
||||
TEST_EQUAL("cm://AwAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_ZoomNegative)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, -5, "Name");
|
||||
TEST_EQUAL("cm://AwAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_ZoomLarge)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 20, "Name");
|
||||
TEST_EQUAL("cm://_wAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_ZoomVeryLarge)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 2000000000, "Name");
|
||||
TEST_EQUAL("cm://_wAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_FractionalZoom)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 8.25, "Name");
|
||||
TEST_EQUAL("cm://RwAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_FractionalZoomRoundsDown)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 8.499, "Name");
|
||||
TEST_EQUAL("cm://RwAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_FractionalZoomNextStep)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 8.5, "Name");
|
||||
TEST_EQUAL("cm://SwAAAAAAAA/Name", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_SpaceIsReplacedWithUnderscore)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "Hello World");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/Hello_World", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_NamesAreEscaped)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "'Hello,World!%$");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/%27Hello%2CWorld%21%25%24", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_UnderscoreIsReplacedWith_Percent_20)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "Hello_World");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/Hello%20World", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_ControlCharsAreEscaped)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "Hello\tWorld\n");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/Hello%09World%0A", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_Unicode)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "\xe2\x98\x84");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/\xe2\x98\x84", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateShortShowMapUrl_UnicodeMixedWithOtherChars)
|
||||
{
|
||||
string res = GenerateShortShowMapUrl(0, 0, 19, "Back_in \xe2\x98\x84!\xd1\x8e\xd0\xbc");
|
||||
TEST_EQUAL("cm://8wAAAAAAAA/Back%20in_\xe2\x98\x84%21\xd1\x8e\xd0\xbc", res, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateGeoUri_SmokeTest)
|
||||
{
|
||||
string res = GenerateGeoUri(33.8904075, 35.5066454, 16.5, "Falafel M. Sahyoun");
|
||||
TEST_EQUAL("geo:33.8904075,35.5066454?z=16.5&q=33.8904075,35.5066454(Falafel%20M.%20Sahyoun)", res, ());
|
||||
|
||||
// geo:33.8904075,35.5066454?z=16.5(Falafel%20M.%20Sahyoun)
|
||||
// geo:33.890408,35.506645?z=16.5(Falafel%20M.%20Sahyoun)
|
||||
|
||||
geo::GeoURLInfo info;
|
||||
geo::GeoParser parser;
|
||||
TEST(parser.Parse(res, info), ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.8904075, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.5066454, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.5, kEps, ());
|
||||
TEST_EQUAL(info.m_label, "Falafel M. Sahyoun", ());
|
||||
}
|
||||
|
||||
} // namespace ge0
|
||||
389
libs/ge0/geo_url_parser.cpp
Normal file
389
libs/ge0/geo_url_parser.cpp
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
#include "geo_url_parser.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
namespace geo
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kEps = 1e-10;
|
||||
|
||||
// Same as scales::GetUpperScale() from indexer/scales.hpp.
|
||||
// 1. Duplicated here to avoid a dependency on indexer/.
|
||||
// 2. The value is arbitrary anyway: we want to parse a "z=%f" pair
|
||||
// from a URL parameter, and different map providers may have different
|
||||
// maximal zoom levels.
|
||||
double constexpr kMaxZoom = 20.0;
|
||||
|
||||
bool MatchLatLonZoom(string const & s, boost::regex const & re, size_t lati, size_t loni, size_t zoomi, GeoURLInfo & info)
|
||||
{
|
||||
boost::smatch m;
|
||||
if (!boost::regex_search(s, m, re) || m.size() != 4)
|
||||
return false;
|
||||
|
||||
double lat, lon, zoom;
|
||||
VERIFY(strings::to_double(m[lati].str(), lat), ());
|
||||
VERIFY(strings::to_double(m[loni].str(), lon), ());
|
||||
VERIFY(strings::to_double(m[zoomi].str(), zoom), ());
|
||||
if (info.SetLat(lat) && info.SetLon(lon))
|
||||
{
|
||||
info.SetZoom(zoom);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(LWARNING, ("Bad coordinates url:", s));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MatchHost(url::Url const & url, char const * host)
|
||||
{
|
||||
return url.GetHost().find(host) != std::string::npos;
|
||||
}
|
||||
|
||||
// Canonical parsing is float-only coordinates and int-only scale.
|
||||
std::string const kFloatCoord = R"(([+-]?\d+\.\d+))";
|
||||
std::string const kIntScale = R"((\d+))";
|
||||
|
||||
// 2gis can accept float or int coordinates and scale.
|
||||
std::string const kFloatIntCoord = R"(([+-]?\d+\.?\d*))";
|
||||
std::string const kFloatIntScale = R"((\d+\.?\d*))";
|
||||
} // namespace
|
||||
|
||||
LatLonParser::LatLonParser() : m_info(nullptr), m_regexp(boost::regex(kFloatCoord + ", *" + kFloatCoord)) {}
|
||||
|
||||
void LatLonParser::Reset(url::Url const & url, GeoURLInfo & info)
|
||||
{
|
||||
info.Reset();
|
||||
m_info = &info;
|
||||
m_swapLatLon = MatchHost(url, "2gis") || MatchHost(url, "yandex");
|
||||
m_latPriority = m_lonPriority = -1;
|
||||
}
|
||||
|
||||
bool LatLonParser::IsValid() const
|
||||
{
|
||||
return m_latPriority == m_lonPriority && m_latPriority != -1;
|
||||
}
|
||||
|
||||
void LatLonParser::operator()(std::string name, std::string const & value)
|
||||
{
|
||||
strings::AsciiToLower(name);
|
||||
if (name == "z" || name == "zoom")
|
||||
{
|
||||
double x;
|
||||
if (strings::to_double(value, x))
|
||||
m_info->SetZoom(x);
|
||||
return;
|
||||
}
|
||||
|
||||
int const priority = GetCoordinatesPriority(name);
|
||||
if (priority == -1 || priority < m_latPriority || priority < m_lonPriority)
|
||||
return;
|
||||
|
||||
if (priority != kXYPriority && priority != kLatLonPriority)
|
||||
{
|
||||
boost::smatch m;
|
||||
if (boost::regex_search(value, m, m_regexp) && m.size() == 3)
|
||||
{
|
||||
double lat, lon;
|
||||
VERIFY(strings::to_double(m[1].str(), lat), ());
|
||||
VERIFY(strings::to_double(m[2].str(), lon), ());
|
||||
|
||||
if (m_swapLatLon)
|
||||
std::swap(lat, lon);
|
||||
|
||||
if (m_info->SetLat(lat) && m_info->SetLon(lon))
|
||||
{
|
||||
m_latPriority = priority;
|
||||
m_lonPriority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double x;
|
||||
if (strings::to_double(value, x))
|
||||
{
|
||||
if (name == "lat" || name == "mlat" || name == "y")
|
||||
{
|
||||
if (m_info->SetLat(x))
|
||||
m_latPriority = priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(name == "lon" || name == "mlon" || name == "x", (name));
|
||||
if (m_info->SetLon(x))
|
||||
m_lonPriority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int LatLonParser::GetCoordinatesPriority(string const & token)
|
||||
{
|
||||
if (token.empty())
|
||||
return 0;
|
||||
if (token == "q" || token == "m")
|
||||
return 1;
|
||||
if (token == "saddr" || token == "daddr")
|
||||
return 2;
|
||||
if (token == "sll")
|
||||
return 3;
|
||||
if (token.find("point") != string::npos)
|
||||
return 4;
|
||||
if (token == "ll")
|
||||
return kLLPriority;
|
||||
if (token == "x" || token == "y")
|
||||
return kXYPriority;
|
||||
if (token == "lat" || token == "mlat" || token == "lon" || token == "mlon")
|
||||
return kLatLonPriority;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
GeoParser::GeoParser() : m_latlonRe(boost::regex(R"(([+-]?\d+(?:\.\d+)?), *([+-]?\d+(?:\.\d+)?)(:?, *([+-]?\d+(?:\.\d+)?))?)")), m_zoomRe(boost::regex(kFloatIntScale)) {}
|
||||
|
||||
bool GeoParser::Parse(std::string const & raw, GeoURLInfo & info) const
|
||||
{
|
||||
info.Reset();
|
||||
|
||||
/*
|
||||
* References:
|
||||
* - https://datatracker.ietf.org/doc/html/rfc5870
|
||||
* - https://developer.android.com/guide/components/intents-common#Maps
|
||||
* - https://developers.google.com/maps/documentation/urls/android-intents
|
||||
*/
|
||||
|
||||
/*
|
||||
* Check that URI starts with geo:
|
||||
*/
|
||||
if (!raw.starts_with("geo:"))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Check for trailing `(label)` which is not RFC3986-compliant (thanks, Google).
|
||||
*/
|
||||
size_t end = string::npos;
|
||||
if (raw.size() > 2 && raw.back() == ')' && string::npos != (end = raw.rfind('(')))
|
||||
{
|
||||
// head (label)
|
||||
// ^end
|
||||
info.m_label = url::UrlDecode(raw.substr(end + 1, raw.size() - end - 2));
|
||||
// Remove any whitespace between `head` and `(`.
|
||||
end--;
|
||||
while (end > 0 && (raw[end] == ' ' || raw[end] == '+'))
|
||||
end--;
|
||||
}
|
||||
|
||||
url::Url url(end == string::npos ? raw : raw.substr(0, end + 1));
|
||||
if (!url.IsValid())
|
||||
return false;
|
||||
ASSERT_EQUAL(url.GetScheme(), "geo", ());
|
||||
|
||||
// Fix non-RFC url/hostname, reported by an Android user, with & instead of ?
|
||||
std::string_view constexpr kWrongZoomInHost = "&z=";
|
||||
if (std::string::npos != url.GetHost().find(kWrongZoomInHost))
|
||||
{
|
||||
auto fixedUrl = raw;
|
||||
fixedUrl.replace(raw.find(kWrongZoomInHost), 1, 1, std::string::value_type{'?'});
|
||||
url = url::Url{fixedUrl};
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse coordinates before ';' character
|
||||
*/
|
||||
std::string coordinates = url.GetHost().substr(0, url.GetHost().find(';'));
|
||||
if (!coordinates.empty())
|
||||
{
|
||||
boost::smatch m;
|
||||
if (!boost::regex_match(coordinates, m, m_latlonRe) || m.size() < 3)
|
||||
{
|
||||
// no match? try URL decoding before giving up
|
||||
coordinates = url::UrlDecode(coordinates);
|
||||
if (!boost::regex_match(coordinates, m, m_latlonRe) || m.size() < 3)
|
||||
{
|
||||
LOG(LWARNING, ("Missing coordinates in", raw));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double lat, lon;
|
||||
VERIFY(strings::to_double(m[1].str(), lat), ());
|
||||
VERIFY(strings::to_double(m[2].str(), lon), ());
|
||||
if (!mercator::ValidLat(lat) || !mercator::ValidLon(lon))
|
||||
{
|
||||
LOG(LWARNING, ("Invalid lat,lon in", raw));
|
||||
return false;
|
||||
}
|
||||
info.m_lat = lat;
|
||||
info.m_lon = lon;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse q=
|
||||
*/
|
||||
std::string const * q = url.GetParamValue("q");
|
||||
if (q != nullptr && !q->empty())
|
||||
{
|
||||
// Try to extract lat,lon from q=
|
||||
boost::smatch m;
|
||||
if (boost::regex_match(*q, m, m_latlonRe) && m.size() != 3)
|
||||
{
|
||||
double lat, lon;
|
||||
VERIFY(strings::to_double(m[1].str(), lat), ());
|
||||
VERIFY(strings::to_double(m[2].str(), lon), ());
|
||||
if (!mercator::ValidLat(lat) || !mercator::ValidLon(lon))
|
||||
{
|
||||
LOG(LWARNING, ("Invalid lat,lon after q=", raw));
|
||||
info.m_query = *q;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.m_lat = lat;
|
||||
info.m_lon = lon;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.m_query = *q;
|
||||
}
|
||||
// Ignore special 0,0 lat,lon if q= presents.
|
||||
if (!info.m_query.empty() && fabs(info.m_lat) < kEps && fabs(info.m_lon) < kEps)
|
||||
info.m_lat = info.m_lon = ms::LatLon::kInvalid;
|
||||
}
|
||||
|
||||
if (!info.IsLatLonValid() && info.m_query.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Missing coordinates and q=", raw));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse z=
|
||||
*/
|
||||
std::string const * z = url.GetParamValue("z");
|
||||
if (z != nullptr)
|
||||
{
|
||||
boost::smatch m;
|
||||
if (boost::regex_match(*z, m, m_zoomRe) && m.size() == 2)
|
||||
{
|
||||
double zoom;
|
||||
VERIFY(strings::to_double(m[0].str(), zoom), ());
|
||||
info.SetZoom(zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Invalid z=", *z));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DoubleGISParser::DoubleGISParser()
|
||||
: m_pathRe(boost::regex("/" + kFloatIntCoord + "," + kFloatIntCoord + "/zoom/" + kFloatIntScale))
|
||||
, m_paramRe(boost::regex(kFloatIntCoord + "," + kFloatIntCoord + "/" + kFloatIntScale))
|
||||
{}
|
||||
|
||||
bool DoubleGISParser::Parse(url::Url const & url, GeoURLInfo & info) const
|
||||
{
|
||||
info.Reset();
|
||||
|
||||
// Try m=$lon,$lat/$zoom first
|
||||
auto const * value = url.GetParamValue("m");
|
||||
if (value && MatchLatLonZoom(*value, m_paramRe, 2, 1, 3, info))
|
||||
return true;
|
||||
|
||||
// Parse /$lon,$lat/zoom/$zoom from path next
|
||||
return MatchLatLonZoom(url.GetHostAndPath(), m_pathRe, 2, 1, 3, info);
|
||||
}
|
||||
|
||||
OpenStreetMapParser::OpenStreetMapParser() : m_regex(boost::regex(kIntScale + "/" + kFloatCoord + "/" + kFloatCoord)) {}
|
||||
|
||||
bool OpenStreetMapParser::Parse(url::Url const & url, GeoURLInfo & info) const
|
||||
{
|
||||
info.Reset();
|
||||
|
||||
auto const * mapV = url.GetParamValue("map");
|
||||
return (mapV && MatchLatLonZoom(*mapV, m_regex, 2, 3, 1, info));
|
||||
}
|
||||
|
||||
GeoURLInfo::GeoURLInfo()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool GeoURLInfo::IsLatLonValid() const
|
||||
{
|
||||
return m_lat != ms::LatLon::kInvalid && m_lon != ms::LatLon::kInvalid;
|
||||
}
|
||||
|
||||
void GeoURLInfo::Reset()
|
||||
{
|
||||
m_lat = ms::LatLon::kInvalid;
|
||||
m_lon = ms::LatLon::kInvalid;
|
||||
m_zoom = 0.0;
|
||||
m_query = "";
|
||||
m_label = "";
|
||||
}
|
||||
|
||||
void GeoURLInfo::SetZoom(double x)
|
||||
{
|
||||
if (x < 1.0)
|
||||
LOG(LWARNING, ("Invalid zoom:", x));
|
||||
else if (x > kMaxZoom)
|
||||
m_zoom = kMaxZoom;
|
||||
else
|
||||
m_zoom = x;
|
||||
}
|
||||
|
||||
bool GeoURLInfo::SetLat(double x)
|
||||
{
|
||||
if (mercator::ValidLat(x))
|
||||
{
|
||||
m_lat = x;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GeoURLInfo::SetLon(double x)
|
||||
{
|
||||
if (mercator::ValidLon(x))
|
||||
{
|
||||
m_lon = x;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnifiedParser::Parse(std::string const & raw, GeoURLInfo & res)
|
||||
{
|
||||
if (raw.starts_with("geo:"))
|
||||
return m_geoParser.Parse(raw, res);
|
||||
|
||||
url::Url url(raw);
|
||||
if (!url.IsValid())
|
||||
return false;
|
||||
|
||||
if (url.GetScheme() != "https" && url.GetScheme() != "http")
|
||||
return false;
|
||||
|
||||
if (MatchHost(url, "2gis") && m_dgParser.Parse(url, res))
|
||||
return true;
|
||||
else if (MatchHost(url, "openstreetmap") && m_osmParser.Parse(url, res))
|
||||
return true;
|
||||
// Fall through.
|
||||
|
||||
m_llParser.Reset(url, res);
|
||||
m_llParser({}, url.GetHostAndPath());
|
||||
url.ForEachParam(m_llParser);
|
||||
return m_llParser.IsValid();
|
||||
}
|
||||
|
||||
} // namespace geo
|
||||
105
libs/ge0/geo_url_parser.hpp
Normal file
105
libs/ge0/geo_url_parser.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/url.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace geo
|
||||
{
|
||||
|
||||
class GeoURLInfo
|
||||
{
|
||||
public:
|
||||
GeoURLInfo();
|
||||
|
||||
bool IsLatLonValid() const;
|
||||
void Reset();
|
||||
|
||||
void SetZoom(double x);
|
||||
bool SetLat(double x);
|
||||
bool SetLon(double x);
|
||||
|
||||
double m_lat;
|
||||
double m_lon;
|
||||
double m_zoom;
|
||||
std::string m_query;
|
||||
std::string m_label;
|
||||
};
|
||||
|
||||
class DoubleGISParser
|
||||
{
|
||||
public:
|
||||
DoubleGISParser();
|
||||
bool Parse(url::Url const & url, GeoURLInfo & info) const;
|
||||
|
||||
private:
|
||||
boost::regex m_pathRe;
|
||||
boost::regex m_paramRe;
|
||||
};
|
||||
|
||||
class OpenStreetMapParser
|
||||
{
|
||||
public:
|
||||
OpenStreetMapParser();
|
||||
bool Parse(url::Url const & url, GeoURLInfo & info) const;
|
||||
|
||||
private:
|
||||
boost::regex m_regex;
|
||||
};
|
||||
|
||||
class LatLonParser
|
||||
{
|
||||
public:
|
||||
LatLonParser();
|
||||
void Reset(url::Url const & url, GeoURLInfo & info);
|
||||
|
||||
bool IsValid() const;
|
||||
void operator()(std::string name, std::string const & value);
|
||||
|
||||
private:
|
||||
// Usually (lat, lon), but some providers use (lon, lat).
|
||||
static int constexpr kLLPriority = 5;
|
||||
// We do not try to guess the projection and do not interpret (x, y)
|
||||
// as Mercator coordinates in URLs. We simply use (y, x) for (lat, lon).
|
||||
static int constexpr kXYPriority = 6;
|
||||
static int constexpr kLatLonPriority = 7;
|
||||
|
||||
// Priority for accepting coordinates if we have many choices.
|
||||
// -1 - not initialized
|
||||
// 0 - coordinates in path;
|
||||
// x - priority for query type (greater is better)
|
||||
static int GetCoordinatesPriority(std::string const & token);
|
||||
|
||||
GeoURLInfo * m_info;
|
||||
bool m_swapLatLon;
|
||||
boost::regex m_regexp;
|
||||
int m_latPriority;
|
||||
int m_lonPriority;
|
||||
};
|
||||
|
||||
class GeoParser
|
||||
{
|
||||
public:
|
||||
GeoParser();
|
||||
bool Parse(std::string const & url, GeoURLInfo & info) const;
|
||||
|
||||
private:
|
||||
boost::regex m_latlonRe;
|
||||
boost::regex m_zoomRe;
|
||||
};
|
||||
|
||||
class UnifiedParser
|
||||
{
|
||||
public:
|
||||
bool Parse(std::string const & url, GeoURLInfo & info);
|
||||
|
||||
private:
|
||||
GeoParser m_geoParser;
|
||||
DoubleGISParser m_dgParser;
|
||||
OpenStreetMapParser m_osmParser;
|
||||
LatLonParser m_llParser;
|
||||
};
|
||||
|
||||
} // namespace geo
|
||||
187
libs/ge0/parser.cpp
Normal file
187
libs/ge0/parser.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#include "ge0/parser.hpp"
|
||||
|
||||
#include "ge0/url_generator.hpp"
|
||||
|
||||
#include "coding/url.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
Ge0Parser::Ge0Parser()
|
||||
{
|
||||
for (size_t i = 0; i < 256; ++i)
|
||||
m_base64ReverseCharTable[i] = 255;
|
||||
for (uint8_t i = 0; i < 64; ++i)
|
||||
{
|
||||
char c = Base64Char(i);
|
||||
m_base64ReverseCharTable[static_cast<uint8_t>(c)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool Ge0Parser::Parse(std::string const & url, Result & result)
|
||||
{
|
||||
// Original URL format:
|
||||
//
|
||||
// +------------------ 1 byte: zoom level
|
||||
// |+-------+--------- 9 bytes: lat,lon
|
||||
// || | +--+---- Variable number of bytes: point name
|
||||
// || | | |
|
||||
// cm://ZCoordba64/Name
|
||||
|
||||
// Alternative format (differs only in the prefix):
|
||||
// http://comaps.app/ZCoordba64/Name
|
||||
|
||||
for (auto prefix : kGe0Prefixes)
|
||||
if (url.starts_with(prefix))
|
||||
return ParseAfterPrefix(url, prefix.size(), result);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ge0Parser::ParseAfterPrefix(std::string const & url, size_t from, Result & result)
|
||||
{
|
||||
size_t constexpr kEncodedZoomAndCoordinatesLength = 10;
|
||||
if (url.size() < from + kEncodedZoomAndCoordinatesLength)
|
||||
return false;
|
||||
|
||||
size_t constexpr kMaxNameLength = 256;
|
||||
|
||||
size_t const posZoom = from;
|
||||
size_t const posLatLon = posZoom + 1;
|
||||
size_t const posName = from + kEncodedZoomAndCoordinatesLength + 1;
|
||||
size_t const lengthLatLon = posName - posLatLon - 1;
|
||||
|
||||
uint8_t const zoomI = DecodeBase64Char(url[posZoom]);
|
||||
if (zoomI >= 64)
|
||||
return false;
|
||||
result.m_zoomLevel = DecodeZoom(zoomI);
|
||||
|
||||
if (!DecodeLatLon(url.substr(posLatLon, lengthLatLon), result.m_lat, result.m_lon))
|
||||
return false;
|
||||
|
||||
ASSERT(mercator::ValidLat(result.m_lat), (result.m_lat));
|
||||
ASSERT(mercator::ValidLon(result.m_lon), (result.m_lon));
|
||||
|
||||
if (url.size() >= posName)
|
||||
{
|
||||
CHECK_GREATER(posName, 0, ());
|
||||
if (url[posName - 1] != '/')
|
||||
return false;
|
||||
result.m_name = DecodeName(url.substr(posName, std::min(url.size() - posName, kMaxNameLength)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t Ge0Parser::DecodeBase64Char(char const c)
|
||||
{
|
||||
return m_base64ReverseCharTable[static_cast<uint8_t>(c)];
|
||||
}
|
||||
|
||||
double Ge0Parser::DecodeZoom(uint8_t const zoomByte)
|
||||
{
|
||||
// Coding zoom - int newZoom = ((oldZoom - 4) * 4)
|
||||
return static_cast<double>(zoomByte) / 4 + 4;
|
||||
}
|
||||
|
||||
bool Ge0Parser::DecodeLatLon(std::string const & s, double & lat, double & lon)
|
||||
{
|
||||
int latInt = 0;
|
||||
int lonInt = 0;
|
||||
if (!DecodeLatLonToInt(s, latInt, lonInt))
|
||||
return false;
|
||||
|
||||
lat = DecodeLatFromInt(latInt, (1 << kMaxCoordBits) - 1);
|
||||
lon = DecodeLonFromInt(lonInt, (1 << kMaxCoordBits) - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ge0Parser::DecodeLatLonToInt(std::string const & s, int & lat, int & lon)
|
||||
{
|
||||
int shift = kMaxCoordBits - 3;
|
||||
for (size_t i = 0; i < s.size(); ++i, shift -= 3)
|
||||
{
|
||||
uint8_t const a = DecodeBase64Char(s[i]);
|
||||
if (a >= 64)
|
||||
return false;
|
||||
|
||||
int const lat1 = (((a >> 5) & 1) << 2 | ((a >> 3) & 1) << 1 | ((a >> 1) & 1));
|
||||
int const lon1 = (((a >> 4) & 1) << 2 | ((a >> 2) & 1) << 1 | (a & 1));
|
||||
lat |= lat1 << shift;
|
||||
lon |= lon1 << shift;
|
||||
}
|
||||
double const middleOfSquare = 1 << (3 * (kMaxPointBytes - s.size()) - 1);
|
||||
lat += middleOfSquare;
|
||||
lon += middleOfSquare;
|
||||
return true;
|
||||
}
|
||||
|
||||
double Ge0Parser::DecodeLatFromInt(int const lat, int const maxValue)
|
||||
{
|
||||
return static_cast<double>(lat) / maxValue * 180 - 90;
|
||||
}
|
||||
|
||||
double Ge0Parser::DecodeLonFromInt(int const lon, int const maxValue)
|
||||
{
|
||||
return static_cast<double>(lon) / (maxValue + 1.0) * 360.0 - 180;
|
||||
}
|
||||
|
||||
std::string Ge0Parser::DecodeName(std::string name)
|
||||
{
|
||||
ValidateName(name);
|
||||
name = url::UrlDecode(name);
|
||||
SpacesToUnderscore(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
void Ge0Parser::SpacesToUnderscore(std::string & name)
|
||||
{
|
||||
for (size_t i = 0; i < name.size(); ++i)
|
||||
if (name[i] == ' ')
|
||||
name[i] = '_';
|
||||
else if (name[i] == '_')
|
||||
name[i] = ' ';
|
||||
}
|
||||
|
||||
void Ge0Parser::ValidateName(std::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 Ge0Parser::IsHexChar(char const a)
|
||||
{
|
||||
return ((a >= '0' && a <= '9') || (a >= 'A' && a <= 'F') || (a >= 'a' && a <= 'f'));
|
||||
}
|
||||
|
||||
std::string DebugPrint(Ge0Parser::Result const & r)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "ParseResult [";
|
||||
oss << "zoom=" << r.m_zoomLevel << ", ";
|
||||
oss << "lat=" << r.m_lat << ", ";
|
||||
oss << "lon=" << r.m_lon << ", ";
|
||||
oss << "name=" << r.m_name << "]";
|
||||
return oss.str();
|
||||
}
|
||||
} // namespace ge0
|
||||
47
libs/ge0/parser.hpp
Normal file
47
libs/ge0/parser.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
class Ge0Parser
|
||||
{
|
||||
public:
|
||||
// Used by map/mwm_url.cpp.
|
||||
static constexpr std::array<std::string_view, 6> kGe0Prefixes = {
|
||||
{"https://comaps.at/", "cm://", "http://comaps.at/", "ge0://", "http://ge0.me/", "https://ge0.me/"}};
|
||||
|
||||
struct Result
|
||||
{
|
||||
double m_zoomLevel = 0.0;
|
||||
double m_lat = 0.0;
|
||||
double m_lon = 0.0;
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
Ge0Parser();
|
||||
|
||||
bool Parse(std::string const & url, Result & result);
|
||||
bool ParseAfterPrefix(std::string const & url, size_t from, Result & result);
|
||||
|
||||
protected:
|
||||
uint8_t DecodeBase64Char(char const c);
|
||||
static double DecodeZoom(uint8_t const zoomByte);
|
||||
bool DecodeLatLon(std::string const & s, double & lat, double & lon);
|
||||
bool DecodeLatLonToInt(std::string const & s, int & lat, int & lon);
|
||||
double DecodeLatFromInt(int const lat, int const maxValue);
|
||||
double DecodeLonFromInt(int const lon, int const maxValue);
|
||||
std::string DecodeName(std::string name);
|
||||
void SpacesToUnderscore(std::string & name);
|
||||
void ValidateName(std::string & name);
|
||||
static bool IsHexChar(char const a);
|
||||
|
||||
private:
|
||||
uint8_t m_base64ReverseCharTable[256];
|
||||
};
|
||||
|
||||
std::string DebugPrint(Ge0Parser::Result const & r);
|
||||
} // namespace ge0
|
||||
203
libs/ge0/url_generator.cpp
Normal file
203
libs/ge0/url_generator.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include "ge0/url_generator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "coding/url.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Replaces ' ' with '_' and vice versa.
|
||||
std::string TransformName(std::string const & s)
|
||||
{
|
||||
std::string result = s;
|
||||
for (auto & c : result)
|
||||
if (c == ' ')
|
||||
c = '_';
|
||||
else if (c == '_')
|
||||
c = ' ';
|
||||
return result;
|
||||
}
|
||||
|
||||
// URL-encodes string |s|.
|
||||
// URL restricted / unsafe / unwise characters are %-encoded.
|
||||
// See rfc3986, rfc1738, rfc2396.
|
||||
//
|
||||
// Not compatible with the url encode function from coding/.
|
||||
std::string UrlEncodeString(std::string const & s)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(s.size() * 3 + 1);
|
||||
for (size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
auto const c = static_cast<unsigned char>(s[i]);
|
||||
switch (c)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B:
|
||||
case 0x0C:
|
||||
case 0x0D:
|
||||
case 0x0E:
|
||||
case 0x0F:
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B:
|
||||
case 0x1C:
|
||||
case 0x1D:
|
||||
case 0x1E:
|
||||
case 0x1F:
|
||||
case 0x7F:
|
||||
case ' ':
|
||||
case '<':
|
||||
case '>':
|
||||
case '#':
|
||||
case '%':
|
||||
case '"':
|
||||
case '!':
|
||||
case '*':
|
||||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
case ';':
|
||||
case ':':
|
||||
case '@':
|
||||
case '&':
|
||||
case '=':
|
||||
case '+':
|
||||
case '$':
|
||||
case ',':
|
||||
case '/':
|
||||
case '?':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '|':
|
||||
case '^':
|
||||
case '`':
|
||||
result += '%';
|
||||
result += "0123456789ABCDEF"[c >> 4];
|
||||
result += "0123456789ABCDEF"[c & 15];
|
||||
break;
|
||||
default: result += s[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
std::string GenerateShortShowMapUrl(double lat, double lon, double zoom, std::string const & name)
|
||||
{
|
||||
size_t constexpr schemaLength = 5; // strlen("cm://")
|
||||
std::string urlSample = "cm://ZCoordba64";
|
||||
|
||||
int const zoomI = (zoom <= 4 ? 0 : (zoom >= 19.75 ? 63 : static_cast<int>((zoom - 4) * 4)));
|
||||
urlSample[schemaLength] = Base64Char(zoomI);
|
||||
|
||||
LatLonToString(lat, lon, urlSample.data() + schemaLength + 1, 9);
|
||||
|
||||
if (!name.empty())
|
||||
{
|
||||
urlSample += '/';
|
||||
urlSample += UrlEncodeString(TransformName(name));
|
||||
}
|
||||
|
||||
return urlSample;
|
||||
}
|
||||
|
||||
std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "geo:" << std::fixed << std::setprecision(7) << lat << ',' << lon << "?z=" << std::setprecision(1) << zoom;
|
||||
// For Google Maps compatibility, otherwise it doesn't select the point on the map, only shows the area.
|
||||
oss << "&q=" << std::setprecision(7) << lat << ',' << lon;
|
||||
|
||||
if (!name.empty())
|
||||
oss << '(' << url::UrlEncode(name) << ')';
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
char Base64Char(int x)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(x, 0, ());
|
||||
CHECK_LESS(x, 64, ());
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"[x];
|
||||
}
|
||||
|
||||
// Map latitude: [-90, 90] -> [0, maxValue]
|
||||
int LatToInt(double lat, int maxValue)
|
||||
{
|
||||
// M = maxValue, L = maxValue-1
|
||||
// lat: -90 90
|
||||
// x: 0 1 2 L M
|
||||
// |--+--|--+--|--...--|--+--|
|
||||
// 000111111222222...LLLLLMMMM
|
||||
|
||||
double const x = (lat + 90.0) / 180.0 * maxValue;
|
||||
return x < 0 ? 0 : (x > maxValue ? maxValue : math::iround(x));
|
||||
}
|
||||
|
||||
// Make lon in [-180, 180)
|
||||
double LonIn180180(double lon)
|
||||
{
|
||||
if (lon >= 0)
|
||||
return fmod(lon + 180.0, 360.0) - 180.0;
|
||||
|
||||
// Handle the case of l = -180
|
||||
double const l = fmod(lon - 180.0, 360.0) + 180.0;
|
||||
return l < 180.0 ? l : l - 360.0;
|
||||
}
|
||||
|
||||
// Map longitude: [-180, 180) -> [0, maxValue]
|
||||
int LonToInt(double lon, int maxValue)
|
||||
{
|
||||
double const x = (LonIn180180(lon) + 180.0) / 360.0 * (maxValue + 1.0) + 0.5;
|
||||
return (x <= 0 || x >= maxValue + 1) ? 0 : static_cast<int>(x);
|
||||
}
|
||||
|
||||
void LatLonToString(double lat, double lon, char * s, size_t nBytes)
|
||||
{
|
||||
if (nBytes > kMaxPointBytes)
|
||||
nBytes = kMaxPointBytes;
|
||||
|
||||
int const latI = LatToInt(lat, (1 << kMaxCoordBits) - 1);
|
||||
int const lonI = LonToInt(lon, (1 << kMaxCoordBits) - 1);
|
||||
|
||||
size_t i;
|
||||
int shift;
|
||||
for (i = 0, shift = kMaxCoordBits - 3; i < nBytes; ++i, shift -= 3)
|
||||
{
|
||||
int const latBits = latI >> shift & 7;
|
||||
int const lonBits = lonI >> shift & 7;
|
||||
|
||||
int const nextByte = (latBits >> 2 & 1) << 5 | (lonBits >> 2 & 1) << 4 | (latBits >> 1 & 1) << 3 |
|
||||
(lonBits >> 1 & 1) << 2 | (latBits & 1) << 1 | (lonBits & 1);
|
||||
|
||||
s[i] = Base64Char(nextByte);
|
||||
}
|
||||
}
|
||||
} // namespace ge0
|
||||
45
libs/ge0/url_generator.hpp
Normal file
45
libs/ge0/url_generator.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ge0
|
||||
{
|
||||
// Max number of base64 bytes to encode a geo point.
|
||||
inline static int constexpr kMaxPointBytes = 10;
|
||||
inline static int constexpr kMaxCoordBits = kMaxPointBytes * 3;
|
||||
|
||||
// Generates a short url.
|
||||
//
|
||||
// URL format:
|
||||
//
|
||||
// +------------------ 1 byte: zoom level
|
||||
// |+-------+--------- 9 bytes: lat,lon
|
||||
// || | +--+---- Variable number of bytes: point name
|
||||
// || | | |
|
||||
// cm://ZCoordba64/Name
|
||||
std::string GenerateShortShowMapUrl(double lat, double lon, double zoomLevel, std::string const & name);
|
||||
|
||||
// Generates a geo: uri.
|
||||
//
|
||||
// - https://datatracker.ietf.org/doc/html/rfc5870
|
||||
// - https://developer.android.com/guide/components/intents-common#Maps
|
||||
// - https://developers.google.com/maps/documentation/urls/android-intents
|
||||
//
|
||||
// URL format:
|
||||
//
|
||||
// +-------------------------------- lat
|
||||
// | +-------------------- lon
|
||||
// | | +---- zoom
|
||||
// | | | +-- url-encoded name
|
||||
// | | | |
|
||||
// | | | |
|
||||
// geo:54.683486138,25.289361259&z=14(Forto%20dvaras)
|
||||
std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name);
|
||||
|
||||
// Exposed for testing.
|
||||
char Base64Char(int x);
|
||||
int LatToInt(double lat, int maxValue);
|
||||
double LonIn180180(double lon);
|
||||
int LonToInt(double lon, int maxValue);
|
||||
void LatLonToString(double lat, double lon, char * s, size_t nBytes);
|
||||
} // namespace ge0
|
||||
Loading…
Add table
Add a link
Reference in a new issue