From 9dd1d67db4bae0d0967d3e9ecc250a5c93cc6afc Mon Sep 17 00:00:00 2001 From: Yury Melnichek Date: Wed, 28 Nov 2012 18:33:43 +0100 Subject: [PATCH] Maps URL Scheme API base parser --- map/map.pro | 2 + map/map_tests/mwm_url_tests.cpp | 80 ++++++++++++++++++++++++++++ map/mwm_url.cpp | 92 +++++++++++++++++++++++++++++++++ map/mwm_url.hpp | 41 +++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 map/map_tests/mwm_url_tests.cpp create mode 100644 map/mwm_url.cpp create mode 100644 map/mwm_url.hpp diff --git a/map/map.pro b/map/map.pro index 7eedc8d29a..aa01fefaba 100644 --- a/map/map.pro +++ b/map/map.pro @@ -50,6 +50,7 @@ HEADERS += \ move_screen_task.hpp \ change_viewport_task.hpp \ dialog_settings.hpp \ + mwm_url.hpp \ SOURCES += \ feature_vec_model.cpp \ @@ -91,6 +92,7 @@ SOURCES += \ move_screen_task.cpp \ change_viewport_task.cpp \ dialog_settings.cpp \ + mwm_url.cpp \ !iphone*:!bada*:!android* { HEADERS += qgl_render_context.hpp diff --git a/map/map_tests/mwm_url_tests.cpp b/map/map_tests/mwm_url_tests.cpp new file mode 100644 index 0000000000..8d37b6e277 --- /dev/null +++ b/map/map_tests/mwm_url_tests.cpp @@ -0,0 +1,80 @@ +#include "../../testing/testing.hpp" + +#include "../mwm_url.hpp" +#include "../../coding/uri.hpp" + + +using namespace url_scheme; + +UNIT_TEST(MapApiSmoke) +{ + Uri uri("mapswithme://map?ll=38.970559,-9.419289&ignoreThisParam=Yes&z=17&n=Point%20Name"); + TEST(uri.IsValid(), ()); + + ParsedMapApi api(uri); + TEST(api.IsValid(), ()); + TEST_EQUAL(api.GetPoints().size(), 1, ()); + TEST_EQUAL(api.GetPoints()[0].m_lat, 38.970559, ()); + TEST_EQUAL(api.GetPoints()[0].m_lon, -9.419289, ()); + TEST_EQUAL(api.GetPoints()[0].m_title, "Point Name", ()); +} + +UNIT_TEST(MapApiInvalidUrl) +{ + TEST(!ParsedMapApi(Uri("competitors://map?ll=12.3,34.54")).IsValid(), ()); + TEST(!ParsedMapApi(Uri("mapswithme://ggg?ll=12.3,34.54")).IsValid(), ()); + TEST(!ParsedMapApi(Uri("mapswithme://")).IsValid(), ("No path")); + TEST(!ParsedMapApi(Uri("mapswithme://map?")).IsValid(), ("No parameters")); + TEST(!ParsedMapApi(Uri("mapswithme://map?ll=23.55")).IsValid(), ("No longtitude")); + TEST(!ParsedMapApi(Uri("mapswithme://map?ll=1,2,3")).IsValid(), ("Too many values for ll")); +} + +UNIT_TEST(MapApiLatLonLimits) +{ + TEST(!ParsedMapApi(Uri("mapswithme://map?ll=-91,10")).IsValid(), ("Invalid latitude")); + TEST(!ParsedMapApi(Uri("mapswithme://map?ll=523.55,10")).IsValid(), ("Invalid latitude")); + TEST(ParsedMapApi(Uri("mapswithme://map?ll=23.55,450")).IsValid(), ("But valid longtitude")); + TEST(ParsedMapApi(Uri("mapswithme://map?ll=23.55,-450")).IsValid(), ("But valid longtitude")); +} + +UNIT_TEST(MapApiPointNameBeforeLatLon) +{ + ParsedMapApi api(Uri("mapswithme://map?n=Name&ll=1,2")); + TEST(api.IsValid(), ()); + TEST_EQUAL(api.GetPoints().size(), 1, ()); + TEST_EQUAL(api.GetPoints()[0].m_title, "", ()); +} + +UNIT_TEST(MapApiPointNameOverwritten) +{ + ParsedMapApi api(Uri("mapswithme://map?ll=1,2&n=A&n=B")); + TEST(api.IsValid(), ()); + TEST_EQUAL(api.GetPoints().size(), 1, ()); + TEST_EQUAL(api.GetPoints()[0].m_title, "B", ()); +} + +UNIT_TEST(MapApiMultiplePoints) +{ + ParsedMapApi api(Uri("mapswithme://map?ll=1.1,1.2&n=A&ll=2.1,2.2&ll=-3.1,-3.2&n=C")); + TEST(api.IsValid(), ()); + TEST_EQUAL(api.GetPoints().size(), 3, ()); + TEST_EQUAL(api.GetPoints()[0].m_lat, 1.1, ()); + TEST_EQUAL(api.GetPoints()[0].m_lon, 1.2, ()); + TEST_EQUAL(api.GetPoints()[0].m_title, "A", ()); + TEST_EQUAL(api.GetPoints()[1].m_title, "", ()); + TEST_EQUAL(api.GetPoints()[1].m_lat, 2.1, ()); + TEST_EQUAL(api.GetPoints()[1].m_lon, 2.2, ()); + TEST_EQUAL(api.GetPoints()[2].m_title, "C", ()); + TEST_EQUAL(api.GetPoints()[2].m_lat, -3.1, ()); + TEST_EQUAL(api.GetPoints()[2].m_lon, -3.2, ()); +} + +UNIT_TEST(MapApiInvalidPointLatLonButValidOtherParts) +{ + ParsedMapApi api(Uri("mapswithme://map?ll=1,1,1&n=A&ll=2,2&n=B&ll=3,3,3&n=C")); + TEST(api.IsValid(), ()); + TEST_EQUAL(api.GetPoints().size(), 1, ()); + TEST_EQUAL(api.GetPoints()[0].m_lat, 2, ()); + TEST_EQUAL(api.GetPoints()[0].m_lon, 2, ()); + TEST_EQUAL(api.GetPoints()[0].m_title, "B", ()); + } diff --git a/map/mwm_url.cpp b/map/mwm_url.cpp new file mode 100644 index 0000000000..6a7541d638 --- /dev/null +++ b/map/mwm_url.cpp @@ -0,0 +1,92 @@ +#include "mwm_url.hpp" + +#include "../indexer/mercator.hpp" + +#include "../coding/uri.hpp" + +#include "../base/logging.hpp" +#include "../base/string_utils.hpp" + +#include "../std/algorithm.hpp" +#include "../std/bind.hpp" + +using namespace url_scheme; + +namespace +{ + +static int const INVALID_LAT_VALUE = -1000; + +bool IsInvalidApiPoint(ApiPoint const & p) { return p.m_lat == INVALID_LAT_VALUE; } + +} // unnames namespace + +ParsedMapApi::ParsedMapApi(Uri const & uri) +{ + if (!Parse(uri)) + { + m_points.clear(); + } +} + +bool ParsedMapApi::IsValid() const +{ + return !m_points.empty(); +} + +bool ParsedMapApi::Parse(Uri const & uri) +{ + if (uri.GetScheme() != "mapswithme" || uri.GetPath() != "map") + return false; + + uri.ForEachKeyValue(bind(&ParsedMapApi::AddKeyValue, this, _1, _2)); + m_points.erase(remove_if(m_points.begin(), m_points.end(), &IsInvalidApiPoint), m_points.end()); + + return true; +} + +void ParsedMapApi::AddKeyValue(string const & key, string const & value) +{ + if (key == "ll") + { + m_points.push_back(ApiPoint()); + m_points.back().m_lat = INVALID_LAT_VALUE; + + size_t const firstComma = value.find(','); + if (firstComma == string::npos) + { + LOG(LWARNING, ("Map API: no comma between lat and lon for 'll' key", key, value)); + return; + } + + if (value.find(',', firstComma + 1) != string::npos) + { + LOG(LWARNING, ("Map API: more than one comma in a value for 'll' key", key, value)); + return; + } + + double lat, lon; + if (!strings::to_double(value.substr(0, firstComma), lat) || + !strings::to_double(value.substr(firstComma + 1), lon)) + { + LOG(LWARNING, ("Map API: can't parse lat,lon for 'll' key", key, value)); + return; + } + + if (!MercatorBounds::ValidLat(lat) || !MercatorBounds::ValidLon(lon)) + { + LOG(LWARNING, ("Map API: incorrect value for lat and/or lon", key, value, lat, lon)); + return; + } + + m_points.back().m_lat = lat; + m_points.back().m_lon = lon; + } + else if (key == "n") + { + if (!m_points.empty()) + m_points.back().m_title = value; + else + LOG(LWARNING, ("Map API: Point name with no point. 'll' should come first!")); + } +} diff --git a/map/mwm_url.hpp b/map/mwm_url.hpp new file mode 100644 index 0000000000..317dfc5311 --- /dev/null +++ b/map/mwm_url.hpp @@ -0,0 +1,41 @@ +#include "../geometry/rect2d.hpp" +#include "../std/string.hpp" + +namespace url_scheme +{ + +class Uri; + +struct ApiPoint +{ + double m_lat; + double m_lon; + string m_title; + string m_url; +}; + +/// Handles mapswithme://map?params - everything related to displaying info on a map +class ParsedMapApi +{ +public: + ParsedMapApi(Uri const & uri); + bool IsValid() const; + vector const & GetPoints() const { return m_points; } + +private: + bool Parse(Uri const & uri); + void AddKeyValue(string const & key, string const & value); + + vector m_points; + /* + string m_title; + string m_backTitle; + string m_backUrl; + // Calculated from zoom parameter or from m_points + m2::RectD m_showRect; + + // vector m_iconData; + */ +}; + +}