Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
38
iphone/Maps/Core/Location/MWMLocationHelpers.h
Normal file
38
iphone/Maps/Core/Location/MWMLocationHelpers.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#import "MWMMyPositionMode.h"
|
||||
|
||||
#include "platform/localization.hpp"
|
||||
#include "platform/location.hpp"
|
||||
#include "platform/distance.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
namespace location_helpers
|
||||
{
|
||||
|
||||
static inline NSString * formattedDistance(double const & meters) {
|
||||
if (meters < 0.)
|
||||
return nil;
|
||||
|
||||
return @(platform::Distance::CreateFormatted(meters).ToString().c_str());
|
||||
}
|
||||
|
||||
static inline ms::LatLon ToLatLon(m2::PointD const & p) { return mercator::ToLatLon(p); }
|
||||
|
||||
static inline m2::PointD ToMercator(CLLocationCoordinate2D const & l)
|
||||
{
|
||||
return mercator::FromLatLon(l.latitude, l.longitude);
|
||||
}
|
||||
|
||||
static inline m2::PointD ToMercator(ms::LatLon const & l) { return mercator::FromLatLon(l); }
|
||||
static inline MWMMyPositionMode mwmMyPositionMode(location::EMyPositionMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case location::EMyPositionMode::PendingPosition: return MWMMyPositionModePendingPosition;
|
||||
case location::EMyPositionMode::NotFollowNoPosition: return MWMMyPositionModeNotFollowNoPosition;
|
||||
case location::EMyPositionMode::NotFollow: return MWMMyPositionModeNotFollow;
|
||||
case location::EMyPositionMode::Follow: return MWMMyPositionModeFollow;
|
||||
case location::EMyPositionMode::FollowAndRotate: return MWMMyPositionModeFollowAndRotate;
|
||||
}
|
||||
}
|
||||
} // namespace location_helpers
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
extension LocationManager {
|
||||
@objc static func speedSymbolFor(_ speed: Double) -> String {
|
||||
switch max(speed, 0) {
|
||||
case 0 ..< 1: return "🐢"
|
||||
case 1 ..< 2: return "🚶"
|
||||
case 2 ..< 5: return "🏃"
|
||||
case 5 ..< 10: return "🚲"
|
||||
case 10 ..< 36: return "🚗"
|
||||
case 36 ..< 120: return "🚄"
|
||||
case 120 ..< 278: return "🛩"
|
||||
default: return "🚀"
|
||||
}
|
||||
}
|
||||
}
|
||||
42
iphone/Maps/Core/Location/MWMLocationManager.h
Normal file
42
iphone/Maps/Core/Location/MWMLocationManager.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#import "MWMMyPositionMode.h"
|
||||
#import "MWMLocationObserver.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol LocationService
|
||||
|
||||
+ (BOOL)isLocationProhibited;
|
||||
+ (void)checkLocationStatus;
|
||||
|
||||
@end
|
||||
|
||||
NS_SWIFT_NAME(LocationManager)
|
||||
@interface MWMLocationManager : NSObject<LocationService>
|
||||
|
||||
+ (void)start;
|
||||
+ (void)stop;
|
||||
+ (BOOL)isStarted;
|
||||
|
||||
+ (void)addObserver:(id<MWMLocationObserver>)observer NS_SWIFT_NAME(add(observer:));
|
||||
+ (void)removeObserver:(id<MWMLocationObserver>)observer NS_SWIFT_NAME(remove(observer:));
|
||||
|
||||
+ (void)setMyPositionMode:(MWMMyPositionMode)mode;
|
||||
|
||||
+ (nullable CLLocation *)lastLocation;
|
||||
+ (nullable CLHeading *)lastHeading;
|
||||
|
||||
+ (void)applicationDidBecomeActive;
|
||||
+ (void)applicationWillResignActive;
|
||||
|
||||
+ (void)enableLocationAlert;
|
||||
|
||||
- (instancetype)init __attribute__((unavailable("call +manager instead")));
|
||||
- (instancetype)copy __attribute__((unavailable("call +manager instead")));
|
||||
- (instancetype)copyWithZone:(NSZone *)zone __attribute__((unavailable("call +manager instead")));
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone
|
||||
__attribute__((unavailable("call +manager instead")));
|
||||
+ (instancetype) new __attribute__((unavailable("call +manager instead")));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
618
iphone/Maps/Core/Location/MWMLocationManager.mm
Normal file
618
iphone/Maps/Core/Location/MWMLocationManager.mm
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
#import "MWMLocationManager.h"
|
||||
#import "MWMAlertViewController.h"
|
||||
#import "MWMLocationObserver.h"
|
||||
#import "MWMLocationPredictor.h"
|
||||
#import "MWMRouter.h"
|
||||
#import "SwiftBridge.h"
|
||||
#import "location_util.h"
|
||||
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
#include "map/gps_tracker.hpp"
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
#include "MountainElevationGenerator.hpp"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
using Observer = id<MWMLocationObserver>;
|
||||
using Observers = NSHashTable<Observer>;
|
||||
|
||||
enum class GeoMode
|
||||
{
|
||||
Pending,
|
||||
InPosition,
|
||||
NotInPosition,
|
||||
FollowAndRotate,
|
||||
VehicleRouting,
|
||||
PedestrianRouting,
|
||||
BicycleRouting
|
||||
};
|
||||
|
||||
std::string DebugPrint(GeoMode geoMode) {
|
||||
using enum GeoMode;
|
||||
switch (geoMode) {
|
||||
case Pending: return "Pending";
|
||||
case InPosition: return "InPosition";
|
||||
case NotInPosition: return "NotInPosition";
|
||||
case FollowAndRotate: return "FollowAndRotate";
|
||||
case VehicleRouting: return "VehicleRouting";
|
||||
case PedestrianRouting: return "PedestrianRouting";
|
||||
case BicycleRouting: return "BicycleRouting";
|
||||
}
|
||||
CHECK(false, ("Unsupported value", static_cast<int>(geoMode)));
|
||||
}
|
||||
|
||||
std::string DebugPrint(MWMMyPositionMode mode) {
|
||||
switch (mode) {
|
||||
case MWMMyPositionModePendingPosition: return "MWMMyPositionModePendingPosition";
|
||||
case MWMMyPositionModeNotFollowNoPosition: return "MWMMyPositionModeNotFollowNoPosition";
|
||||
case MWMMyPositionModeNotFollow: return "MWMMyPositionModeNotFollow";
|
||||
case MWMMyPositionModeFollow: return "MWMMyPositionModeFollow";
|
||||
case MWMMyPositionModeFollowAndRotate: return "MWMMyPositionModeFollowAndRotate";
|
||||
}
|
||||
CHECK(false, ("Unsupported value", static_cast<int>(mode)));
|
||||
}
|
||||
|
||||
std::string DebugPrint(MWMLocationStatus status) {
|
||||
switch (status) {
|
||||
case MWMLocationStatusNoError: return "MWMLocationStatusNoError";
|
||||
case MWMLocationStatusNotSupported: return "MWMLocationStatusNotSupported";
|
||||
case MWMLocationStatusDenied: return "MWMLocationStatusDenied";
|
||||
case MWMLocationStatusGPSIsOff: return "MWMLocationStatusGPSIsOff";
|
||||
case MWMLocationStatusTimeout: return "MWMLocationStatusTimeout";
|
||||
}
|
||||
CHECK(false, ("Unsupported value", static_cast<int>(status)));
|
||||
}
|
||||
|
||||
std::string DebugPrint(CLAuthorizationStatus status) {
|
||||
switch (status) {
|
||||
case kCLAuthorizationStatusNotDetermined: return "kCLAuthorizationStatusNotDetermined";
|
||||
case kCLAuthorizationStatusRestricted: return "kCLAuthorizationStatusRestricted";
|
||||
case kCLAuthorizationStatusDenied: return "kCLAuthorizationStatusDenied";
|
||||
case kCLAuthorizationStatusAuthorizedAlways: return "kCLAuthorizationStatusAuthorizedAlways";
|
||||
case kCLAuthorizationStatusAuthorizedWhenInUse: return "kCLAuthorizationStatusAuthorizedWhenInUse";
|
||||
}
|
||||
CHECK(false, ("Unsupported value", static_cast<int>(status)));
|
||||
}
|
||||
|
||||
struct DesiredAccuracy
|
||||
{
|
||||
CLLocationAccuracy charging;
|
||||
CLLocationAccuracy battery;
|
||||
};
|
||||
|
||||
struct GeoModeSettings
|
||||
{
|
||||
CLLocationDistance distanceFilter;
|
||||
DesiredAccuracy accuracy;
|
||||
};
|
||||
|
||||
std::map<GeoMode, GeoModeSettings> const kGeoSettings{
|
||||
{GeoMode::Pending,
|
||||
{.distanceFilter = kCLDistanceFilterNone,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBestForNavigation}}},
|
||||
{GeoMode::InPosition,
|
||||
{.distanceFilter = 2,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}},
|
||||
{GeoMode::NotInPosition,
|
||||
{.distanceFilter = 5,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}},
|
||||
{GeoMode::FollowAndRotate,
|
||||
{.distanceFilter = 2,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}},
|
||||
{GeoMode::VehicleRouting,
|
||||
{.distanceFilter = kCLDistanceFilterNone,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}},
|
||||
{GeoMode::PedestrianRouting,
|
||||
{.distanceFilter = 2,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}},
|
||||
{GeoMode::BicycleRouting,
|
||||
{.distanceFilter = 2,
|
||||
.accuracy = {.charging = kCLLocationAccuracyBestForNavigation,
|
||||
.battery = kCLLocationAccuracyBest}}}};
|
||||
|
||||
BOOL keepRunningInBackground()
|
||||
{
|
||||
if (GpsTracker::Instance().IsEnabled())
|
||||
return YES;
|
||||
|
||||
auto const isOnRoute = [MWMRouter isOnRoute];
|
||||
auto const isRouteFinished = [MWMRouter isRouteFinished];
|
||||
if (isOnRoute && !isRouteFinished)
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString * const kLocationPermissionRequestedKey = @"kLocationPermissionRequestedKey";
|
||||
NSString * const kLocationAlertNeedShowKey = @"kLocationAlertNeedShowKey";
|
||||
|
||||
BOOL needShowLocationAlert() {
|
||||
NSUserDefaults * ud = NSUserDefaults.standardUserDefaults;
|
||||
if ([ud objectForKey:kLocationAlertNeedShowKey] == nil)
|
||||
return YES;
|
||||
return [ud boolForKey:kLocationAlertNeedShowKey];
|
||||
}
|
||||
|
||||
void setShowLocationAlert(BOOL needShow) {
|
||||
NSUserDefaults * ud = NSUserDefaults.standardUserDefaults;
|
||||
[ud setBool:needShow forKey:kLocationAlertNeedShowKey];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@interface MWMLocationManager ()<CLLocationManagerDelegate>
|
||||
|
||||
@property(nonatomic) BOOL started;
|
||||
@property(nonatomic) CLLocationManager * locationManager;
|
||||
@property(nonatomic) GeoMode geoMode;
|
||||
@property(nonatomic) CLHeading * lastHeadingInfo;
|
||||
@property(nonatomic) CLLocation * lastLocationInfo;
|
||||
@property(nonatomic) MWMLocationStatus lastLocationStatus;
|
||||
@property(nonatomic) MWMLocationPredictor * predictor;
|
||||
@property(nonatomic) Observers * observers;
|
||||
@property(nonatomic) location::TLocationSource locationSource;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MWMLocationManager
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
+ (MWMLocationManager *)manager
|
||||
{
|
||||
static MWMLocationManager * manager;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[self alloc] initManager];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (instancetype)initManager
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_observers = [Observers weakObjectsHashTable];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[NSNotificationCenter.defaultCenter removeObserver:self];
|
||||
self.locationManager.delegate = nil;
|
||||
}
|
||||
|
||||
+ (void)start { [self manager].started = YES; }
|
||||
|
||||
+ (void)stop { [self manager].started = NO; }
|
||||
|
||||
+ (BOOL)isStarted { return [self manager].started; }
|
||||
|
||||
#pragma mark - Add/Remove Observers
|
||||
|
||||
+ (void)addObserver:(Observer)observer
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
MWMLocationManager * manager = [self manager];
|
||||
[manager.observers addObject:observer];
|
||||
[manager processLocationUpdate:manager.lastLocationInfo];
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)removeObserver:(Observer)observer
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[self manager].observers removeObject:observer];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - App Life Cycle
|
||||
|
||||
+ (void)applicationDidBecomeActive
|
||||
{
|
||||
[self start];
|
||||
}
|
||||
|
||||
+ (void)applicationWillResignActive
|
||||
{
|
||||
BOOL const keepRunning = keepRunningInBackground();
|
||||
MWMLocationManager * manager = [self manager];
|
||||
CLLocationManager * locationManager = manager.locationManager;
|
||||
if ([locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
|
||||
[locationManager setAllowsBackgroundLocationUpdates:keepRunning];
|
||||
manager.started = keepRunning;
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
+ (CLLocation *)lastLocation
|
||||
{
|
||||
MWMLocationManager * manager = [self manager];
|
||||
if (!manager.started || !manager.lastLocationInfo ||
|
||||
manager.lastLocationInfo.horizontalAccuracy < 0 ||
|
||||
manager.lastLocationStatus != MWMLocationStatusNoError)
|
||||
return nil;
|
||||
return manager.lastLocationInfo;
|
||||
}
|
||||
|
||||
+ (BOOL)isLocationProhibited
|
||||
{
|
||||
auto const status = [self manager].lastLocationStatus;
|
||||
return status == MWMLocationStatusDenied ||
|
||||
status == MWMLocationStatusGPSIsOff;
|
||||
}
|
||||
|
||||
+ (CLHeading *)lastHeading
|
||||
{
|
||||
MWMLocationManager * manager = [self manager];
|
||||
if (!manager.started || !manager.lastHeadingInfo || manager.lastHeadingInfo.headingAccuracy < 0)
|
||||
return nil;
|
||||
return manager.lastHeadingInfo;
|
||||
}
|
||||
|
||||
#pragma mark - Observer notifications
|
||||
|
||||
- (void)processLocationStatus:(MWMLocationStatus)locationStatus
|
||||
{
|
||||
LOG(LINFO, ("Location status updated from", DebugPrint(self.lastLocationStatus), "to", DebugPrint(locationStatus)));
|
||||
self.lastLocationStatus = locationStatus;
|
||||
if (self.lastLocationStatus != MWMLocationStatusNoError)
|
||||
GetFramework().OnLocationError((location::TLocationError)self.lastLocationStatus);
|
||||
for (Observer observer in self.observers)
|
||||
{
|
||||
if ([observer respondsToSelector:@selector(onLocationError:)])
|
||||
[observer onLocationError:self.lastLocationStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)processHeadingUpdate:(CLHeading *)headingInfo
|
||||
{
|
||||
self.lastHeadingInfo = headingInfo;
|
||||
GetFramework().OnCompassUpdate(location_util::compassInfoFromHeading(headingInfo));
|
||||
for (Observer observer in self.observers)
|
||||
{
|
||||
if ([observer respondsToSelector:@selector(onHeadingUpdate:)])
|
||||
[observer onHeadingUpdate:headingInfo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)processLocationUpdate:(CLLocation *)locationInfo
|
||||
{
|
||||
if (!locationInfo || self.lastLocationStatus != MWMLocationStatusNoError)
|
||||
return;
|
||||
[self onLocationUpdate:locationInfo source:self.locationSource];
|
||||
if (![self.lastLocationInfo isEqual:locationInfo])
|
||||
[self.predictor reset:locationInfo];
|
||||
}
|
||||
|
||||
- (void)onLocationUpdate:(CLLocation *)locationInfo source:(location::TLocationSource)source
|
||||
{
|
||||
location::GpsInfo const gpsInfo = location_util::gpsInfoFromLocation(locationInfo, source);
|
||||
GpsTracker::Instance().OnLocationUpdated(gpsInfo);
|
||||
GetFramework().OnLocationUpdate(gpsInfo);
|
||||
|
||||
self.lastLocationInfo = locationInfo;
|
||||
self.locationSource = source;
|
||||
for (Observer observer in self.observers)
|
||||
{
|
||||
if ([observer respondsToSelector:@selector(onLocationUpdate:)])
|
||||
[observer onLocationUpdate:locationInfo];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Location Status
|
||||
|
||||
- (void)setLastLocationStatus:(MWMLocationStatus)lastLocationStatus
|
||||
{
|
||||
_lastLocationStatus = lastLocationStatus;
|
||||
switch (lastLocationStatus)
|
||||
{
|
||||
case MWMLocationStatusNoError:
|
||||
break;
|
||||
case MWMLocationStatusNotSupported:
|
||||
[[MWMAlertViewController activeAlertController] presentLocationServiceNotSupportedAlert];
|
||||
break;
|
||||
case MWMLocationStatusDenied:
|
||||
if (needShowLocationAlert()) {
|
||||
[[MWMAlertViewController activeAlertController] presentLocationAlertWithCancelBlock:^{
|
||||
setShowLocationAlert(NO);
|
||||
}];
|
||||
}
|
||||
break;
|
||||
case MWMLocationStatusGPSIsOff:
|
||||
if (needShowLocationAlert()) {
|
||||
[[MWMAlertViewController activeAlertController] presentLocationServicesDisabledAlert];
|
||||
setShowLocationAlert(NO);
|
||||
}
|
||||
break;
|
||||
case MWMLocationStatusTimeout:
|
||||
CHECK(false, ("MWMLocationStatusTimeout is only used in Qt/Desktop builds"));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - My Position
|
||||
|
||||
+ (void)setMyPositionMode:(MWMMyPositionMode)mode
|
||||
{
|
||||
LOG(LINFO, ("MyPositionMode updated to", DebugPrint(mode)));
|
||||
MWMLocationManager * manager = [self manager];
|
||||
[manager.predictor setMyPositionMode:mode];
|
||||
[manager processLocationStatus:manager.lastLocationStatus];
|
||||
if ([MWMRouter isRoutingActive])
|
||||
{
|
||||
switch ([MWMRouter type])
|
||||
{
|
||||
case MWMRouterTypeVehicle: manager.geoMode = GeoMode::VehicleRouting; break;
|
||||
case MWMRouterTypePublicTransport:
|
||||
case MWMRouterTypePedestrian: manager.geoMode = GeoMode::PedestrianRouting; break;
|
||||
case MWMRouterTypeBicycle: manager.geoMode = GeoMode::BicycleRouting; break;
|
||||
case MWMRouterTypeRuler: break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case MWMMyPositionModePendingPosition: manager.geoMode = GeoMode::Pending; break;
|
||||
case MWMMyPositionModeNotFollowNoPosition:
|
||||
case MWMMyPositionModeNotFollow: manager.geoMode = GeoMode::NotInPosition; break;
|
||||
case MWMMyPositionModeFollow: manager.geoMode = GeoMode::InPosition; break;
|
||||
case MWMMyPositionModeFollowAndRotate: manager.geoMode = GeoMode::FollowAndRotate; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)checkLocationStatus
|
||||
{
|
||||
setShowLocationAlert(YES);
|
||||
[self.manager processLocationStatus:self.manager.lastLocationStatus];
|
||||
}
|
||||
|
||||
#pragma mark - Prediction
|
||||
|
||||
- (MWMLocationPredictor *)predictor
|
||||
{
|
||||
if (!_predictor)
|
||||
{
|
||||
__weak MWMLocationManager * weakSelf = self;
|
||||
_predictor = [[MWMLocationPredictor alloc] initWithOnPredictionBlock:^(CLLocation * location) {
|
||||
[weakSelf onLocationUpdate:location source:location::EPredictor];
|
||||
}];
|
||||
}
|
||||
return _predictor;
|
||||
}
|
||||
|
||||
#pragma mark - Device notifications
|
||||
|
||||
- (void)orientationChanged
|
||||
{
|
||||
self.locationManager.headingOrientation = (CLDeviceOrientation)UIDevice.currentDevice.orientation;
|
||||
}
|
||||
|
||||
- (void)batteryStateChangedNotification:(NSNotification *)notification
|
||||
{
|
||||
[MWMLocationManager refreshGeoModeSettingsFor:self.locationManager geoMode:self.geoMode];
|
||||
}
|
||||
|
||||
#pragma mark - Location manager
|
||||
|
||||
- (void)setGeoMode:(GeoMode)geoMode
|
||||
{
|
||||
LOG(LINFO, ("GeoMode updated to", geoMode));
|
||||
if (_geoMode == geoMode)
|
||||
return;
|
||||
_geoMode = geoMode;
|
||||
|
||||
CLLocationManager * locationManager = self.locationManager;
|
||||
switch (geoMode)
|
||||
{
|
||||
case GeoMode::Pending:
|
||||
case GeoMode::InPosition:
|
||||
case GeoMode::NotInPosition:
|
||||
case GeoMode::FollowAndRotate:
|
||||
locationManager.activityType = CLActivityTypeOther;
|
||||
break;
|
||||
case GeoMode::VehicleRouting:
|
||||
locationManager.activityType = CLActivityTypeAutomotiveNavigation;
|
||||
break;
|
||||
case GeoMode::PedestrianRouting:
|
||||
case GeoMode::BicycleRouting:
|
||||
locationManager.activityType = CLActivityTypeOtherNavigation;
|
||||
break;
|
||||
}
|
||||
|
||||
[MWMLocationManager refreshGeoModeSettingsFor:self.locationManager geoMode:self.geoMode];
|
||||
}
|
||||
|
||||
+ (void)refreshGeoModeSettingsFor:(CLLocationManager *)locationManager geoMode:(GeoMode)geoMode
|
||||
{
|
||||
UIDeviceBatteryState const state = UIDevice.currentDevice.batteryState;
|
||||
BOOL const isCharging =
|
||||
(state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull);
|
||||
GeoModeSettings const settings = kGeoSettings.at(geoMode);
|
||||
locationManager.desiredAccuracy =
|
||||
isCharging ? settings.accuracy.charging : settings.accuracy.battery;
|
||||
locationManager.distanceFilter = settings.distanceFilter;
|
||||
LOG(LINFO, ("Refreshed GeoMode settings: accuracy", locationManager.desiredAccuracy,
|
||||
"distance filter", locationManager.distanceFilter, "charging", isCharging));
|
||||
}
|
||||
|
||||
- (CLLocationManager *)locationManager
|
||||
{
|
||||
if (!_locationManager)
|
||||
{
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
_locationManager.delegate = self;
|
||||
[MWMLocationManager refreshGeoModeSettingsFor:_locationManager geoMode:self.geoMode];
|
||||
_locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
_locationManager.headingFilter = 3.0;
|
||||
}
|
||||
return _locationManager;
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)heading
|
||||
{
|
||||
[self processHeadingUpdate:heading];
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
CLLocation * location = locations.lastObject;
|
||||
// According to documentation, lat and lon are valid only if horizontalAccuracy is non-negative.
|
||||
// So we filter out such events completely.
|
||||
if (location.horizontalAccuracy < 0.)
|
||||
return;
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
// There is no simulator < 15.0 in the new XCode.
|
||||
if (@available(iOS 15.0, *))
|
||||
{
|
||||
// iOS Simulator doesn't provide any elevation in its locations. Mock it.
|
||||
static MountainElevationGenerator generator;
|
||||
location = [[CLLocation alloc] initWithCoordinate:location.coordinate
|
||||
altitude:generator.NextElevation()
|
||||
horizontalAccuracy:location.horizontalAccuracy
|
||||
verticalAccuracy:location.horizontalAccuracy
|
||||
course:location.course
|
||||
courseAccuracy:location.courseAccuracy
|
||||
speed:location.speed
|
||||
speedAccuracy:location.speedAccuracy
|
||||
timestamp:location.timestamp
|
||||
sourceInfo:location.sourceInformation];
|
||||
}
|
||||
#endif
|
||||
|
||||
self.lastLocationStatus = MWMLocationStatusNoError;
|
||||
self.locationSource = location::EAppleNative;
|
||||
[self processLocationUpdate:location];
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
LOG(LWARNING, ("CLLocationManagerDelegate: Did fail with error:", error.localizedDescription.UTF8String));
|
||||
if (self.lastLocationStatus == MWMLocationStatusNoError && error.code == kCLErrorDenied)
|
||||
[self processLocationStatus:MWMLocationStatusDenied];
|
||||
}
|
||||
|
||||
// Delegate's method didChangeAuthorizationStatus is used to handle the authorization status when the application finishes launching
|
||||
// or user changes location access in the application settings.
|
||||
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager
|
||||
{
|
||||
LOG(LWARNING, ("CLLocationManagerDelegate: Authorization status has changed to", DebugPrint(manager.authorizationStatus)));
|
||||
switch (manager.authorizationStatus) {
|
||||
case kCLAuthorizationStatusAuthorizedWhenInUse:
|
||||
case kCLAuthorizationStatusAuthorizedAlways:
|
||||
[self startUpdatingLocationFor:manager];
|
||||
break;
|
||||
case kCLAuthorizationStatusNotDetermined:
|
||||
[manager requestWhenInUseAuthorization];
|
||||
break;
|
||||
case kCLAuthorizationStatusRestricted:
|
||||
case kCLAuthorizationStatusDenied:
|
||||
if ([CLLocationManager locationServicesEnabled])
|
||||
[self processLocationStatus:MWMLocationStatusDenied];
|
||||
else
|
||||
[self processLocationStatus:MWMLocationStatusGPSIsOff];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
|
||||
{
|
||||
LOG(LINFO, ("CLLocationManagerDelegate: Location updates were paused"));
|
||||
}
|
||||
|
||||
- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
|
||||
{
|
||||
LOG(LINFO, ("CLLocationManagerDelegate: Location updates were resumed"));
|
||||
}
|
||||
|
||||
#pragma mark - Start / Stop
|
||||
|
||||
- (void)setStarted:(BOOL)started
|
||||
{
|
||||
if (_started == started)
|
||||
return;
|
||||
NSNotificationCenter * notificationCenter = NSNotificationCenter.defaultCenter;
|
||||
if (started) {
|
||||
_started = [self start];
|
||||
if (_started) {
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(orientationChanged)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(batteryStateChangedNotification:)
|
||||
name:UIDeviceBatteryStateDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
} else {
|
||||
_started = NO;
|
||||
[self stop];
|
||||
[notificationCenter removeObserver:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startUpdatingLocationFor:(CLLocationManager *)manager
|
||||
{
|
||||
LOG(LINFO, ("Start updating location"));
|
||||
[manager startUpdatingLocation];
|
||||
if ([CLLocationManager headingAvailable])
|
||||
[manager startUpdatingHeading];
|
||||
}
|
||||
|
||||
- (BOOL)start
|
||||
{
|
||||
if ([CLLocationManager locationServicesEnabled])
|
||||
{
|
||||
CLLocationManager * locationManager = self.locationManager;
|
||||
switch (locationManager.authorizationStatus)
|
||||
{
|
||||
case kCLAuthorizationStatusAuthorizedWhenInUse:
|
||||
case kCLAuthorizationStatusAuthorizedAlways:
|
||||
[self startUpdatingLocationFor:locationManager];
|
||||
return YES;
|
||||
break;
|
||||
case kCLAuthorizationStatusNotDetermined:
|
||||
[locationManager requestWhenInUseAuthorization];
|
||||
return YES;
|
||||
break;
|
||||
case kCLAuthorizationStatusRestricted:
|
||||
case kCLAuthorizationStatusDenied:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
LOG(LINFO, ("Stop updating location"));
|
||||
CLLocationManager * locationManager = self.locationManager;
|
||||
[locationManager stopUpdatingLocation];
|
||||
if ([CLLocationManager headingAvailable])
|
||||
[locationManager stopUpdatingHeading];
|
||||
}
|
||||
|
||||
#pragma mark - Location alert
|
||||
|
||||
+ (void)enableLocationAlert {
|
||||
setShowLocationAlert(YES);
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
@end
|
||||
22
iphone/Maps/Core/Location/MWMLocationObserver.h
Normal file
22
iphone/Maps/Core/Location/MWMLocationObserver.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//#include "platform/location.hpp"
|
||||
|
||||
typedef NS_ENUM(NSInteger, MWMLocationStatus) {
|
||||
MWMLocationStatusNoError,
|
||||
MWMLocationStatusNotSupported,
|
||||
MWMLocationStatusDenied,
|
||||
MWMLocationStatusGPSIsOff,
|
||||
MWMLocationStatusTimeout // Unused on iOS, (only used on Qt)
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol MWMLocationObserver<NSObject>
|
||||
|
||||
@optional
|
||||
- (void)onHeadingUpdate:(CLHeading *)heading;
|
||||
- (void)onLocationUpdate:(CLLocation *)location;
|
||||
- (void)onLocationError:(MWMLocationStatus)locationError;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
13
iphone/Maps/Core/Location/MWMLocationPredictor.h
Normal file
13
iphone/Maps/Core/Location/MWMLocationPredictor.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#import "MWMMyPositionMode.h"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
using TPredictionBlock = void (^)(CLLocation *);
|
||||
|
||||
@interface MWMLocationPredictor : NSObject
|
||||
|
||||
- (instancetype)initWithOnPredictionBlock:(TPredictionBlock)onPredictBlock;
|
||||
- (void)reset:(CLLocation *)info;
|
||||
- (void)setMyPositionMode:(MWMMyPositionMode)mode;
|
||||
|
||||
@end
|
||||
93
iphone/Maps/Core/Location/MWMLocationPredictor.mm
Normal file
93
iphone/Maps/Core/Location/MWMLocationPredictor.mm
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#import "MWMLocationPredictor.h"
|
||||
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
NSTimeInterval constexpr kPredictionIntervalInSeconds = 0.5;
|
||||
NSUInteger constexpr kMaxPredictionCount = 20;
|
||||
} // namespace
|
||||
|
||||
@interface MWMLocationPredictor ()
|
||||
|
||||
@property(copy, nonatomic) CLLocation * lastLocation;
|
||||
@property(nonatomic) BOOL isLastLocationValid;
|
||||
@property (nonatomic) BOOL isLastPositionModeValid;
|
||||
@property (nonatomic) NSUInteger predictionsCount;
|
||||
@property (copy, nonatomic) TPredictionBlock onPredictionBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MWMLocationPredictor
|
||||
|
||||
- (instancetype)initWithOnPredictionBlock:(TPredictionBlock)onPredictionBlock
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
_onPredictionBlock = [onPredictionBlock copy];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setMyPositionMode:(MWMMyPositionMode)mode
|
||||
{
|
||||
self.isLastPositionModeValid = (mode == MWMMyPositionModeFollowAndRotate);
|
||||
[self restart];
|
||||
}
|
||||
|
||||
- (void)reset:(CLLocation *)location
|
||||
{
|
||||
self.isLastLocationValid = (location.speed >= 0.0 && location.course >= 0.0);
|
||||
if (self.isLastLocationValid)
|
||||
self.lastLocation = location;
|
||||
|
||||
[self restart];
|
||||
}
|
||||
|
||||
- (BOOL)isActive
|
||||
{
|
||||
return self.isLastLocationValid && self.isLastPositionModeValid &&
|
||||
self.predictionsCount < kMaxPredictionCount;
|
||||
}
|
||||
|
||||
- (void)restart
|
||||
{
|
||||
self.predictionsCount = 0;
|
||||
[self schedule];
|
||||
}
|
||||
|
||||
- (void)schedule
|
||||
{
|
||||
SEL const predict = @selector(predict);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:predict object:nil];
|
||||
[self performSelector:predict withObject:nil afterDelay:kPredictionIntervalInSeconds];
|
||||
}
|
||||
|
||||
- (void)predict
|
||||
{
|
||||
if (!self.isActive)
|
||||
return;
|
||||
|
||||
self.predictionsCount++;
|
||||
|
||||
CLLocation * l = self.lastLocation;
|
||||
CLLocationCoordinate2D coordinate = l.coordinate;
|
||||
CLLocationDistance altitude = l.altitude;
|
||||
CLLocationAccuracy hAccuracy = l.horizontalAccuracy;
|
||||
CLLocationAccuracy vAccuracy = l.verticalAccuracy;
|
||||
CLLocationDirection course = l.course;
|
||||
CLLocationSpeed speed = l.speed;
|
||||
NSDate * timestamp = [NSDate date];
|
||||
Framework::PredictLocation(coordinate.latitude, coordinate.longitude, hAccuracy, course, speed,
|
||||
timestamp.timeIntervalSince1970 - l.timestamp.timeIntervalSince1970);
|
||||
CLLocation * location = [[CLLocation alloc] initWithCoordinate:coordinate
|
||||
altitude:altitude
|
||||
horizontalAccuracy:hAccuracy
|
||||
verticalAccuracy:vAccuracy
|
||||
course:course
|
||||
speed:speed
|
||||
timestamp:timestamp];
|
||||
self.onPredictionBlock(location);
|
||||
[self schedule];
|
||||
}
|
||||
|
||||
@end
|
||||
7
iphone/Maps/Core/Location/MWMMyPositionMode.h
Normal file
7
iphone/Maps/Core/Location/MWMMyPositionMode.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
typedef NS_CLOSED_ENUM(NSUInteger, MWMMyPositionMode) {
|
||||
MWMMyPositionModePendingPosition,
|
||||
MWMMyPositionModeNotFollowNoPosition,
|
||||
MWMMyPositionModeNotFollow,
|
||||
MWMMyPositionModeFollow,
|
||||
MWMMyPositionModeFollowAndRotate
|
||||
};
|
||||
67
iphone/Maps/Core/Location/MountainElevationGenerator.hpp
Normal file
67
iphone/Maps/Core/Location/MountainElevationGenerator.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <random>
|
||||
|
||||
class MountainElevationGenerator
|
||||
{
|
||||
static double constexpr kRandom{-1.};
|
||||
|
||||
std::mt19937_64 rng;
|
||||
|
||||
double const minElevation;
|
||||
double const maxElevation;
|
||||
double const maxSlopeChange;
|
||||
|
||||
std::normal_distribution<double> slopeChangeDist;
|
||||
|
||||
double currentElevation;
|
||||
double currentSlope;
|
||||
|
||||
double ValueOrRandomInRange(double value, double min, double max)
|
||||
{
|
||||
if (value != kRandom)
|
||||
return value;
|
||||
|
||||
return std::uniform_int_distribution<>(min, max)(rng);
|
||||
}
|
||||
|
||||
public:
|
||||
MountainElevationGenerator(double minElevation = kRandom, double maxElevation = kRandom,
|
||||
double startElevation = kRandom, double maxSlopeChange = kRandom,
|
||||
time_t seed = std::time(nullptr))
|
||||
: rng(seed)
|
||||
, minElevation(ValueOrRandomInRange(minElevation, 0., 2000.))
|
||||
, maxElevation(ValueOrRandomInRange(maxElevation, 3000., 7000.))
|
||||
, maxSlopeChange(ValueOrRandomInRange(maxSlopeChange, 1., 5.))
|
||||
, slopeChangeDist(0.0, maxSlopeChange)
|
||||
, currentElevation(ValueOrRandomInRange(startElevation, minElevation, maxElevation))
|
||||
, currentSlope(0.0)
|
||||
{}
|
||||
|
||||
double NextElevation()
|
||||
{
|
||||
// Change the slope gradually
|
||||
currentSlope += slopeChangeDist(rng);
|
||||
|
||||
// Limit maximum steepness
|
||||
currentSlope = std::max(-maxSlopeChange, std::min(maxSlopeChange, currentSlope));
|
||||
|
||||
// Update elevation based on current slope
|
||||
currentElevation += currentSlope;
|
||||
|
||||
// Ensure we stay within elevation bounds
|
||||
if (currentElevation < minElevation)
|
||||
{
|
||||
currentElevation = minElevation;
|
||||
currentSlope = std::abs(currentSlope) * 0.5; // Bounce back up
|
||||
}
|
||||
if (currentElevation > maxElevation)
|
||||
{
|
||||
currentElevation = maxElevation;
|
||||
currentSlope = -std::abs(currentSlope) * 0.5; // Start going down
|
||||
}
|
||||
|
||||
return currentElevation;
|
||||
}
|
||||
};
|
||||
41
iphone/Maps/Core/Location/location_util.h
Normal file
41
iphone/Maps/Core/Location/location_util.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
namespace location_util {
|
||||
|
||||
static location::GpsInfo gpsInfoFromLocation(CLLocation * l, location::TLocationSource source)
|
||||
{
|
||||
location::GpsInfo info;
|
||||
info.m_source = source;
|
||||
|
||||
info.m_latitude = l.coordinate.latitude;
|
||||
info.m_longitude = l.coordinate.longitude;
|
||||
info.m_timestamp = l.timestamp.timeIntervalSince1970;
|
||||
|
||||
if (l.horizontalAccuracy >= 0.0)
|
||||
info.m_horizontalAccuracy = l.horizontalAccuracy;
|
||||
|
||||
if (l.verticalAccuracy >= 0.0)
|
||||
{
|
||||
info.m_verticalAccuracy = l.verticalAccuracy;
|
||||
info.m_altitude = l.altitude;
|
||||
}
|
||||
|
||||
if (l.course >= 0.0)
|
||||
info.m_bearing = l.course;
|
||||
|
||||
if (l.speed >= 0.0)
|
||||
info.m_speed = l.speed;
|
||||
return info;
|
||||
}
|
||||
|
||||
static location::CompassInfo compassInfoFromHeading(CLHeading * h)
|
||||
{
|
||||
location::CompassInfo info;
|
||||
if (h.trueHeading >= 0.0)
|
||||
info.m_bearing = math::DegToRad(h.trueHeading);
|
||||
else if (h.headingAccuracy >= 0.0)
|
||||
info.m_bearing = math::DegToRad(h.magneticHeading);
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace location_util
|
||||
Loading…
Add table
Add a link
Reference in a new issue