diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 9efab79d49..295a259809 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -540,6 +540,37 @@ Java_com_mapswithme_maps_Framework_nativeClearApiPoints(JNIEnv * env, jclass cla guard.m_controller.Clear(); } +JNIEXPORT jint JNICALL +Java_com_mapswithme_maps_Framework_nativeParseAndSetApiUrl(JNIEnv * env, jclass clazz, jstring url) +{ + return static_cast(frm()->ParseAndSetApiURL(jni::ToNativeString(env, url))); +} + +JNIEXPORT jobject JNICALL +Java_com_mapswithme_maps_Framework_nativeGetParsedRoutingData(JNIEnv * env, jclass clazz) +{ + using namespace url_scheme; + static jclass const pointClazz = jni::GetGlobalClassRef(env, "com/mapswithme/maps/api/RoutePoint"); + // Java signature : RoutePoint(double lat, double lon, String name) + static jmethodID const pointConstructor = jni::GetConstructorID(env, pointClazz, "(DDLjava/lang/String;)V"); + + static jclass const routeDataClazz = jni::GetGlobalClassRef(env, "com/mapswithme/maps/api/ParsedRoutingData"); + // Java signature : ParsedRoutingData(RoutePoint[] points, int routerType) { + static jmethodID const routeDataConstructor = jni::GetConstructorID(env, routeDataClazz, "([Lcom/mapswithme/maps/api/RoutePoint;I)V"); + + auto const & routingData = frm()->GetParsedRoutingData(); + jobjectArray points = jni::ToJavaArray(env, pointClazz, routingData.m_points, + [](JNIEnv * env, RoutePoint const & point) + { + jni::TScopedLocalRef const name(env, jni::ToJavaString(env, point.m_name)); + return env->NewObject(pointClazz, pointConstructor, + MercatorBounds::YToLat(point.m_org.y), + MercatorBounds::XToLon(point.m_org.x), name.get()); + }); + + return env->NewObject(routeDataClazz, routeDataConstructor, points, routingData.m_type); +} + JNIEXPORT void JNICALL Java_com_mapswithme_maps_Framework_nativeSetMapObjectListener(JNIEnv * env, jclass clazz, jobject jListener) { diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java index 36b26a9156..2929374217 100644 --- a/android/src/com/mapswithme/maps/Framework.java +++ b/android/src/com/mapswithme/maps/Framework.java @@ -8,6 +8,8 @@ import android.support.annotation.UiThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import com.mapswithme.maps.api.ParsedRoutingData; +import com.mapswithme.maps.api.ParsedUrlMwmRequest; import com.mapswithme.maps.bookmarks.data.DistanceAndAzimut; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.routing.RoutingInfo; @@ -103,6 +105,9 @@ public class Framework public static native long nativeGetDataVersion(); public static native void nativeClearApiPoints(); + @ParsedUrlMwmRequest.ParsingResult + public static native int nativeParseAndSetApiUrl(String url); + public static native ParsedRoutingData nativeGetParsedRoutingData(); public static native void nativeDeactivatePopup(); diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index a96994b9ce..b880483150 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -26,6 +26,9 @@ import com.mapswithme.maps.Framework.MapObjectListener; import com.mapswithme.maps.activity.CustomNavigateUpListener; import com.mapswithme.maps.ads.LikesManager; import com.mapswithme.maps.api.ParsedMwmRequest; +import com.mapswithme.maps.api.ParsedRoutingData; +import com.mapswithme.maps.api.ParsedUrlMwmRequest; +import com.mapswithme.maps.api.RoutePoint; import com.mapswithme.maps.base.BaseMwmFragmentActivity; import com.mapswithme.maps.base.OnBackPressListener; import com.mapswithme.maps.bookmarks.BookmarkCategoriesActivity; @@ -1137,7 +1140,27 @@ public class MwmActivity extends BaseMwmFragmentActivity @Override public boolean run(MwmActivity target) { - return MapFragment.nativeShowMapForUrl(mUrl); + final @ParsedUrlMwmRequest.ParsingResult int result = Framework.nativeParseAndSetApiUrl(mUrl); + switch (result) + { + case ParsedUrlMwmRequest.RESULT_INCORRECT: + // TODO handle error + break; + case ParsedUrlMwmRequest.RESULT_MAP: + return MapFragment.nativeShowMapForUrl(mUrl); + case ParsedUrlMwmRequest.RESULT_ROUTE: + final ParsedRoutingData data = Framework.nativeGetParsedRoutingData(); + RoutingController.get().setRouterType(data.mRouterType); + final RoutePoint from = data.mPoints[0]; + final RoutePoint to = data.mPoints[1]; + RoutingController.get().prepare(new MapObject(MapObject.API_POINT, from.mName, "", "", + from.mLat, from.mLon, ""), + new MapObject(MapObject.API_POINT, to.mName, "", "", + to.mLat, to.mLon, "")); + return true; + } + + return false; } } diff --git a/android/src/com/mapswithme/maps/api/ParsedRoutingData.java b/android/src/com/mapswithme/maps/api/ParsedRoutingData.java new file mode 100644 index 0000000000..206b46786e --- /dev/null +++ b/android/src/com/mapswithme/maps/api/ParsedRoutingData.java @@ -0,0 +1,18 @@ +package com.mapswithme.maps.api; + +import com.mapswithme.maps.Framework; + +/** + * Represents Framework::ParsedRoutingData from core. + */ +public class ParsedRoutingData +{ + public final RoutePoint[] mPoints; + @Framework.RouterType + public final int mRouterType; + + public ParsedRoutingData(RoutePoint[] points, int routerType) { + this.mPoints = points; + this.mRouterType = routerType; + } +} diff --git a/android/src/com/mapswithme/maps/api/ParsedUrlMwmRequest.java b/android/src/com/mapswithme/maps/api/ParsedUrlMwmRequest.java new file mode 100644 index 0000000000..84f35f4ec2 --- /dev/null +++ b/android/src/com/mapswithme/maps/api/ParsedUrlMwmRequest.java @@ -0,0 +1,38 @@ +package com.mapswithme.maps.api; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents url_scheme::ParsedMapApi::ParsingResult from core. + */ +public class ParsedUrlMwmRequest +{ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_INCORRECT, RESULT_MAP, RESULT_ROUTE}) + public @interface ParsingResult {} + + public static final int RESULT_INCORRECT = 0; + public static final int RESULT_MAP = 1; + public static final int RESULT_ROUTE = 2; + + public final RoutePoint[] mRoutePoints; + public final String mGlobalUrl; + public final String mAppTitle; + public final int mVersion; + public final double mZoomLevel; + public final boolean mGoBackOnBalloonClick; + public final boolean mIsValid; + + public ParsedUrlMwmRequest(RoutePoint[] routePoints, String globalUrl, String appTitle, int version, double zoomLevel, boolean goBackOnBalloonClick, boolean isValid) { + this.mRoutePoints = routePoints; + this.mGlobalUrl = globalUrl; + this.mAppTitle = appTitle; + this.mVersion = version; + this.mZoomLevel = zoomLevel; + this.mGoBackOnBalloonClick = goBackOnBalloonClick; + this.mIsValid = isValid; + } +} diff --git a/android/src/com/mapswithme/maps/api/RoutePoint.java b/android/src/com/mapswithme/maps/api/RoutePoint.java new file mode 100644 index 0000000000..cad6c0ce68 --- /dev/null +++ b/android/src/com/mapswithme/maps/api/RoutePoint.java @@ -0,0 +1,18 @@ +package com.mapswithme.maps.api; + +/** + * Represents url_scheme::RoutePoint from core. + */ +public class RoutePoint +{ + public final double mLat; + public final double mLon; + public final String mName; + + public RoutePoint(double lat, double lon, String name) + { + mLat = lat; + mLon = lon; + mName = name; + } +} diff --git a/android/src/com/mapswithme/maps/routing/RoutingController.java b/android/src/com/mapswithme/maps/routing/RoutingController.java index 2d2a94d60b..7fb4d4443a 100644 --- a/android/src/com/mapswithme/maps/routing/RoutingController.java +++ b/android/src/com/mapswithme/maps/routing/RoutingController.java @@ -254,7 +254,7 @@ public class RoutingController Framework.nativeBuildRoute(mStartPoint.getLat(), mStartPoint.getLon(), mEndPoint.getLat(), mEndPoint.getLon()); } - private void showDisclaimer(final MapObject endPoint) + private void showDisclaimer(final MapObject startPoint, final MapObject endPoint) { StringBuilder builder = new StringBuilder(); for (int resId : new int[] { R.string.dialog_routing_disclaimer_priority, R.string.dialog_routing_disclaimer_precision, @@ -273,23 +273,28 @@ public class RoutingController public void onClick(DialogInterface dlg, int which) { Config.acceptRoutingDisclaimer(); - prepare(endPoint); + prepare(startPoint, endPoint); } }).show(); } public void prepare(@Nullable MapObject endPoint) + { + prepare(LocationHelper.INSTANCE.getMyPosition(), endPoint); + } + + public void prepare(@Nullable MapObject startPoint, @Nullable MapObject endPoint) { mLogger.d("prepare (" + (endPoint == null ? "route)" : "p2p)")); if (!Config.isRoutingDisclaimerAccepted()) { - showDisclaimer(endPoint); + showDisclaimer(startPoint, endPoint); return; } cancel(); - mStartPoint = LocationHelper.INSTANCE.getMyPosition(); + mStartPoint = startPoint; mEndPoint = endPoint; setState(State.PREPARE); @@ -655,7 +660,7 @@ public class RoutingController checkAndBuildRoute(); } - void setRouterType(@Framework.RouterType int router) + public void setRouterType(@Framework.RouterType int router) { mLogger.d("setRouterType: " + mLastRouterType + " -> " + router); diff --git a/coding/coding_tests/uri_test.cpp b/coding/coding_tests/uri_test.cpp index 9a46296aac..c6676ea112 100644 --- a/coding/coding_tests/uri_test.cpp +++ b/coding/coding_tests/uri_test.cpp @@ -33,12 +33,13 @@ public: private: - void AddTestValue(string const & key, string const & value) + bool AddTestValue(string const & key, string const & value) { TEST(!m_keyValuePairs.empty(), ("Failed for uri = ", m_uri, "Passed KV = ", key, value)); TEST_EQUAL(m_keyValuePairs.front().first, key, ()); TEST_EQUAL(m_keyValuePairs.front().second, value, ()); m_keyValuePairs.pop(); + return true; } string m_uri, m_scheme, m_path; diff --git a/coding/uri.cpp b/coding/uri.cpp index 57242042ac..4b98a96839 100644 --- a/coding/uri.cpp +++ b/coding/uri.cpp @@ -47,7 +47,7 @@ bool Uri::Parse() return true; } -void Uri::ForEachKeyValue(CallbackT const & callback) const +bool Uri::ForEachKeyValue(TCallback const & callback) const { // parse query for keys and values size_t const count = m_url.size(); @@ -71,11 +71,13 @@ void Uri::ForEachKeyValue(CallbackT const & callback) const else key = UrlDecode(m_url.substr(start, end - start)); - callback(key, value); + if (!callback(key, value)) + return false; } start = end + 1; } + return true; } } diff --git a/coding/uri.hpp b/coding/uri.hpp index e1ee611000..8d2bae7d20 100644 --- a/coding/uri.hpp +++ b/coding/uri.hpp @@ -13,7 +13,7 @@ namespace url_scheme class Uri { public: - typedef function CallbackT; + using TCallback = function; explicit Uri(string const & uri) : m_url(uri) { Init(); } Uri(char const * uri, size_t size) : m_url(uri, uri + size) { Init(); } @@ -21,8 +21,7 @@ public: string const & GetScheme() const { return m_scheme; } string const & GetPath() const { return m_path; } bool IsValid() const { return !m_scheme.empty(); } - - void ForEachKeyValue(CallbackT const & callback) const; + bool ForEachKeyValue(TCallback const & callback) const; private: void Init(); diff --git a/iphone/Maps/Classes/MapsAppDelegate.mm b/iphone/Maps/Classes/MapsAppDelegate.mm index 3c14c2ffa2..b9d1894514 100644 --- a/iphone/Maps/Classes/MapsAppDelegate.mm +++ b/iphone/Maps/Classes/MapsAppDelegate.mm @@ -254,11 +254,36 @@ using namespace osm_auth_ios; } else if (m_mwmURL) { - if (f.ShowMapForURL([m_mwmURL UTF8String])) + using namespace url_scheme; + + string const url = m_mwmURL.UTF8String; + auto const parsingType = f.ParseAndSetApiURL(url); + switch (parsingType) { - [[Statistics instance] logApiUsage:m_sourceApplication]; + case ParsedMapApi::ParsingResult::Incorrect: + LOG(LWARNING, ("Incorrect parsing result for url:", url)); + break; + case ParsedMapApi::ParsingResult::Route: + { + auto const parsedData = f.GetParsedRoutingData(); + f.SetRouter(parsedData.m_type); + auto const points = parsedData.m_points; + auto const & p1 = points[0]; + auto const & p2 = points[1]; + [[MWMRouter router] buildFromPoint:MWMRoutePoint(p1.m_org, @(p1.m_name.c_str())) + toPoint:MWMRoutePoint(p2.m_org, @(p2.m_name.c_str())) + bestRouter:YES]; [self showMap]; [self.mapViewController showAPIBar]; + break; + } + case ParsedMapApi::ParsingResult::Map: + if (f.ShowMapForURL(url)) + { + [self showMap]; + [self.mapViewController showAPIBar]; + } + break; } } else if (m_fileURL) @@ -802,7 +827,8 @@ using namespace osm_auth_ios; m_geoURL = [url absoluteString]; return YES; } - else if ([scheme isEqualToString:@"mapswithme"] || [scheme isEqualToString:@"mwm"]) + else if ([scheme isEqualToString:@"mapswithme"] || [scheme isEqualToString:@"mwm"] || + [scheme isEqualToString:@"mapsme"]) { m_mwmURL = [url absoluteString]; return YES; diff --git a/iphone/Maps/Classes/Routing/MWMRouter.h b/iphone/Maps/Classes/Routing/MWMRouter.h index de691c2d3b..ca4fb95387 100644 --- a/iphone/Maps/Classes/Routing/MWMRouter.h +++ b/iphone/Maps/Classes/Routing/MWMRouter.h @@ -13,6 +13,9 @@ - (void)swapPointsAndRebuild; - (void)buildFromPoint:(MWMRoutePoint const &)start bestRouter:(BOOL)bestRouter; - (void)buildToPoint:(MWMRoutePoint const &)finish bestRouter:(BOOL)bestRouter; +- (void)buildFromPoint:(MWMRoutePoint const &)start + toPoint:(MWMRoutePoint const &)finish + bestRouter:(BOOL)bestRouter; - (void)rebuildWithBestRouter:(BOOL)bestRouter; - (void)start; - (void)stop; diff --git a/iphone/Maps/Classes/Routing/MWMRouter.mm b/iphone/Maps/Classes/Routing/MWMRouter.mm index c2832e092e..478ab1c212 100644 --- a/iphone/Maps/Classes/Routing/MWMRouter.mm +++ b/iphone/Maps/Classes/Routing/MWMRouter.mm @@ -116,6 +116,15 @@ bool isMarkerPoint(MWMRoutePoint const & point) { return point.IsValid() && !poi [self rebuildWithBestRouter:bestRouter]; } +- (void)buildFromPoint:(MWMRoutePoint const &)start + toPoint:(MWMRoutePoint const &)finish + bestRouter:(BOOL)bestRouter +{ + self.startPoint = start; + self.finishPoint = finish; + [self rebuildWithBestRouter:bestRouter]; +} + - (void)rebuildWithBestRouter:(BOOL)bestRouter { if (self.startPoint.IsMyPosition()) diff --git a/map/framework.cpp b/map/framework.cpp index 35b720d862..8b1612f5ee 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1749,31 +1749,13 @@ bool Framework::ShowMapForURL(string const & url) result = NEED_CLICK; } } - else if (StartsWith(url, "mapswithme://") || StartsWith(url, "mwm://")) + else if (m_ParsedMapApi.IsValid()) { - // clear every current API-mark. - { - UserMarkControllerGuard guard(m_bmManager, UserMarkType::API_MARK); - guard.m_controller.Clear(); - guard.m_controller.SetIsVisible(true); - guard.m_controller.SetIsDrawable(true); - } + if (!m_ParsedMapApi.GetViewportRect(rect)) + rect = df::GetWorldRect(); - if (m_ParsedMapApi.SetUriAndParse(url)) - { - if (!m_ParsedMapApi.GetViewportRect(rect)) - rect = df::GetWorldRect(); - - if ((apiMark = m_ParsedMapApi.GetSinglePoint())) - result = NEED_CLICK; - else - result = NO_NEED_CLICK; - } - else - { - UserMarkControllerGuard guard(m_bmManager, UserMarkType::API_MARK); - guard.m_controller.SetIsVisible(false); - } + apiMark = m_ParsedMapApi.GetSinglePoint(); + result = apiMark ? NEED_CLICK : NO_NEED_CLICK; } else // Actually, we can parse any geo url scheme with correct coordinates. { @@ -1819,6 +1801,27 @@ bool Framework::ShowMapForURL(string const & url) return false; } +url_scheme::ParsedMapApi::ParsingResult Framework::ParseAndSetApiURL(string const & url) +{ + using namespace url_scheme; + + // Clear every current API-mark. + { + UserMarkControllerGuard guard(m_bmManager, UserMarkType::API_MARK); + guard.m_controller.Clear(); + guard.m_controller.SetIsVisible(true); + guard.m_controller.SetIsDrawable(true); + } + + return m_ParsedMapApi.SetUriAndParse(url); +} + +Framework::ParsedRoutingData Framework::GetParsedRoutingData() const +{ + return Framework::ParsedRoutingData(m_ParsedMapApi.GetRoutePoints(), + routing::FromString(m_ParsedMapApi.GetRoutingType())); +} + void Framework::ForEachFeatureAtPoint(TFeatureTypeFn && fn, m2::PointD const & mercator, double featureDistanceToleranceInMeters) const { @@ -2240,7 +2243,6 @@ void Framework::BuildRoute(m2::PointD const & start, m2::PointD const & finish, if (IsRoutingActive()) CloseRouting(); - SetLastUsedRouter(m_currentRouterType); m_routingSession.SetUserCurrentPosition(start); auto readyCallback = [this] (Route const & route, IRouter::ResultCode code) @@ -2310,6 +2312,8 @@ void Framework::SetRouter(RouterType type) if (m_currentRouterType == type) return; + + SetLastUsedRouter(type); SetRouterImpl(type); } diff --git a/map/framework.hpp b/map/framework.hpp index 1c807b6e10..ab884b26ea 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -503,6 +503,19 @@ public: /// Set correct viewport, parse API, show balloon. bool ShowMapForURL(string const & url); + url_scheme::ParsedMapApi::ParsingResult ParseAndSetApiURL(string const & url); + + struct ParsedRoutingData + { + ParsedRoutingData(vector const & points, routing::RouterType type) + : m_points(points), m_type(type) + { + } + vector m_points; + routing::RouterType m_type; + }; + + ParsedRoutingData GetParsedRoutingData() const; private: // TODO(vng): Uncomment when needed. diff --git a/map/geourl_process.cpp b/map/geourl_process.cpp index a254758ef4..ef9721c70b 100644 --- a/map/geourl_process.cpp +++ b/map/geourl_process.cpp @@ -124,19 +124,19 @@ namespace url_scheme return (m_latPriority == m_lonPriority && m_latPriority != -1); } - void operator()(string const & key, string const & value) + bool operator()(string const & key, string const & value) { if (key == "z" || key == "zoom") { double x; if (strings::to_double(value, x)) m_info.SetZoom(x); - return; + return true; } int const priority = GetCoordinatesPriority(key); if (priority == -1 || priority < m_latPriority || priority < m_lonPriority) - return; + return false; if (priority != LL_PRIORITY) { @@ -149,17 +149,20 @@ namespace url_scheme { if (key == "lat") { - if (m_info.SetLat(x)) - m_latPriority = priority; + if (!m_info.SetLat(x)) + return false; + m_latPriority = priority; } else { ASSERT_EQUAL(key, "lon", ()); - if (m_info.SetLon(x)) - m_lonPriority = priority; + if (!m_info.SetLon(x)) + return false; + m_lonPriority = priority; } } } + return true; } }; diff --git a/map/map_tests/mwm_url_tests.cpp b/map/map_tests/mwm_url_tests.cpp index 006aa22871..0d1261bd47 100644 --- a/map/map_tests/mwm_url_tests.cpp +++ b/map/map_tests/mwm_url_tests.cpp @@ -31,7 +31,8 @@ namespace m_m = &m_fm.GetBookmarkManager(); m_api.SetBookmarkManager(m_m); - if (m_api.SetUriAndParse(uriString)) + auto const res = m_api.SetUriAndParse(uriString); + if (res != ParsedMapApi::ParsingResult::Incorrect) { if (!m_api.GetViewportRect(m_viewportRect)) m_viewportRect = df::GetWorldRect(); @@ -44,6 +45,7 @@ namespace string const & GetAppTitle() const { return m_api.GetAppTitle(); } bool GoBackOnBalloonClick() const { return m_api.GoBackOnBalloonClick(); } int GetPointCount() const { return UserMarkControllerGuard(*m_m, type).m_controller.GetUserMarkCount(); } + vector GetRoutePoints() const { return m_api.GetRoutePoints(); } string const & GetGlobalBackUrl() const { return m_api.GetGlobalBackUrl(); } int GetApiVersion() const { return m_api.GetApiVersion(); } bool TestLatLon(int index, double lat, double lon) const @@ -52,6 +54,12 @@ namespace return my::AlmostEqualULPs(ll.lat, lat) && my::AlmostEqualULPs(ll.lon, lon); } + bool TestRoutePoint(int index, double lat, double lon, string const & name) + { + RoutePoint const pt = GetRoutePoints()[index]; + return pt.m_org == MercatorBounds::FromLatLon(lat, lon) && pt.m_name == name; + } + bool TestName(int index, string const & name) const { return GetMark(index)->GetName() == name; @@ -62,6 +70,7 @@ namespace return GetMark(index)->GetID() == id; } + bool TestRouteType(string const & type) const { return m_api.GetRoutingType() == type; } private: ApiMarkPoint const * GetMark(int index) const { @@ -80,17 +89,14 @@ namespace bool IsValid(Framework & fm, string const & uriString) { ParsedMapApi api; - bool isValid = false; + api.SetBookmarkManager(&fm.GetBookmarkManager()); + api.SetUriAndParse(uriString); { - api.SetBookmarkManager(&fm.GetBookmarkManager()); - if (api.SetUriAndParse(uriString)) - isValid = api.IsValid(); - UserMarkControllerGuard guard(fm.GetBookmarkManager(), UserMarkType::API_MARK); guard.m_controller.Clear(); } - return isValid; + return api.IsValid(); } } @@ -109,6 +115,19 @@ UNIT_TEST(MapApiSmoke) TEST_EQUAL(test.GetGlobalBackUrl(), "", ()); } +UNIT_TEST(RouteApiSmoke) +{ + string const uriString = + "mapswithme://route?sll=1,1&saddr=name0&dll=2,2&daddr=name1&type=vehicle"; + TEST(Uri(uriString).IsValid(), ()); + + ApiTest test(uriString); + TEST(test.IsValid(), ()); + TEST(test.TestRoutePoint(0, 1, 1, "name0"), ()); + TEST(test.TestRoutePoint(1, 2, 2, "name1"), ()); + TEST(test.TestRouteType("vehicle"), ()); +} + UNIT_TEST(MapApiInvalidUrl) { Framework fm; @@ -117,6 +136,27 @@ UNIT_TEST(MapApiInvalidUrl) TEST(!IsValid(fm, "mwm://"), ("No parameters")); TEST(!IsValid(fm, "mapswithme://map?"), ("No longtitude")); TEST(!IsValid(fm, "mapswithme://map?ll=1,2,3"), ("Too many values for ll")); + TEST(!IsValid(fm, "mapswithme://fffff://map?ll=1,2"), ()); +} + +UNIT_TEST(RouteApiInvalidUrl) +{ + Framework f; + TEST(!IsValid(f, "mapswithme://route?sll=1,1&saddr=name0&dll=2,2&daddr=name2"), + ("Route type doesn't exist")); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&saddr=name0"), ("Destination doesn't exist")); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&dll=2,2&type=vehicle"), + ("Source or destination name doesn't exist")); + TEST(!IsValid(f, "mapswithme://route?saddr=name0&daddr=name1&type=vehicle"), ()); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&sll=2.2&type=vehicle"), ()); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&dll=2.2&type=666"), ()); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&saddr=name0&sll=2,2&saddr=name1&type=vehicle"), ()); + TEST(!IsValid(f, "mapswithme://route?sll=1,1&type=vehicle"), ()); + TEST(!IsValid(f, + "mapswithme://" + "route?sll=1,1&saddr=name0&sll=2,2&saddr=name1&sll=1,1&saddr=name0&type=vehicle"), + ()); + TEST(!IsValid(f, "mapswithme://route?type=vehicle"), ()); } UNIT_TEST(MapApiLatLonLimits) @@ -131,9 +171,8 @@ UNIT_TEST(MapApiLatLonLimits) UNIT_TEST(MapApiPointNameBeforeLatLon) { ApiTest test("mapswithme://map?n=Name&ll=1,2"); - TEST(test.IsValid(), ()); - TEST_EQUAL(test.GetPointCount(), 1, ()); - TEST(test.TestName(0, ""), ()); + TEST(!test.IsValid(), ()); + TEST_EQUAL(test.GetPointCount(), 0, ()); } UNIT_TEST(MapApiPointNameOverwritten) @@ -160,10 +199,8 @@ UNIT_TEST(MapApiMultiplePoints) UNIT_TEST(MapApiInvalidPointLatLonButValidOtherParts) { ApiTest api("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.GetPointCount(), 1, ()); - TEST(api.TestLatLon(0, 2, 2), ()); - TEST(api.TestName(0, "B"), ()); + TEST(!api.IsValid(), ()); + TEST_EQUAL(api.GetPointCount(), 0, ()); } UNIT_TEST(MapApiPointURLEncoded) diff --git a/map/mwm_url.cpp b/map/mwm_url.cpp index 1c213088d4..593f9a3ea8 100644 --- a/map/mwm_url.cpp +++ b/map/mwm_url.cpp @@ -19,58 +19,112 @@ namespace url_scheme { - namespace { +string const kLatLon = "ll"; +string const kSourceLatLon = "sll"; +string const kDestLatLon = "dll"; +string const kZoomLevel = "z"; +string const kName = "n"; +string const kSourceName = "saddr"; +string const kDestName = "daddr"; +string const kId = "id"; +string const kStyle = "s"; +string const kBackUrl = "backurl"; +string const kVersion = "v"; +string const kAppName = "appname"; +string const kBalloonAction = "balloonaction"; +string const kRouteType = "type"; +string const kRouteTypeVehicle = "vehicle"; +string const kRouteTypePedestrian = "pedestrian"; +string const kRouteTypeBicycle = "bicycle"; -static int const INVALID_LAT_VALUE = -1000; - -bool IsInvalidApiPoint(ApiPoint const & p) { return p.m_lat == INVALID_LAT_VALUE; } - -} // unnames namespace - -ParsedMapApi::ParsedMapApi() - : m_bmManager(nullptr) - , m_version(0) - , m_zoomLevel(0.0) - , m_goBackOnBalloonClick(false) +bool ParseLatLon(string const & key, string const & value, double & lat, double & lon) { + size_t const firstComma = value.find(','); + if (firstComma == string::npos) + { + LOG(LWARNING, ("Map API: no comma between lat and lon for key:", key, " value:", value)); + return false; + } + + 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 key:", key, " value:", value)); + return false; + } + + if (!MercatorBounds::ValidLat(lat) || !MercatorBounds::ValidLon(lon)) + { + LOG(LWARNING, ("Map API: incorrect value for lat and/or lon", key, value, lat, lon)); + return false; + } + return true; } +} // namespace + void ParsedMapApi::SetBookmarkManager(BookmarkManager * manager) { m_bmManager = manager; } - -bool ParsedMapApi::SetUriAndParse(string const & url) +ParsedMapApi::ParsingResult ParsedMapApi::SetUriAndParse(string const & url) { Reset(); - return Parse(url_scheme::Uri(url)); + + if (!strings::StartsWith(url, "mapswithme://") && !strings::StartsWith(url, "mwm://") && + !strings::StartsWith(url, "mapsme://")) + { + return ParsingResult::Incorrect; + } + + ParsingResult const res = Parse(url_scheme::Uri(url)); + m_isValid = res != ParsingResult::Incorrect; + return res; } -bool ParsedMapApi::IsValid() const -{ - ASSERT(m_bmManager != nullptr, ()); - UserMarkControllerGuard guard(*m_bmManager, UserMarkType::API_MARK); - return guard.m_controller.GetUserMarkCount() > 0; -} - -bool ParsedMapApi::Parse(Uri const & uri) +ParsedMapApi::ParsingResult ParsedMapApi::Parse(Uri const & uri) { string const & scheme = uri.GetScheme(); - if ((scheme != "mapswithme" && scheme != "mwm") || uri.GetPath() != "map") - return false; + string const & path = uri.GetPath(); + bool const isRoutePath = path == "route"; + if ((scheme != "mapswithme" && scheme != "mwm" && scheme != "mapsme") || + (path != "map" && !isRoutePath)) + { + return ParsingResult::Incorrect; + } + + if (isRoutePath) + { + m_routePoints.clear(); + vector pattern{kSourceLatLon, kSourceName, kDestLatLon, kDestName, kRouteType}; + if (!uri.ForEachKeyValue(bind(&ParsedMapApi::RouteKeyValue, this, _1, _2, ref(pattern)))) + return ParsingResult::Incorrect; + + if (pattern.size() != 0) + return ParsingResult::Incorrect; + + if (m_routePoints.size() != 2) + { + ASSERT(false, ()); + return ParsingResult::Incorrect; + } + + return ParsingResult::Route; + } + + vector points; + if (!uri.ForEachKeyValue(bind(&ParsedMapApi::AddKeyValue, this, _1, _2, ref(points)))) + return ParsingResult::Incorrect; + + if (points.empty()) + return ParsingResult::Incorrect; ASSERT(m_bmManager != nullptr, ()); UserMarkControllerGuard guard(*m_bmManager, UserMarkType::API_MARK); - - vector points; - uri.ForEachKeyValue(bind(&ParsedMapApi::AddKeyValue, this, _1, _2, ref(points))); - points.erase(remove_if(points.begin(), points.end(), &IsInvalidApiPoint), points.end()); - - for (size_t i = 0; i < points.size(); ++i) + for (auto const & p : points) { - ApiPoint const & p = points[i]; m2::PointD glPoint(MercatorBounds::FromLatLon(p.m_lat, p.m_lon)); ApiMarkPoint * mark = static_cast(guard.m_controller.CreateUserMark(glPoint)); mark->SetName(p.m_name); @@ -78,76 +132,103 @@ bool ParsedMapApi::Parse(Uri const & uri) mark->SetStyle(style::GetSupportedStyle(p.m_style, p.m_name, "")); } + return ParsingResult::Map; +} + +bool ParsedMapApi::RouteKeyValue(string key, string const & value, vector & pattern) +{ + if (pattern.empty() || key != pattern.front()) + return false; + + if (key == kSourceLatLon || key == kDestLatLon) + { + double lat = 0.0; + double lon = 0.0; + if (!ParseLatLon(key, value, lat, lon)) + return false; + + RoutePoint p; + p.m_org = MercatorBounds::FromLatLon(lat, lon); + m_routePoints.push_back(p); + } + else if (key == kSourceName || key == kDestName) + { + m_routePoints.back().m_name = value; + } + else if (key == kRouteType) + { + string const lowerValue = strings::MakeLowerCase(value); + if (lowerValue == kRouteTypePedestrian || lowerValue == kRouteTypeVehicle || lowerValue == kRouteTypeBicycle) + { + m_routingType = lowerValue; + } + else + { + LOG(LWARNING, ("Incorrect routing type:", value)); + return false; + } + } + + pattern.erase(pattern.begin()); return true; } -void ParsedMapApi::AddKeyValue(string key, string const & value, vector & points) +bool ParsedMapApi::AddKeyValue(string key, string const & value, vector & points) { strings::AsciiToLower(key); - if (key == "ll") + if (key == kLatLon) { - points.push_back(ApiPoint()); - 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 = 0.0; double lon = 0.0; - 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 (!ParseLatLon(key, value, lat, lon)) + return false; - if (!MercatorBounds::ValidLat(lat) || !MercatorBounds::ValidLon(lon)) - { - LOG(LWARNING, ("Map API: incorrect value for lat and/or lon", key, value, lat, lon)); - return; - } - - points.back().m_lat = lat; - points.back().m_lon = lon; + ApiPoint pt{.m_lat = lat, .m_lon = lon}; + points.push_back(pt); } - else if (key == "z") + else if (key == kZoomLevel) { if (!strings::to_double(value, m_zoomLevel)) m_zoomLevel = 0.0; } - else if (key == "n") + else if (key == kName) { if (!points.empty()) + { points.back().m_name = value; + } else + { LOG(LWARNING, ("Map API: Point name with no point. 'll' should come first!")); + return false; + } } - else if (key == "id") + else if (key == kId) { if (!points.empty()) + { points.back().m_id = value; + } else + { LOG(LWARNING, ("Map API: Point url with no point. 'll' should come first!")); + return false; + } } - else if (key == "s") + else if (key == kStyle) { if (!points.empty()) + { points.back().m_style = value; + } else + { LOG(LWARNING, ("Map API: Point style with no point. 'll' should come first!")); + return false; + } } - else if (key == "backurl") + else if (key == kBackUrl) { // Fix missing :// in back url, it's important for iOS if (value.find("://") == string::npos) @@ -155,19 +236,20 @@ void ParsedMapApi::AddKeyValue(string key, string const & value, vector const & GetRoutePoints() const { return m_routePoints; } + string const & GetRoutingType() const { return m_routingType; } private: - bool Parse(Uri const & uri); - void AddKeyValue(string key, string const & value, vector & points); + ParsingResult Parse(Uri const & uri); + bool AddKeyValue(string key, string const & value, vector & points); + bool RouteKeyValue(string key, string const & value, vector & pattern); - BookmarkManager * m_bmManager; + BookmarkManager * m_bmManager = nullptr; + vector m_routePoints; string m_globalBackUrl; string m_appTitle; - int m_version; + string m_routingType; + int m_version = 0; /// Zoom level in OSM format (e.g. from 1.0 to 20.0) /// Taken into an account when calculating viewport rect, but only if points count is == 1 - double m_zoomLevel; - bool m_goBackOnBalloonClick; + double m_zoomLevel = 0.0; + bool m_goBackOnBalloonClick = false; + bool m_isValid = false; }; } diff --git a/routing/router.cpp b/routing/router.cpp index 2cfd491360..ffd69ad642 100644 --- a/routing/router.cpp +++ b/routing/router.cpp @@ -7,12 +7,25 @@ string ToString(RouterType type) { switch(type) { - case RouterType::Vehicle: return "Vehicle"; - case RouterType::Pedestrian: return "Pedestrian"; - case RouterType::Bicycle: return "Bicycle"; + case RouterType::Vehicle: return "Vehicle"; + case RouterType::Pedestrian: return "Pedestrian"; + case RouterType::Bicycle: return "Bicycle"; } ASSERT(false, ()); return "Error"; } +RouterType FromString(string const & str) +{ + if (str == "vehicle") + return RouterType::Vehicle; + if (str == "pedestrian") + return RouterType::Pedestrian; + if (str == "bicycle") + return RouterType::Bicycle; + + ASSERT(false, ("Incorrect routing string:", str)); + return RouterType::Vehicle; +} + } // namespace routing diff --git a/routing/router.hpp b/routing/router.hpp index 667521b432..ac91916414 100644 --- a/routing/router.hpp +++ b/routing/router.hpp @@ -25,6 +25,7 @@ enum class RouterType }; string ToString(RouterType type); +RouterType FromString(string const & str); class IRouter { diff --git a/xcode/map/map.xcodeproj/project.pbxproj b/xcode/map/map.xcodeproj/project.pbxproj index 04cab246ba..94ecc3ac72 100644 --- a/xcode/map/map.xcodeproj/project.pbxproj +++ b/xcode/map/map.xcodeproj/project.pbxproj @@ -100,6 +100,14 @@ F6B283081C1B03320081957A /* gps_track_storage.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F6B283001C1B03320081957A /* gps_track_storage.hpp */; }; F6B283091C1B03320081957A /* gps_track.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6B283011C1B03320081957A /* gps_track.cpp */; }; F6B2830A1C1B03320081957A /* gps_track.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F6B283021C1B03320081957A /* gps_track.hpp */; }; + F6DA2A951CCE24DA00F087B5 /* libdrape.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 674A29F61B26FE62001A525C /* libdrape.a */; }; + F6DA2A971CCE24DB00F087B5 /* libdrape_frontend.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A961CCE24DB00F087B5 /* libdrape_frontend.a */; }; + F6DA2A9E1CCE252600F087B5 /* libeditor.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A981CCE252600F087B5 /* libeditor.a */; }; + F6DA2A9F1CCE252600F087B5 /* liboauthcpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A991CCE252600F087B5 /* liboauthcpp.a */; }; + F6DA2AA01CCE252600F087B5 /* libplatform_tests_support.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A9A1CCE252600F087B5 /* libplatform_tests_support.a */; }; + F6DA2AA11CCE252600F087B5 /* libpugixml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A9B1CCE252600F087B5 /* libpugixml.a */; }; + F6DA2AA21CCE252600F087B5 /* libsdf_image.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A9C1CCE252600F087B5 /* libsdf_image.a */; }; + F6DA2AA31CCE252600F087B5 /* libstb_image.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6DA2A9D1CCE252600F087B5 /* libstb_image.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -214,6 +222,13 @@ F6B283001C1B03320081957A /* gps_track_storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = gps_track_storage.hpp; sourceTree = ""; }; F6B283011C1B03320081957A /* gps_track.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gps_track.cpp; sourceTree = ""; }; F6B283021C1B03320081957A /* gps_track.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = gps_track.hpp; sourceTree = ""; }; + F6DA2A961CCE24DB00F087B5 /* libdrape_frontend.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdrape_frontend.a; path = "../../../omim-xcode-build/Debug/libdrape_frontend.a"; sourceTree = ""; }; + F6DA2A981CCE252600F087B5 /* libeditor.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libeditor.a; path = "../../../omim-xcode-build/Debug/libeditor.a"; sourceTree = ""; }; + F6DA2A991CCE252600F087B5 /* liboauthcpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = liboauthcpp.a; path = "../../../omim-xcode-build/Debug/liboauthcpp.a"; sourceTree = ""; }; + F6DA2A9A1CCE252600F087B5 /* libplatform_tests_support.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libplatform_tests_support.a; path = "/Users/v.mikhaylenko/mapsme/omim/xcode/platform/../../../omim-xcode-build/Debug/libplatform_tests_support.a"; sourceTree = ""; }; + F6DA2A9B1CCE252600F087B5 /* libpugixml.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpugixml.a; path = "../../../omim-xcode-build/Debug/libpugixml.a"; sourceTree = ""; }; + F6DA2A9C1CCE252600F087B5 /* libsdf_image.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsdf_image.a; path = "../../../omim-xcode-build/Debug/libsdf_image.a"; sourceTree = ""; }; + F6DA2A9D1CCE252600F087B5 /* libstb_image.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libstb_image.a; path = "../../../omim-xcode-build/Debug/libstb_image.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -347,6 +362,13 @@ 675345B21A4054AD00A0A8C3 = { isa = PBXGroup; children = ( + F6DA2A981CCE252600F087B5 /* libeditor.a */, + F6DA2A991CCE252600F087B5 /* liboauthcpp.a */, + F6DA2A9A1CCE252600F087B5 /* libplatform_tests_support.a */, + F6DA2A9B1CCE252600F087B5 /* libpugixml.a */, + F6DA2A9C1CCE252600F087B5 /* libsdf_image.a */, + F6DA2A9D1CCE252600F087B5 /* libstb_image.a */, + F6DA2A961CCE24DB00F087B5 /* libdrape_frontend.a */, 670D05A41B0DF4250013A7AC /* defaults.xcconfig */, 674A2A341B2700B2001A525C /* libs */, 675345BD1A4054AD00A0A8C3 /* map */,