[iOS] elevation profile basic integration

This commit is contained in:
Aleksey Belousov 2020-03-19 20:21:45 +03:00 committed by Vladimir Byko-Ianko
parent 1d03f1ba4b
commit 8fc6e1ad2d
24 changed files with 302 additions and 106 deletions

View file

@ -124,7 +124,7 @@
9957FAE8237AE5B000855F48 /* Logger.h in Headers */ = {isa = PBXBuildFile; fileRef = 9957FAE6237AE5B000855F48 /* Logger.h */; settings = {ATTRIBUTES = (Public, ); }; };
9957FAE9237AE5B000855F48 /* Logger.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9957FAE7237AE5B000855F48 /* Logger.mm */; };
9974CA2923DF1968003FE824 /* ElevationProfileData.h in Headers */ = {isa = PBXBuildFile; fileRef = 9974CA2723DF1968003FE824 /* ElevationProfileData.h */; settings = {ATTRIBUTES = (Public, ); }; };
9974CA2A23DF1968003FE824 /* ElevationProfileData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9974CA2823DF1968003FE824 /* ElevationProfileData.m */; };
9974CA2A23DF1968003FE824 /* ElevationProfileData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9974CA2823DF1968003FE824 /* ElevationProfileData.mm */; };
9974CA2D23DF197B003FE824 /* ElevationProfileData+Core.h in Headers */ = {isa = PBXBuildFile; fileRef = 9974CA2B23DF197B003FE824 /* ElevationProfileData+Core.h */; };
999D3A64237B097C00C5F7A8 /* DeepLinkSubscriptionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 999D3A62237B097C00C5F7A8 /* DeepLinkSubscriptionData.h */; settings = {ATTRIBUTES = (Public, ); }; };
999D3A65237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 999D3A63237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm */; };
@ -259,7 +259,7 @@
9957FAE6237AE5B000855F48 /* Logger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Logger.h; sourceTree = "<group>"; };
9957FAE7237AE5B000855F48 /* Logger.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Logger.mm; sourceTree = "<group>"; };
9974CA2723DF1968003FE824 /* ElevationProfileData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ElevationProfileData.h; sourceTree = "<group>"; };
9974CA2823DF1968003FE824 /* ElevationProfileData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ElevationProfileData.m; sourceTree = "<group>"; };
9974CA2823DF1968003FE824 /* ElevationProfileData.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ElevationProfileData.mm; sourceTree = "<group>"; };
9974CA2B23DF197B003FE824 /* ElevationProfileData+Core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElevationProfileData+Core.h"; sourceTree = "<group>"; };
999D3A62237B097C00C5F7A8 /* DeepLinkSubscriptionData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepLinkSubscriptionData.h; sourceTree = "<group>"; };
999D3A63237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DeepLinkSubscriptionData.mm; sourceTree = "<group>"; };
@ -633,7 +633,7 @@
isa = PBXGroup;
children = (
9974CA2723DF1968003FE824 /* ElevationProfileData.h */,
9974CA2823DF1968003FE824 /* ElevationProfileData.m */,
9974CA2823DF1968003FE824 /* ElevationProfileData.mm */,
9974CA2B23DF197B003FE824 /* ElevationProfileData+Core.h */,
9940621E23EAC57900493D1A /* ElevationHeightPoint.h */,
9940621F23EAC57900493D1A /* ElevationHeightPoint.m */,
@ -809,7 +809,7 @@
47942D9D237D927800DEFAE3 /* PlacePageBookmarkData.mm in Sources */,
4738A8E1239FACE7007C0F43 /* CoreBanner.mm in Sources */,
47942D86237CC55500DEFAE3 /* MWMOpeningHoursCommon.mm in Sources */,
9974CA2A23DF1968003FE824 /* ElevationProfileData.m in Sources */,
9974CA2A23DF1968003FE824 /* ElevationProfileData.mm in Sources */,
47942D82237CC52A00DEFAE3 /* MWMOpeningHours.mm in Sources */,
47942D73237CC41400DEFAE3 /* OpeningHours.mm in Sources */,
47C637DC2354B79B00E12DE0 /* MWMSearchFrameworkHelper.mm in Sources */,

View file

@ -119,5 +119,7 @@ typedef void (^PingCompletionBlock)(BOOL success);
- (NSString *)deviceId;
- (NSDictionary<NSString *, NSString *> *)getCatalogHeaders;
- (void)setElevationActivePoint:(double)distance trackId:(uint64_t)trackId;
@end
NS_ASSUME_NONNULL_END

View file

@ -768,4 +768,8 @@
return [result copy];
}
- (void)setElevationActivePoint:(double)distance trackId:(uint64_t)trackId {
self.bm.SetElevationActivePoint(trackId, distance);
}
@end

View file

@ -8,7 +8,7 @@ typedef void (^MWMBoolBlock)(BOOL);
typedef NS_ENUM(NSUInteger, MWMDayTime) { MWMDayTimeDay, MWMDayTimeNight };
typedef NS_ENUM(NSUInteger, MWMUnits) { MWMUnitsMetric, MWMUnitsImperial };
typedef NS_ENUM(NSUInteger, MWMUnits) { MWMUnitsMetric, MWMUnitsImperial } NS_SWIFT_NAME(Units);
typedef NS_ENUM(NSUInteger, MWMTheme) {
MWMThemeDay,

View file

@ -5,9 +5,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface ElevationHeightPoint : NSObject
@property(nonatomic, readonly) double distance;
@property(nonatomic, readonly) double height;
@property(nonatomic, readonly) double altitude;
- (instancetype)initWithDistance:(double)distance andHeight:(double)height;
- (instancetype)initWithDistance:(double)distance andAltitude:(double)altitude;
@end

View file

@ -2,11 +2,11 @@
@implementation ElevationHeightPoint
- (instancetype)initWithDistance:(double)distance andHeight:(double)height {
- (instancetype)initWithDistance:(double)distance andAltitude:(double)altitude {
self = [super init];
if (self) {
_distance = distance;
_height = height;
_altitude = altitude;
}
return self;
}

View file

@ -1,9 +1,13 @@
#import "ElevationProfileData.h"
#include "map/elevation_info.hpp"
NS_ASSUME_NONNULL_BEGIN
@interface ElevationProfileData (Core)
- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo activePoint:(double)activePoint;
@end
NS_ASSUME_NONNULL_END

View file

@ -11,16 +11,17 @@ typedef NS_ENUM(NSInteger, ElevationDifficulty) {
@interface ElevationProfileData : NSObject
@property(nonatomic, readonly) NSString* serverId;
@property(nonatomic, readonly) NSString* ascent;
@property(nonatomic, readonly) NSString* descent;
@property(nonatomic, readonly) NSString* maxAttitude;
@property(nonatomic, readonly) NSString* minAttitude;
@property(nonatomic, readonly) uint64_t trackId;
@property(nonatomic, readonly) NSUInteger ascent;
@property(nonatomic, readonly) NSUInteger descent;
@property(nonatomic, readonly) NSUInteger maxAttitude;
@property(nonatomic, readonly) NSUInteger minAttitude;
@property(nonatomic, readonly) ElevationDifficulty difficulty;
@property(nonatomic, readonly) NSString* trackTime;
@property(nonatomic, readonly, nullable) NSString* extendedDifficultyGrade;
@property(nonatomic, readonly, nullable) NSString* extendedDifficultyDescription;
@property(nonatomic, readonly) NSArray<ElevationHeightPoint*>* points;
@property(nonatomic, readonly) NSUInteger trackTime;
//@property(nonatomic, readonly, nullable) NSString *extendedDifficultyGrade;
//@property(nonatomic, readonly, nullable) NSString *extendedDifficultyDescription;
@property(nonatomic, readonly) NSArray<ElevationHeightPoint *> *points;
@property(nonatomic, readonly) double activePoint;
@end

View file

@ -1,31 +0,0 @@
#import "ElevationProfileData.h"
@implementation ElevationProfileData
- (instancetype)init {
self = [super init];
if (self) {
_serverId = @"0";
_ascent = @"10000 m";
_descent = @"20000 m";
_maxAttitude = @"1213 m";
_minAttitude = @"22134 m";
_difficulty = ElevationDifficultyModerate;
_trackTime = @"3h 12m";
_extendedDifficultyGrade = @"S5";
_extendedDifficultyDescription =
@"On a S2 trail you will face bigger roots and stones (but never the Rolling Stones). Usually the ground isnt "
@"compact and you will find steps and stairs. Oftentimes there are narrow turns and the steepness in some spots "
@"can be up to 70%.";
NSMutableArray<ElevationHeightPoint *> *randPoints = [[NSMutableArray alloc] init];
for (int i = 0; i < 100; i++) {
[randPoints addObject:[[ElevationHeightPoint alloc] initWithDistance:arc4random() % 100
andHeight:arc4random() % 200]];
}
_points = randPoints;
}
return self;
}
@end

View file

@ -0,0 +1,32 @@
#import "ElevationProfileData+Core.h"
@implementation ElevationProfileData
@end
@implementation ElevationProfileData (Core)
- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo activePoint:(double)activePoint {
self = [super init];
if (self) {
_trackId = elevationInfo.GetId();
_ascent = elevationInfo.GetAscent();
_descent = elevationInfo.GetDescent();
_maxAttitude = elevationInfo.GetMaxAltitude();
_minAttitude = elevationInfo.GetMinAltitude();
_difficulty = (ElevationDifficulty)elevationInfo.GetDifficulty();
_trackTime = elevationInfo.GetDuration();
NSMutableArray *pointsArray = [NSMutableArray array];
for (auto const &p : elevationInfo.GetPoints()) {
ElevationHeightPoint *point = [[ElevationHeightPoint alloc] initWithDistance:p.m_distance
andAltitude:p.m_altitude];
[pointsArray addObject:point];
}
_points = [pointsArray copy];
_activePoint = activePoint;
}
return self;
}
@end

View file

@ -60,7 +60,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly, nullable) HotelRooms *hotelRooms;
@property(nonatomic, readonly, nullable) UgcData *ugcData;
@property(nonatomic, readonly, nullable) ElevationProfileData *elevationProfileData;
@property(nonatomic, readonly) MWMMapNodeAttributes *mapNodeAttributes;
@property(nonatomic, readonly, nullable) MWMMapNodeAttributes *mapNodeAttributes;
@property(nonatomic, readonly, nullable) NSString *bookingSearchUrl;
@property(nonatomic, readonly) BOOL isLargeToponim;
@property(nonatomic, readonly) BOOL isSightseeing;

View file

@ -132,15 +132,26 @@ static PlacePageRoadType convertRoadType(RoadWarningMarkType roadType) {
_sponsoredDeeplink = @(rawData().GetSponsoredDeepLink().c_str());
}
_mapNodeAttributes = [[MWMStorage sharedStorage] attributesForCountry:@(rawData().GetCountryId().c_str())];
[[MWMStorage sharedStorage] addObserver:self];
// _elevationProfileData = [[ElevationProfileData alloc] init];
if (rawData().IsTrack()) {
auto const &bm = GetFramework().GetBookmarkManager();
auto const &trackId = rawData().GetTrackId();
_elevationProfileData = [[ElevationProfileData alloc] initWithElevationInfo:bm.MakeElevationInfo(trackId)
activePoint:bm.GetElevationActivePoint(trackId)];
}
auto const &countryId = rawData().GetCountryId();
if (!countryId.empty()) {
_mapNodeAttributes = [[MWMStorage sharedStorage] attributesForCountry:@(rawData().GetCountryId().c_str())];
[[MWMStorage sharedStorage] addObserver:self];
}
}
return self;
}
- (void)dealloc {
[[MWMStorage sharedStorage] removeObserver:self];
if (self.mapNodeAttributes != nil) {
[[MWMStorage sharedStorage] removeObserver:self];
}
}
- (void)loadOnlineDataWithCompletion:(MWMVoidBlock)completion {

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>

View file

@ -1,3 +1,4 @@
NS_SWIFT_NAME(Settings)
@interface MWMSettings : NSObject
+ (BOOL)adServerForbidden;

View file

@ -13,7 +13,7 @@ final class ThemeManager: NSObject {
@objc static func setDarkModeEnabled(_ val: Bool) {
instance.isDarkModeEnabled = val
instance.update(theme: MWMSettings.theme())
instance.update(theme: Settings.theme())
}
private func update(theme: MWMTheme) {
@ -68,7 +68,7 @@ final class ThemeManager: NSObject {
}
@objc static func invalidate() {
instance.update(theme: MWMSettings.theme())
instance.update(theme: Settings.theme())
}
@available(iOS, deprecated:13.0)

View file

@ -56,7 +56,7 @@ class ActionBarViewController: UIViewController {
stackView.addArrangedSubview(button)
if buttonType == .download {
downloadButton = button
updateDownloadButtonState(placePageData.mapNodeAttributes.nodeStatus)
updateDownloadButtonState(placePageData.mapNodeAttributes!.nodeStatus)
}
}
}
@ -79,8 +79,8 @@ class ActionBarViewController: UIViewController {
}
func updateDownloadButtonState(_ nodeStatus: MapNodeStatus) {
guard let downloadButton = downloadButton else { return }
switch self.placePageData.mapNodeAttributes.nodeStatus {
guard let downloadButton = downloadButton, let mapNodeAttributes = placePageData.mapNodeAttributes else { return }
switch mapNodeAttributes.nodeStatus {
case .downloading:
downloadButton.mapDownloadProgress?.state = .progress
case .applying, .inQueue:
@ -97,14 +97,16 @@ class ActionBarViewController: UIViewController {
}
private func configButton1() {
switch placePageData.mapNodeAttributes.nodeStatus {
case .onDiskOutOfDate, .onDisk, .undefined:
break
case .downloading, .applying, .inQueue, .error, .notDownloaded, .partly:
visibleButtons.append(.download)
return
@unknown default:
fatalError()
if let mapNodeAttributes = placePageData.mapNodeAttributes {
switch mapNodeAttributes.nodeStatus {
case .onDiskOutOfDate, .onDisk, .undefined:
break
case .downloading, .applying, .inQueue, .error, .notDownloaded, .partly:
visibleButtons.append(.download)
return
@unknown default:
fatalError()
}
}
var buttons: [ActionBarButtonType] = []
if isRoutePlanning {

View file

@ -20,8 +20,8 @@ class ElevationDetailsPresenter {
extension ElevationDetailsPresenter: ElevationDetailsPresenterProtocol {
func configure() {
view?.setDifficulty(data.difficulty)
view?.setExtendedDifficultyGrade(data.extendedDifficultyGrade ?? "")
view?.setDifficultyDescription(data.extendedDifficultyDescription ?? "")
// view?.setExtendedDifficultyGrade(data.extendedDifficultyGrade ?? "")
// view?.setDifficultyDescription(data.extendedDifficultyDescription ?? "")
Statistics.logEvent(kStatElevationProfilePageDetailsOpen, withParameters: [kStatType: data.difficulty.rawValue]);
}

View file

@ -6,8 +6,9 @@ class ElevationProfileBuilder {
let storyboard = UIStoryboard.instance(.placePage)
let viewController = storyboard.instantiateViewController(ofType: ElevationProfileViewController.self);
let presenter = ElevationProfilePresenter(view: viewController,
data: elevationProfileData,
delegate: delegate)
data: elevationProfileData,
imperialUnits: Settings.measurementUnits() == .imperial,
delegate: delegate)
viewController.presenter = presenter

View file

@ -1,3 +1,5 @@
import Chart
protocol ElevationProfilePresenterProtocol: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func configure()
func onAppear()
@ -7,15 +9,17 @@ protocol ElevationProfilePresenterProtocol: UICollectionViewDataSource, UICollec
func onDragBegin()
func onZoomBegin()
func onNavigateBegin()
func onMapPointChanged(_ point: CGFloat)
}
protocol ElevationProfileViewControllerDelegate: AnyObject {
func openDifficultyPopup()
func updateMapPoint(_ distance: Double)
}
fileprivate struct DescriptionsViewModel {
let title: String
let value: String
let value: UInt
let imageName: String
}
@ -24,16 +28,20 @@ class ElevationProfilePresenter: NSObject {
private let data: ElevationProfileData
private let delegate: ElevationProfileViewControllerDelegate?
private let cellSpacing: CGFloat = 8
private let descriptionModels: [DescriptionsViewModel]
private let chartData: ElevationProfileChartData
private let imperialUnits: Bool
init(view: ElevationProfileViewProtocol,
data: ElevationProfileData,
imperialUnits: Bool,
delegate: ElevationProfileViewControllerDelegate?) {
self.view = view
self.data = data
self.imperialUnits = imperialUnits
self.delegate = delegate
chartData = ElevationProfileChartData(data)
descriptionModels = [
DescriptionsViewModel(title: L("elevation_profile_ascent"), value: data.ascent, imageName: "ic_em_ascent_24"),
@ -47,25 +55,31 @@ class ElevationProfilePresenter: NSObject {
extension ElevationProfilePresenter: ElevationProfilePresenterProtocol {
func configure() {
view?.setDifficulty(data.difficulty)
view?.setTrackTime(data.trackTime)
if let extendedDifficultyGrade = data.extendedDifficultyGrade {
view?.isExtendedDifficultyLabelHidden = false
view?.setExtendedDifficultyGrade(extendedDifficultyGrade)
} else {
view?.setTrackTime("\(data.trackTime)")
let presentationData = ChartPresentationData(chartData,
formatter: ChartFormatter(imperial: imperialUnits),
useFilter: true)
view?.setChartData(presentationData)
let routeLength = data.points.last!.distance
view?.setActivePoint(data.activePoint / routeLength)
// if let extendedDifficultyGrade = data.extendedDifficultyGrade {
// view?.isExtendedDifficultyLabelHidden = false
// view?.setExtendedDifficultyGrade(extendedDifficultyGrade)
// } else {
view?.isExtendedDifficultyLabelHidden = true
}
// }
}
func onAppear() {
Statistics.logEvent(kStatElevationProfilePageOpen,
withParameters: [kStatServerId: data.serverId,
withParameters: [/*kStatServerId: data.serverId,*/ //TODO: clarify
kStatMethod: "info|track",
kStatState: "preview"])
}
func onDissapear() {
Statistics.logEvent(kStatElevationProfilePageClose,
withParameters: [kStatServerId: data.serverId,
withParameters: [/*kStatServerId: data.serverId,*/ //TODO: clarify
kStatMethod: "swipe"])
}
@ -75,7 +89,7 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol {
func onDragBegin() {
Statistics.logEvent(kStatElevationProfilePageDrag,
withParameters: [kStatServerId: data.serverId,
withParameters: [/*kStatServerId: data.serverId,*/ //TODO: clarify
kStatAction: "zoom_in|zoom_out|drag",
kStatSide: "left|right|all"])
}
@ -83,13 +97,24 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol {
func onZoomBegin() {
Statistics.logEvent(kStatElevationProfilePageZoom,
withParameters: [kStatServerId: data.serverId,
withParameters: [/*kStatServerId: data.serverId,*/ //TODO: clarify
kStatIsZoomIn: true])
}
func onNavigateBegin() {
Statistics.logEvent(kStatElevationProfilePageNavigationAction,
withParameters: [kStatServerId: data.serverId])
withParameters: [/*kStatServerId: data.serverId,*/ //TODO: clarify
:])
}
func onMapPointChanged(_ point: CGFloat) {
let x1 = Int(floor(point))
let x2 = Int(ceil(point))
let d1: Double = chartData.points[x1].distance
let d2: Double = chartData.points[x2].distance
let dx = Double(point.truncatingRemainder(dividingBy: 1))
let distance = d1 + (d2 - d1) * dx
delegate?.updateMapPoint(distance)
}
}
@ -103,7 +128,7 @@ extension ElevationProfilePresenter {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ElevationProfileDescriptionCell", for: indexPath) as! ElevationProfileDescriptionCell
let model = descriptionModels[indexPath.row]
cell.configure(title: model.title, value: model.value, imageName: model.imageName)
cell.configure(title: model.title, value: "\(model.value)", imageName: model.imageName)
return cell
}
}
@ -123,3 +148,111 @@ extension ElevationProfilePresenter {
return cellSpacing
}
}
fileprivate struct ElevationProfileChartData {
struct Line: IChartLine {
var values: [Int]
var name: String
var color: UIColor
var type: ChartLineType
}
fileprivate let chartLines: [Line]
fileprivate let distances: [Double]
fileprivate let points: [ElevationHeightPoint]
init(_ elevationData: ElevationProfileData) {
points = ElevationProfileChartData.rearrangePoints(elevationData.points)
let values = points.map { Int($0.altitude) }
// let formatter = MKDistanceFormatter()
// formatter.unitStyle = .abbreviated
// formatter.units = .metric
// labels = points.map { formatter.string(fromDistance: $0.distance )}
distances = points.map { $0.distance }
let color = UIColor(red: 0.12, green: 0.59, blue: 0.94, alpha: 1)
let l1 = Line(values: values, name: "Altitude", color: color, type: .line)
let l2 = Line(values: values, name: "Altitude", color: color.withAlphaComponent(0.12), type: .lineArea)
chartLines = [l1, l2]
}
private static func rearrangePoints(_ points: [ElevationHeightPoint]) -> [ElevationHeightPoint] {
if points.isEmpty {
return []
}
var result: [ElevationHeightPoint] = []
let distance = points.last?.distance ?? 0
let step = floor(distance / Double(points.count))
result.append(points[0])
var currentDistance = step
var i = 1
while i < points.count {
let prevPoint = points[i - 1]
let nextPoint = points[i]
if currentDistance > nextPoint.distance {
i += 1
continue
}
result.append(ElevationHeightPoint(distance: currentDistance,
andAltitude: altBetweenPoints(prevPoint, nextPoint, at: currentDistance)))
currentDistance += step
if currentDistance > nextPoint.distance {
i += 1
}
}
return result
}
private static func altBetweenPoints(_ p1: ElevationHeightPoint,
_ p2: ElevationHeightPoint,
at distance: Double) -> Double {
assert(distance > p1.distance && distance < p2.distance, "distance must be between points")
let d = (distance - p1.distance) / (p2.distance - p1.distance)
return p1.altitude + round(Double(p2.altitude - p1.altitude) * d)
}
}
extension ElevationProfileChartData: IChartData {
public var xAxisValues: [Double] {
distances
}
public var lines: [IChartLine] {
chartLines
}
public var type: ChartType {
.regular
}
}
fileprivate struct ChartFormatter: IFormatter {
private let distanceFormatter: MKDistanceFormatter
private let altFormatter: MeasurementFormatter
private let imperial: Bool
init(imperial: Bool) {
self.imperial = imperial
distanceFormatter = MKDistanceFormatter()
distanceFormatter.units = imperial ? .imperial : .metric
distanceFormatter.unitStyle = .abbreviated
altFormatter = MeasurementFormatter()
altFormatter.unitOptions = [.providedUnit]
}
func distanceString(from value: Double) -> String {
distanceFormatter.string(fromDistance: value)
}
func altitudeString(from value: Double) -> String {
let alt = imperial ? value / 0.3048 : value
let measurement = Measurement(value: alt.rounded(), unit: imperial ? UnitLength.feet : UnitLength.meters)
return altFormatter.string(from: measurement)
}
}

View file

@ -1,3 +1,5 @@
import Chart
protocol ElevationProfileViewProtocol: class {
var presenter: ElevationProfilePresenterProtocol? { get set }
@ -5,11 +7,14 @@ protocol ElevationProfileViewProtocol: class {
func setExtendedDifficultyGrade(_ value: String)
func setTrackTime(_ value: String?)
func setDifficulty(_ value: ElevationDifficulty)
func setChartData(_ data: ChartPresentationData)
func setActivePoint(_ distance: Double)
}
class ElevationProfileViewController: UIViewController {
var presenter: ElevationProfilePresenterProtocol?
@IBOutlet private var chartView: ChartView!
@IBOutlet private var graphViewContainer: UIView!
@IBOutlet private var descriptionCollectionView: UICollectionView!
@IBOutlet private var difficultyView: DifficultyView!
@ -22,6 +27,9 @@ class ElevationProfileViewController: UIViewController {
descriptionCollectionView.dataSource = presenter
descriptionCollectionView.delegate = presenter
presenter?.configure()
chartView.onSelectedPointChanged = { [weak self] in
self?.presenter?.onMapPointChanged($0)
}
}
override func viewDidAppear(_ animated: Bool) {
@ -55,10 +63,20 @@ extension ElevationProfileViewController: ElevationProfileViewProtocol {
func setExtendedDifficultyGrade(_ value: String) {
extendedDifficultyGradeLabel.text = value
}
func setTrackTime(_ value: String?) {
trackTimeLabel.text = value
}
func setDifficulty(_ value: ElevationDifficulty) {
difficultyView.difficulty = value
}
func setChartData(_ data: ChartPresentationData) {
chartView.chartData = data
}
func setActivePoint(_ distance: Double) {
chartView.setInfoX(CGFloat(distance))
}
}

View file

@ -2962,18 +2962,28 @@
<objects>
<viewController storyboardIdentifier="ElevationProfileViewController" id="d1y-Na-lDm" customClass="ElevationProfileViewController" customModule="maps_me" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="7Mx-au-yIa">
<rect key="frame" x="0.0" y="0.0" width="375" height="303"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="319"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jKi-gT-ZfM">
<rect key="frame" x="16" y="0.0" width="343" height="160"/>
<rect key="frame" x="16" y="0.0" width="343" height="176"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jIS-0e-Ztd" customClass="ChartView" customModule="Chart">
<rect key="frame" x="0.0" y="0.0" width="343" height="176"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBrownColor" red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="160" id="utH-YA-2pe"/>
<constraint firstItem="jIS-0e-Ztd" firstAttribute="top" secondItem="jKi-gT-ZfM" secondAttribute="top" id="QeA-Yb-58l"/>
<constraint firstAttribute="trailing" secondItem="jIS-0e-Ztd" secondAttribute="trailing" id="XRb-7G-y3q"/>
<constraint firstAttribute="bottom" secondItem="jIS-0e-Ztd" secondAttribute="bottom" id="g8g-f5-krt"/>
<constraint firstItem="jIS-0e-Ztd" firstAttribute="leading" secondItem="jKi-gT-ZfM" secondAttribute="leading" id="khr-Sp-8jS"/>
<constraint firstAttribute="height" constant="176" id="utH-YA-2pe"/>
</constraints>
</view>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Xc9-ED-V4K">
<rect key="frame" x="16" y="176" width="343" height="68"/>
<rect key="frame" x="16" y="192" width="343" height="68"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="68" id="AM4-tj-liE"/>
@ -3048,7 +3058,7 @@
</cells>
</collectionView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="Difficulty" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIo-No-CbK">
<rect key="frame" x="16" y="265" width="68.5" height="20.5"/>
<rect key="frame" x="16" y="281" width="68.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -3058,7 +3068,7 @@
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bc9-z0-p88" customClass="DifficultyView" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="91.5" y="271" width="40" height="10"/>
<rect key="frame" x="91.5" y="287" width="40" height="10"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="2Tg-JW-8Tr"/>
@ -3066,7 +3076,7 @@
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1h 10m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQJ-fW-QVh">
<rect key="frame" x="301" y="265" width="58" height="20.5"/>
<rect key="frame" x="301" y="281" width="58" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -3075,7 +3085,7 @@
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Time:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hoy-lg-Wl9">
<rect key="frame" x="249" y="264.5" width="43" height="21"/>
<rect key="frame" x="249" y="280.5" width="43" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -3085,13 +3095,13 @@
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g6D-fD-0Ug">
<rect key="frame" x="133.5" y="260.5" width="30" height="30"/>
<rect key="frame" x="133.5" y="276.5" width="30" height="30"/>
<connections>
<action selector="onExtendedDifficultyButtonPressed:" destination="d1y-Na-lDm" eventType="touchUpInside" id="4zH-m2-OSE"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="S1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GPk-XR-oL1" customClass="InsetsLabel" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="139" y="265" width="19" height="20.5"/>
<rect key="frame" x="139" y="281" width="19" height="20.5"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<nil key="textColor"/>
@ -3129,8 +3139,9 @@
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</view>
<size key="freeformSize" width="375" height="303"/>
<size key="freeformSize" width="375" height="319"/>
<connections>
<outlet property="chartView" destination="jIS-0e-Ztd" id="KHY-Bn-Pe6"/>
<outlet property="descriptionCollectionView" destination="Xc9-ED-V4K" id="dHB-dH-HYE"/>
<outlet property="difficultyView" destination="bc9-z0-p88" id="p5u-Au-7i2"/>
<outlet property="extendedDifficultyGradeLabel" destination="GPk-XR-oL1" id="SpR-XZ-6ou"/>
@ -3141,7 +3152,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="mfQ-ai-TWx" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="926.81159420289862" y="2501.4945652173915"/>
<point key="canvasLocation" x="925.60000000000002" y="2501.1994002998504"/>
</scene>
<!--More Reviews View Controller-->
<scene sceneID="Mgr-vZ-eCK">

View file

@ -205,11 +205,14 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate {
case .call:
MWMPlacePageManagerHelper.call(placePageData)
case .download:
switch placePageData.mapNodeAttributes.nodeStatus {
guard let mapNodeAttributes = placePageData.mapNodeAttributes else {
fatalError("Download button can't be displayed if mapNodeAttributes is empty")
}
switch mapNodeAttributes.nodeStatus {
case .downloading, .inQueue, .applying:
Storage.shared().cancelDownloadNode(placePageData.mapNodeAttributes.countryId)
Storage.shared().cancelDownloadNode(mapNodeAttributes.countryId)
case .notDownloaded, .partly, .error:
Storage.shared().downloadNode(placePageData.mapNodeAttributes.countryId)
Storage.shared().downloadNode(mapNodeAttributes.countryId)
case .undefined, .onDiskOutOfDate, .onDisk:
fatalError("Download button shouldn't be displayed when node is in these states")
@unknown default:
@ -251,4 +254,8 @@ extension PlacePageInteractor: ElevationProfileViewControllerDelegate {
func openDifficultyPopup() {
MWMPlacePageManagerHelper.openElevationDifficultPopup(placePageData)
}
func updateMapPoint(_ distance: Double) {
MWMBookmarksManager.shared().setElevationActivePoint(distance, trackId: placePageData.elevationProfileData!.trackId)
}
}

View file

@ -92,7 +92,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout {
lazy var buttonsViewController: PlacePageButtonsViewController = {
let vc = storyboard.instantiateViewController(ofType: PlacePageButtonsViewController.self)
vc.buttonsData = placePageData.buttonsData!
vc.buttonsEnabled = placePageData.mapNodeAttributes.nodeStatus == .onDisk
vc.buttonsEnabled = placePageData.mapNodeAttributes!.nodeStatus == .onDisk
vc.delegate = interactor
return vc
} ()
@ -208,8 +208,8 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout {
placePageData.onMapNodeStatusUpdate = { [weak self] in
guard let self = self else { return }
self.actionBarViewController.updateDownloadButtonState(self.placePageData.mapNodeAttributes.nodeStatus)
if self.placePageData.mapNodeAttributes.nodeStatus == .onDisk {
self.actionBarViewController.updateDownloadButtonState(self.placePageData.mapNodeAttributes!.nodeStatus)
if self.placePageData.mapNodeAttributes!.nodeStatus == .onDisk {
self.actionBarViewController.resetButtons()
if self.placePageData.buttonsData != nil {
self.buttonsViewController.buttonsEnabled = true

View file

@ -31,7 +31,7 @@ class PlacePageElevationLayout: IPlacePageLayout {
private func configureViewControllers() -> [UIViewController] {
var viewControllers = [UIViewController]()
viewControllers.append(previewViewController)
// viewControllers.append(previewViewController)
viewControllers.append(elevationMapViewController)
return viewControllers