Compare commits

...

2 Commits

Author SHA1 Message Date
Yannik Bloscheck
f5b68183c9 [ios] Support geo-navigation URL
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2025-11-07 21:12:13 +01:00
x7z4w
74122e2fae [core] Support geo-navigation URL
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-11-07 21:12:12 +01:00
19 changed files with 234 additions and 19 deletions

View File

@@ -24,7 +24,11 @@ static inline DeeplinkUrlType deeplinkUrlType(url_scheme::ParsedMapApi::UrlType
+ (DeeplinkUrlType)parseAndSetApiURL:(NSURL *)url {
Framework &f = GetFramework();
return deeplinkUrlType(f.ParseAndSetApiURL(url.absoluteString.UTF8String));
if ([url.scheme isEqual: @"geo-navigation"]) {
return deeplinkUrlType(f.ParseGeoNav(url.absoluteString.UTF8String, f));
} else {
return deeplinkUrlType(f.ParseAndSetApiURL(url.absoluteString.UTF8String));
}
}
+ (void)executeMapApiRequest {

View File

@@ -19,6 +19,8 @@
<string>CloudDocuments</string>
<string>CloudKit</string>
</array>
<key>com.apple.developer.navigation-app</key>
<true/>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.app.comaps.debug</string>

View File

@@ -19,6 +19,8 @@
<string>CloudDocuments</string>
<string>CloudKit</string>
</array>
<key>com.apple.developer.navigation-app</key>
<true/>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.app.comaps</string>

View File

@@ -65,6 +65,7 @@
<string>mapsme</string>
<string>ge0</string>
<string>geo</string>
<string>geo-navigation</string>
<string>om</string>
<string>mapswithmepro</string>
</array>
@@ -89,7 +90,7 @@
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>comaps.at</key>
<key>comaps.app</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
@@ -98,7 +99,7 @@
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>comaps.app</key>
<key>comaps.at</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>

View File

@@ -91,7 +91,7 @@
switch urlType {
case .route:
if let adapter = DeepLinkRouteStrategyAdapter(url) {
MWMRouter.buildApiRoute(with: adapter.type, start: adapter.p1, finish: adapter.p2)
MWMRouter.buildApiRoute(with: adapter.type, start: adapter.pStart, intermediatePoint: adapter.pIntermediate, finish: adapter.pFinish)
MapsAppDelegate.theApp().showMap()
return true
}

View File

@@ -6,8 +6,9 @@ NS_ASSUME_NONNULL_BEGIN
@class MWMRoutePoint;
@interface DeepLinkRouteStrategyAdapter : NSObject
@property(nonatomic, readonly) MWMRoutePoint* p1;
@property(nonatomic, readonly) MWMRoutePoint* p2;
@property(nonatomic, readonly) MWMRoutePoint* pStart;
@property(nonatomic, readonly) MWMRoutePoint* pIntermediate;
@property(nonatomic, readonly) MWMRoutePoint* pFinish;
@property(nonatomic, readonly) MWMRouterType type;
- (nullable instancetype)init:(NSURL*)url;

View File

@@ -11,11 +11,29 @@
auto const parsedData = GetFramework().GetParsedRoutingData();
auto const points = parsedData.m_points;
if (points.size() == 2) {
_p1 = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:points.front()
for (auto point: points) {
if (point.m_type == RouteMarkType::Start) {
_pStart = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:point
type:MWMRoutePointTypeStart
intermediateIndex:0];
} else if (point.m_type == RouteMarkType::Finish) {
_pFinish = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:point
type:MWMRoutePointTypeFinish
intermediateIndex:0];
} else if (point.m_type == RouteMarkType::Intermediate) {
_pIntermediate = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:point
type:MWMRoutePointTypeIntermediate
intermediateIndex:0];
}
}
if (_pStart && _pFinish) {
_type = routerType(parsedData.m_type);
} else if (points.size() == 2) {
_pStart = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:points.front()
type:MWMRoutePointTypeStart
intermediateIndex:0];
_p2 = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:points.back()
_pFinish = [[MWMRoutePoint alloc] initWithURLSchemeRoutePoint:points.back()
type:MWMRoutePointTypeFinish
intermediateIndex:0];
_type = routerType(parsedData.m_type);

View File

@@ -56,6 +56,7 @@ typedef void (^MWMImageHeightBlock)(UIImage *, NSString *, NSString *);
+ (void)buildToPoint:(MWMRoutePoint *)finish bestRouter:(BOOL)bestRouter;
+ (void)buildApiRouteWithType:(MWMRouterType)type
startPoint:(MWMRoutePoint *)startPoint
intermediatePoint:(MWMRoutePoint *)intermediatePoint
finishPoint:(MWMRoutePoint *)finishPoint;
+ (void)rebuildWithBestRouter:(BOOL)bestRouter;

View File

@@ -255,6 +255,7 @@ char const *kRenderAltitudeImagesQueueLabel = "mapsme.mwmrouter.renderAltitudeIm
+ (void)buildApiRouteWithType:(MWMRouterType)type
startPoint:(MWMRoutePoint *)startPoint
intermediatePoint:(MWMRoutePoint *)intermediatePoint
finishPoint:(MWMRoutePoint *)finishPoint {
if (!startPoint || !finishPoint)
return;
@@ -264,6 +265,9 @@ char const *kRenderAltitudeImagesQueueLabel = "mapsme.mwmrouter.renderAltitudeIm
auto router = [MWMRouter router];
router.isAPICall = YES;
[self addPoint:startPoint];
if (intermediatePoint) {
[self addPoint:intermediatePoint];
}
[self addPoint:finishPoint];
router.isAPICall = NO;

View File

@@ -41,6 +41,7 @@ public:
return &p.second;
return nullptr;
}
std::vector<Param> const & GetParams() const { return m_params; }
private:
bool Parse(std::string const & url);

View File

@@ -585,6 +585,13 @@ public:
return m_parsedMapApi.SetUrlAndParse(url);
}
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
url_scheme::ParsedMapApi::UrlType ParseGeoNav(std::string const & raw, Framework & fm)
{
return m_parsedMapApi.ParseGeoNav(raw, fm);
}
#endif
struct ParsedRoutingData
{
ParsedRoutingData(std::vector<url_scheme::RoutePoint> const & points, routing::RouterType type)

View File

@@ -1,8 +1,19 @@
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
#include "platform/preferred_languages.hpp"
#endif
#include "map/mwm_url.hpp"
#include "map/api_mark_point.hpp"
#include "map/bookmark_manager.hpp"
#include "map/framework.hpp"
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
#include "map/everywhere_search_params.hpp"
#include "map/routing_manager.hpp"
#include "map/routing_mark.hpp"
#include "search/result.hpp"
#endif
#include "ge0/geo_url_parser.hpp"
#include "ge0/parser.hpp"
@@ -17,8 +28,9 @@
#include "base/scope_guard.hpp"
#include "base/string_utils.hpp"
#include <array>
#include <tuple>
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
#include <future>
#endif
namespace url_scheme
{
@@ -238,6 +250,131 @@ ParsedMapApi::UrlType ParsedMapApi::SetUrlAndParse(std::string const & raw)
UNREACHABLE();
}
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
ParsedMapApi::UrlType ParsedMapApi::ParseGeoNav(std::string const & raw, Framework & fm)
{
Reset();
SCOPE_GUARD(guard, [this]
{
if (m_requestType == UrlType::Incorrect)
Reset();
});
url::Url const url(raw);
if (url.GetHost() == "place")
{
auto const latLon = url.GetParamValue("coordinate");
auto const addr = url.GetParamValue("address");
if (latLon)
{
auto const tokens = strings::Tokenize(*latLon, ",");
double lat;
double lon;
if (tokens.size() != 2 || !strings::to_double(tokens[0], lat) || !strings::to_double(tokens[1], lon) ||
!mercator::ValidLat(lat) || !mercator::ValidLon(lon))
{
LOG(LWARNING, ("Invalid lat,lon in", raw));
return m_requestType = UrlType::Incorrect;
}
if (addr)
{
m_searchRequest = SearchRequest();
m_searchRequest.m_query = *addr;
m_centerLatLon = {lat, lon};
return m_requestType = UrlType::Search;
}
else
{
m_centerLatLon = {lat, lon};
m_mapPoints.push_back({lat /* m_lat */, lon /* m_lon */, "" /* m_label */, "" /* m_id */, "" /* m_style */});
return m_requestType = UrlType::Map;
}
}
else if (addr)
{
m_searchRequest = SearchRequest();
m_searchRequest.m_query = *addr;
return m_requestType = UrlType::Search;
}
}
else if (url.GetHost() == "directions")
{
auto const source = url.GetParamValue("source");
auto const destination = url.GetParamValue("destination");
if (source)
SetRouteMark(*source, fm, RouteMarkType::Finish);
if (url.GetParamValue("waypoint"))
for (auto const & param : url.GetParams())
if (param.first == "waypoint")
SetRouteMark(param.second, fm, RouteMarkType::Intermediate);
if (destination)
SetRouteMark(*destination, fm, RouteMarkType::Finish);
if (source || destination)
{
m_routingType = routing::ToString(routing::GetLastUsedRouter());
return m_requestType = UrlType::Route;
}
return m_requestType = UrlType::Incorrect;
}
return m_requestType = UrlType::Incorrect;
}
void ParsedMapApi::SetRouteMark(std::string_view const raw, Framework & fm, RouteMarkType const type)
{
auto const tokens = strings::Tokenize(raw, ",");
double lat;
double lon;
if (tokens.size() != 2 || !strings::to_double(tokens[0], lat) || !strings::to_double(tokens[1], lon) ||
!mercator::ValidLat(lat) || !mercator::ValidLon(lon))
{
std::promise<void> signal;
std::future<void> future = signal.get_future();
::search::EverywhereSearchParams params{
std::string(raw),
languages::GetMostPreferredLang(),
{} /* timeout */,
false,
// m_onResults
[this, type = std::move(type), &signal](::search::Results results, std::vector<::search::ProductInfo>)
{
auto const center = results[0].GetFeatureCenter();
RoutePoint p;
p.m_type = type;
p.m_org = mercator::FromLatLon(mercator::YToLat(center.y), mercator::XToLon(center.x));
p.m_name = results[0].GetString();
m_routePoints.push_back(p);
signal.set_value();
}};
fm.GetSearchAPI().SearchEverywhere(std::move(params));
future.wait();
}
else
{
RoutePoint p;
p.m_org = mercator::FromLatLon(lat, lon);
p.m_type = type;
m_routePoints.push_back(p);
}
}
#endif
void ParsedMapApi::ParseMapParam(std::string const & key, std::string const & value, bool & correctOrder)
{
using namespace map;

View File

@@ -1,5 +1,10 @@
#pragma once
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
#include "map/everywhere_search_params.hpp"
#include "map/routing_mark.hpp"
#endif
#include "geometry/latlon.hpp"
#include "geometry/point2d.hpp"
@@ -22,9 +27,20 @@ struct MapPoint
struct RoutePoint
{
RoutePoint() = default;
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
RoutePoint(m2::PointD const & org, std::string const & name, RouteMarkType type)
: m_org(org)
, m_name(name)
, m_type(type)
{}
#else
RoutePoint(m2::PointD const & org, std::string const & name) : m_org(org), m_name(name) {}
#endif
m2::PointD m_org = m2::PointD::Zero();
std::string m_name;
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
RouteMarkType m_type;
#endif
};
struct SearchRequest
@@ -66,6 +82,9 @@ public:
explicit ParsedMapApi(std::string const & url) { SetUrlAndParse(url); }
UrlType SetUrlAndParse(std::string const & url);
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
UrlType ParseGeoNav(std::string const & raw, Framework & fm);
#endif
UrlType GetRequestType() const { return m_requestType; }
std::string const & GetGlobalBackUrl() const { return m_globalBackUrl; }
std::string const & GetAppName() const { return m_appName; }
@@ -123,6 +142,9 @@ public:
private:
void ParseMapParam(std::string const & key, std::string const & value, bool & correctOrder);
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_IPHONE)
void SetRouteMark(std::string_view const raw, Framework & fm, RouteMarkType const type);
#endif
void ParseRouteParam(std::string const & key, std::string const & value, std::vector<std::string_view> & pattern);
void ParseSearchParam(std::string const & key, std::string const & value);
void ParseInAppFeatureHighlightParam(std::string const & key, std::string const & value);

View File

@@ -470,10 +470,10 @@ void RoutingManager::OnLocationUpdate(location::GpsInfo const & info)
RouterType RoutingManager::GetBestRouter(m2::PointD const & startPoint, m2::PointD const & finalPoint) const
{
// todo Implement something more sophisticated here (or delete the method).
return GetLastUsedRouter();
return routing::GetLastUsedRouter();
}
RouterType RoutingManager::GetLastUsedRouter() const
RouterType routing::GetLastUsedRouter()
{
string routerTypeStr;
if (!settings::Get(kRouterTypeKey, routerTypeStr))

View File

@@ -40,6 +40,7 @@ class CountryInfoGetter;
namespace routing
{
class NumMwmIds;
RouterType GetLastUsedRouter();
}
class DataSource;
@@ -172,7 +173,6 @@ public:
m2::PointD GetRouteEndPoint() const { return m_routingSession.GetEndPoint(); }
/// Returns the most situable router engine type.
routing::RouterType GetBestRouter(m2::PointD const & startPoint, m2::PointD const & finalPoint) const;
routing::RouterType GetLastUsedRouter() const;
void SetLastUsedRouter(routing::RouterType type);
// Sound notifications for turn instructions.
void EnableTurnNotifications(bool enable) { m_routingSession.EnableTurnNotifications(enable); }

View File

@@ -579,6 +579,11 @@ std::string GetCurrentMapLanguage()
return languageCode;
}
std::string GetMostPreferredLang()
{
return std::string(StringUtf8Multilang::GetLangByCode(GetPreferredLangIndexes()[0]));
}
std::vector<int8_t> GetPreferredLangIndexes()
{
std::vector<int8_t> langs = {};

View File

@@ -25,6 +25,7 @@ std::string GetCurrentMapTwine();
std::string Normalize(std::string_view lang);
std::string GetCurrentNorm();
std::string GetCurrentMapLanguage();
std::string GetMostPreferredLang();
std::vector<int8_t> GetPreferredLangIndexes();
buffer_vector<std::string, 4> const & GetSystemPreferred();

View File

@@ -49,6 +49,13 @@ bool FromString<string>(string const & strIn, string & strOut)
return true;
}
template <>
bool FromString<std::string_view>(string const & strIn, std::string_view & strOut)
{
strOut = strIn;
return true;
}
namespace impl
{
template <class T, size_t N>

View File

@@ -130,11 +130,13 @@ void QuerySaver::Deserialize(string const & data)
{
Length localeLength = ReadPrimitiveFromSource<Length>(reader);
vector<char> locale(localeLength);
reader.Read(&locale[0], localeLength);
Length stringLength = ReadPrimitiveFromSource<Length>(reader);
vector<char> str(stringLength);
reader.Read(&str[0], stringLength);
m_topQueries.emplace_back(make_pair(string(&locale[0], localeLength), string(&str[0], stringLength)));
if (locale.size() > 0) {
reader.Read(&locale[0], localeLength);
Length stringLength = ReadPrimitiveFromSource<Length>(reader);
vector<char> str(stringLength);
reader.Read(&str[0], stringLength);
m_topQueries.emplace_back(make_pair(string(&locale[0], localeLength), string(&str[0], stringLength)));
}
}
}