diff options
-rw-r--r-- | gyp/platform-ios.gypi | 2 | ||||
-rw-r--r-- | include/mbgl/ios/MGLAnnotation.h | 28 | ||||
-rw-r--r-- | include/mbgl/ios/MGLMapView.h | 60 | ||||
-rw-r--r-- | include/mbgl/ios/MapboxGL.h | 7 | ||||
-rw-r--r-- | include/mbgl/map/map.hpp | 48 | ||||
-rw-r--r-- | include/mbgl/util/geo.hpp | 7 | ||||
-rw-r--r-- | ios/MapboxGL.podspec | 16 | ||||
-rw-r--r-- | ios/app/MBXViewController.mm | 5 | ||||
-rwxr-xr-x | ios/docs/install_docs.sh | 10 | ||||
-rwxr-xr-x | ios/docs/remove_docs.sh | 2 | ||||
-rw-r--r-- | platform/ios/MGLMapView.mm | 311 | ||||
-rwxr-xr-x | scripts/package_ios.sh | 1 | ||||
-rw-r--r-- | src/mbgl/geometry/buffer.hpp | 3 | ||||
-rw-r--r-- | src/mbgl/geometry/line_atlas.cpp | 6 | ||||
-rw-r--r-- | src/mbgl/geometry/sprite_atlas.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/geometry/vao.cpp | 3 | ||||
-rw-r--r-- | src/mbgl/map/annotation.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/map/environment.cpp | 55 | ||||
-rw-r--r-- | src/mbgl/map/environment.hpp | 42 | ||||
-rw-r--r-- | src/mbgl/map/map.cpp | 237 | ||||
-rw-r--r-- | src/mbgl/map/map_data.cpp | 44 | ||||
-rw-r--r-- | src/mbgl/map/map_data.hpp | 102 | ||||
-rw-r--r-- | src/mbgl/map/tile_data.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/util/texture_pool.cpp | 15 | ||||
m--------- | styles | 0 |
25 files changed, 845 insertions, 169 deletions
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 317b37b9b3..73f611a8e7 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -16,10 +16,12 @@ '../platform/darwin/asset_root.mm', '../platform/darwin/image.mm', '../platform/darwin/reachability.m', + '../include/mbgl/ios/MapboxGL.h', '../include/mbgl/ios/MGLMapboxEvents.h', '../platform/ios/MGLMapboxEvents.m', '../include/mbgl/ios/MGLMapView.h', '../platform/ios/MGLMapView.mm', + '../include/mbgl/ios/MGLAnnotation.h', '../include/mbgl/ios/MGLMetricsLocationManager.h', '../platform/ios/MGLMetricsLocationManager.m', '../include/mbgl/ios/MGLStyleFunctionValue.h', diff --git a/include/mbgl/ios/MGLAnnotation.h b/include/mbgl/ios/MGLAnnotation.h new file mode 100644 index 0000000000..e4907d9b94 --- /dev/null +++ b/include/mbgl/ios/MGLAnnotation.h @@ -0,0 +1,28 @@ +#import <Foundation/Foundation.h> +#import <CoreLocation/CoreLocation.h> + +/** The MGLAnnotation protocol is used to provide annotation-related information to a map view. To use this protocol, you adopt it in any custom objects that store or represent annotation data. Each object then serves as the source of information about a single map annotation and provides critical information, such as the annotation’s location on the map. Annotation objects do not provide the visual representation of the annotation but typically coordinate (in conjunction with the map view’s delegate) the creation of an appropriate objects to handle the display. +* +* An object that adopts this protocol must implement the `coordinate` property. The other methods of this protocol are optional. */ +@protocol MGLAnnotation <NSObject> + +/** @name Position Attributes */ + +/** The center point (specified as a map coordinate) of the annotation. (required) (read-only) */ +@property (nonatomic, readonly) CLLocationCoordinate2D coordinate; + +@optional + +/** @name Title Attributes */ + +/** The string containing the annotation’s title. +* +* Although this property is optional, if you support the selection of annotations in your map view, you are expected to provide this property. This string is displayed in the callout for the associated annotation. */ +@property (nonatomic, readonly, copy) NSString *title; + +/** The string containing the annotation’s subtitle. +* +* This string is displayed in the callout for the associated annotation. */ +@property (nonatomic, readonly, copy) NSString *subtitle; + +@end diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 481e878edc..3a6e622e12 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -2,6 +2,7 @@ #import <CoreLocation/CoreLocation.h> @protocol MGLMapViewDelegate; +@protocol MGLAnnotation; /** An MGLMapView object provides an embeddable map interface, similar to the one provided by Apple's MapKit. You use this class to display map information and to manipulate the map contents from your application. You can center the map on a given coordinate, specify the size of the area you want to display, and style the features of the map to fit your application's use case. * @@ -184,6 +185,55 @@ * @param styleName The map style name to use. */ - (void)useBundledStyleNamed:(NSString *)styleName; +#pragma mark - Annotating the Map + +/** @name Annotating the Map */ + +/** The complete list of annotations associated with the receiver. (read-only) +* +* The objects in this array must adopt the MGLAnnotation protocol. If no annotations are associated with the map view, the value of this property is `nil`. */ +@property (nonatomic, readonly) NSArray *annotations; + +/** Adds the specified annotation to the map view. +* @param annotation The annotation object to add to the receiver. This object must conform to the MGLAnnotation protocol. The map view retains the specified object. */ +- (void)addAnnotation:(id <MGLAnnotation>)annotation; + +/** Adds an array of annotation objects to the map view. +* @param annotations An array of annotation objects. Each object in the array must conform to the MGLAnnotation protocol. The map view retains the individual annotation objects. */ +- (void)addAnnotations:(NSArray *)annotations; + +/** Removes the specified annotation object from the map view. +* +* Removing an annotation object disassociates it from the map view entirely, preventing it from being displayed on the map. Thus, you would typically call this method only when you want to hide or delete a given annotation. +* +* @param annotation The annotation object to remove. This object must conform to the MGLAnnotation protocol. */ +- (void)removeAnnotation:(id <MGLAnnotation>)annotation; + +/** Removes an array of annotation objects from the map view. +* +* Removing annotation objects disassociates them from the map view entirely, preventing them from being displayed on the map. Thus, you would typically call this method only when you want to hide or delete the specified annotations. +* +* @param annotations The array of annotations to remove. Objects in the array must conform to the MGLAnnotation protocol. */ +- (void)removeAnnotations:(NSArray *)annotations; + +/** The annotations that are currently selected. +* +* Assigning a new array to this property selects only the first annotation in the array. */ +@property (nonatomic, copy) NSArray *selectedAnnotations; + +/** Selects the specified annotation and displays a callout view for it. +* +* If the specified annotation is not onscreen, this method has no effect. +* +* @param annotation The annotation object to select. +* @param animated If `YES`, the callout view is animated into position. */ +- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated; + +/** Deselects the specified annotation and hides its callout view. +* @param annotation The annotation object to deselect. +* @param animated If `YES`, the callout view is animated offscreen. */ +- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated; + #pragma mark - Debugging /** @name Debugging */ @@ -201,11 +251,19 @@ @end -// TODO +/** The MGLMapViewDelegate protocol defines a set of optional methods that you can use to receive map-related update messages. Because many map operations require the MGLMapView class to load data asynchronously, the map view calls these methods to notify your application when specific operations complete. The map view also uses these methods to request annotation marker symbology and to manage interactions with those markers. */ @protocol MGLMapViewDelegate <NSObject> @optional +/** @name Managing the Display of Annotations */ + +/** Returns the style's symbol name to use for the marker for the specified point annotation object. +* @param mapView The map view that requested the annotation symbol name. +* @param annotation The object representing the annotation that is about to be displayed. +* @return The marker symbol to display for the specified annotation or `nil` if you want to display the default symbol. */ +- (NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation; + // Responding to Map Position Changes // TODO diff --git a/include/mbgl/ios/MapboxGL.h b/include/mbgl/ios/MapboxGL.h new file mode 100644 index 0000000000..237d493f31 --- /dev/null +++ b/include/mbgl/ios/MapboxGL.h @@ -0,0 +1,7 @@ +#import "MGLAnnotation.h" +#import "MGLMapView.h" +#import "MGLStyleFunctionValue.h" +#import "MGLTypes.h" +#import "NSArray+MGLAdditions.h" +#import "NSDictionary+MGLAdditions.h" +#import "UIColor+MGLAdditions.h" diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 86c89f769d..e5eabdac4d 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -37,7 +37,9 @@ class GlyphAtlas; class SpriteAtlas; class LineAtlas; class Environment; +class EnvironmentScope; class AnnotationManager; +class MapData; class Map : private util::noncopyable { friend class View; @@ -74,7 +76,15 @@ public: void render(); // Notifies the Map thread that the state has changed and an update might be necessary. - void triggerUpdate(); + using UpdateType = uint32_t; + enum class Update : UpdateType { + Nothing = 0, + StyleInfo = 1 << 0, + Debug = 1 << 1, + DefaultTransitionDuration = 1 << 2, + Classes = 1 << 3, + }; + void triggerUpdate(Update = Update::Nothing); // Triggers a render. Can be called from any thread. void triggerRender(); @@ -91,8 +101,8 @@ public: void setDefaultTransitionDuration(std::chrono::steady_clock::duration duration = std::chrono::steady_clock::duration::zero()); std::chrono::steady_clock::duration getDefaultTransitionDuration(); - void setStyleURL(const std::string &url); - void setStyleJSON(std::string newStyleJSON, const std::string &base = ""); + void setStyleURL(const std::string& url); + void setStyleJSON(const std::string& json, const std::string& base = ""); std::string getStyleJSON() const; // Transition @@ -130,7 +140,7 @@ public: // API void setAccessToken(const std::string &token); - const std::string &getAccessToken() const; + std::string getAccessToken() const; // Projection inline void getWorldBoundsMeters(ProjectedMeters &sw, ProjectedMeters &ne) const { Projection::getWorldBoundsMeters(sw, ne); } @@ -156,7 +166,7 @@ public: bool getDebug() const; inline const TransformState &getState() const { return state; } - inline std::chrono::steady_clock::time_point getTime() const { return animationTime; } + std::chrono::steady_clock::time_point getTime() const; inline AnnotationManager& getAnnotationManager() const { return *annotationManager; } private: @@ -177,6 +187,13 @@ private: void updateSources(); void updateSources(const util::ptr<StyleLayerGroup> &group); + // Triggered by triggerUpdate(); + void update(); + + // Loads the style set in the data object. Called by Update::StyleInfo + void reloadStyle(); + void loadStyleJSON(const std::string& json, const std::string& base); + // Prepares a map render by updating the tiles we need for the current view, as well as updating // the stylesheet. void prepare(); @@ -192,6 +209,7 @@ private: Mode mode = Mode::None; const std::unique_ptr<Environment> env; + std::unique_ptr<EnvironmentScope> scope; View &view; private: @@ -223,26 +241,20 @@ private: FileSource& fileSource; util::ptr<Style> style; - const std::unique_ptr<GlyphAtlas> glyphAtlas; + std::unique_ptr<GlyphAtlas> glyphAtlas; util::ptr<GlyphStore> glyphStore; - const std::unique_ptr<SpriteAtlas> spriteAtlas; + std::unique_ptr<SpriteAtlas> spriteAtlas; util::ptr<Sprite> sprite; - const std::unique_ptr<LineAtlas> lineAtlas; + std::unique_ptr<LineAtlas> lineAtlas; util::ptr<TexturePool> texturePool; - const std::unique_ptr<Painter> painter; + std::unique_ptr<Painter> painter; util::ptr<AnnotationManager> annotationManager; - std::string styleURL; - std::string styleJSON = ""; - std::vector<std::string> classes; - std::string accessToken; - - std::chrono::steady_clock::duration defaultTransitionDuration; - - bool debug = false; - std::chrono::steady_clock::time_point animationTime = std::chrono::steady_clock::time_point::min(); + const std::unique_ptr<MapData> data; std::set<util::ptr<StyleSource>> activeSources; + + std::atomic<UpdateType> updated; }; } diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp index b99a6e6614..6ece6d4de9 100644 --- a/include/mbgl/util/geo.hpp +++ b/include/mbgl/util/geo.hpp @@ -32,6 +32,13 @@ struct LatLngBounds { if (point.longitude < sw.longitude) sw.longitude = point.longitude; if (point.longitude > ne.longitude) ne.longitude = point.longitude; } + + inline bool contains(const LatLng& point) { + return (point.latitude >= sw.latitude && + point.latitude <= ne.latitude && + point.longitude >= sw.longitude && + point.longitude <= ne.longitude); + } }; } diff --git a/ios/MapboxGL.podspec b/ios/MapboxGL.podspec index 6cdec1826d..6179667444 100644 --- a/ios/MapboxGL.podspec +++ b/ios/MapboxGL.podspec @@ -8,25 +8,25 @@ Pod::Spec.new do |m| m.homepage = 'https://www.mapbox.com/blog/mapbox-gl/' m.license = 'BSD' m.author = { 'Mapbox' => 'mobile@mapbox.com' } - m.screenshot = 'https://raw.githubusercontent.com/mapbox/mapbox-gl-cocoa/master/pkg/screenshot.png' - m.social_media_url = 'https://twitter.com/Mapbox' + m.screenshot = 'https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/ios/screenshot.png' + m.social_media_url = 'https://twitter.com/mapbox' - m.source = { :git => 'https://github.com/mapbox/mapbox-gl-cocoa.git', :tag => m.version.to_s } + m.source = { :http => "http://mapbox.s3.amazonaws.com/mapbox-gl-native/ios/mapbox-gl-ios-#{m.version.to_s}.zip" } m.platform = :ios m.ios.deployment_target = '7.0' - m.source_files = 'dist/static/Headers/*.h' + m.source_files = 'Headers/*.h' m.requires_arc = true - m.resource_bundle = { 'MapboxGL' => 'dist/static/MapboxGL.bundle/*' } + m.resource_bundle = { 'MapboxGL' => 'MapboxGL.bundle/*' } - m.frameworks = 'CoreLocation', 'Foundation', 'GLKit', 'SystemConfiguration', 'UIKit' + m.frameworks = 'CoreLocation', 'GLKit', 'ImageIO', 'MobileCoreServices', 'SystemConfiguration' - m.libraries = 'MapboxGL', 'c++', 'sqlite3', 'z' + m.libraries = 'c++', 'sqlite3', 'z' - m.vendored_libraries = 'dist/static/libMapboxGL.a' + m.vendored_library = 'libMapboxGL.a' m.xcconfig = { 'OTHER_CPLUSPLUSFLAGS' => '-std=gnu++11 -stdlib=libc++' } diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index 98b337f927..5286c71908 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -1,6 +1,6 @@ #import "MBXViewController.h" -#import <mbgl/ios/MGLMapView.h> +#import <mbgl/ios/MapboxGL.h> #import <mbgl/platform/darwin/settings_nsuserdefaults.hpp> @@ -9,6 +9,7 @@ static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000]; static NSArray *const kStyleNames = @[ + @"Emerald", @"Bright", @"Basic", @"Outdoors", @@ -232,7 +233,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; } } -#pragma mark - Location +#pragma mark - CLLocationManagerDelegate - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { diff --git a/ios/docs/install_docs.sh b/ios/docs/install_docs.sh index 4d116c0332..cd6fcdda73 100755 --- a/ios/docs/install_docs.sh +++ b/ios/docs/install_docs.sh @@ -5,7 +5,7 @@ if [ -z `which appledoc` ]; then exit 1 fi -VERSION=$( git tag | sort -r | sed -n '1p' ) +VERSION=$( git tag | grep ^[0-9] | sort -r | sed -n '1p' ) echo "Creating new docs for $VERSION..." echo @@ -15,10 +15,4 @@ appledoc \ --project-company Mapbox \ --create-docset \ --company-id com.mapbox \ - --ignore app \ - --ignore dist \ - --ignore pkg \ - --ignore test \ - --ignore .m \ - --ignore .mm \ - .
\ No newline at end of file + ../../include/mbgl/ios diff --git a/ios/docs/remove_docs.sh b/ios/docs/remove_docs.sh index bb8c008dc2..09e2c1d399 100755 --- a/ios/docs/remove_docs.sh +++ b/ios/docs/remove_docs.sh @@ -4,4 +4,4 @@ echo echo "Removing docs from ~/Library/Developer/Shared/Documentation/DocSets..." echo -rm -rfv ~/Library/Developer/Shared/Documentation/DocSets/com.mapbox.Mapbox-GL-*
\ No newline at end of file +rm -rfv ~/Library/Developer/Shared/Documentation/DocSets/com.mapbox.Mapbox-GL-* diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index d6e7a16a21..f20c43cae4 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -16,11 +16,13 @@ #import "MGLTypes.h" #import "MGLStyleFunctionValue.h" +#import "MGLAnnotation.h" #import "UIColor+MGLAdditions.h" #import "NSArray+MGLAdditions.h" #import "NSDictionary+MGLAdditions.h" +#import <algorithm> #import "MGLMapboxEvents.h" #import "MGLMetricsLocationManager.h" @@ -41,6 +43,10 @@ const std::string &defaultCacheDatabase() { static dispatch_once_t loadGLExtensions; +NSString *const MGLDefaultStyleName = @"Emerald"; +NSString *const MGLStyleVersion = @"v7"; +NSString *const MGLDefaultStyleMarkerSymbolName = @"default_marker"; + extern NSString *const MGLStyleKeyGeneric; extern NSString *const MGLStyleKeyFill; extern NSString *const MGLStyleKeyLine; @@ -54,6 +60,8 @@ extern NSString *const MGLStyleValueFunctionAllowed; NSTimeInterval const MGLAnimationDuration = 0.3; +NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey"; + #pragma mark - Private - @interface MGLMapView () <UIGestureRecognizerDelegate, GLKViewDelegate> @@ -69,6 +77,9 @@ NSTimeInterval const MGLAnimationDuration = 0.3; @property (nonatomic) UIRotationGestureRecognizer *rotate; @property (nonatomic) UILongPressGestureRecognizer *quickZoom; @property (nonatomic) NSMutableArray *bundledStyleNames; +@property (nonatomic) NSMapTable *annotationsStore; +@property (nonatomic) std::vector<uint32_t> annotationsNearbyLastTap; +@property (nonatomic, weak) id <MGLAnnotation> selectedAnnotation; @property (nonatomic, readonly) NSDictionary *allowedStyleTypes; @property (nonatomic) CGPoint centerPoint; @property (nonatomic) CGFloat scale; @@ -178,7 +189,9 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; { if ( ! styleJSON) { - [self useBundledStyleNamed:@"bright-v7"]; + [self useBundledStyleNamed:[[[MGLDefaultStyleName lowercaseString] + stringByAppendingString:@"-"] + stringByAppendingString:MGLStyleVersion]]; } else { @@ -280,6 +293,12 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; Reachability* reachability = [Reachability reachabilityForInternetConnection]; [reachability startNotifier]; + // setup annotations + // + _annotationsStore = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory]; + std::string defaultSymbolName([MGLDefaultStyleMarkerSymbolName cStringUsingEncoding:[NSString defaultCStringEncoding]]); + mbglMap->setDefaultPointAnnotationSymbol(defaultSymbolName); + // setup logo bug // _logoBug = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"mapbox.png"]]; @@ -333,6 +352,10 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; doubleTap.numberOfTapsRequired = 2; [self addGestureRecognizer:doubleTap]; + UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; + [singleTap requireGestureRecognizerToFail:doubleTap]; + [self addGestureRecognizer:singleTap]; + UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTapGesture:)]; twoFingerTap.numberOfTouchesRequired = 2; [twoFingerTap requireGestureRecognizerToFail:_pinch]; @@ -701,6 +724,118 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; } } +- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap +{ + CGPoint tapPoint = [singleTap locationInView:self]; + + // tolerances based on touch size & typical marker aspect ratio + CGFloat toleranceWidth = 50; + CGFloat toleranceHeight = 75; + + // setup a recognition area weighted 2/3 of the way above the point to account for average marker imagery + CGRect tapRect = CGRectMake(tapPoint.x - toleranceWidth / 2, tapPoint.y - 2 * toleranceHeight / 3, toleranceWidth, toleranceHeight); + CGPoint tapRectLowerLeft = CGPointMake(tapRect.origin.x, tapRect.origin.y + tapRect.size.height); + CGPoint tapRectUpperLeft = CGPointMake(tapRect.origin.x, tapRect.origin.y); + CGPoint tapRectUpperRight = CGPointMake(tapRect.origin.x + tapRect.size.width, tapRect.origin.y); + CGPoint tapRectLowerRight = CGPointMake(tapRect.origin.x + tapRect.size.width, tapRect.origin.y + tapRect.size.height); + + // figure out what that means in coordinate space + CLLocationCoordinate2D coordinate; + mbgl::LatLngBounds tapBounds; + + coordinate = [self convertPoint:tapRectLowerLeft toCoordinateFromView:self]; + tapBounds.extend(mbgl::LatLng(coordinate.latitude, coordinate.longitude)); + + coordinate = [self convertPoint:tapRectUpperLeft toCoordinateFromView:self]; + tapBounds.extend(mbgl::LatLng(coordinate.latitude, coordinate.longitude)); + + coordinate = [self convertPoint:tapRectUpperRight toCoordinateFromView:self]; + tapBounds.extend(mbgl::LatLng(coordinate.latitude, coordinate.longitude)); + + coordinate = [self convertPoint:tapRectLowerRight toCoordinateFromView:self]; + tapBounds.extend(mbgl::LatLng(coordinate.latitude, coordinate.longitude)); + + // query for nearby annotations + std::vector<uint32_t> nearbyAnnotations = mbglMap->getAnnotationsInBounds(tapBounds); + + int32_t newSelectedAnnotationID = -1; + + if (nearbyAnnotations.size()) + { + // there is at least one nearby annotation; select one + // + // first, sort for comparison and iteration + std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end()); + + if (nearbyAnnotations == self.annotationsNearbyLastTap) + { + // the selection candidates haven't changed; cycle through them + if (self.selectedAnnotation && + [[[self.annotationsStore objectForKey:self.selectedAnnotation] + objectForKey:MGLAnnotationIDKey] unsignedIntValue] == self.annotationsNearbyLastTap.back()) + { + // the selected annotation is the last in the set; cycle back to the first + // note: this could be the selected annotation if only one in set + newSelectedAnnotationID = self.annotationsNearbyLastTap.front(); + } + else if (self.selectedAnnotation) + { + // otherwise increment the selection through the candidates + uint32_t currentID = [[[self.annotationsStore objectForKey:self.selectedAnnotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue]; + auto result = std::find(self.annotationsNearbyLastTap.begin(), self.annotationsNearbyLastTap.end(), currentID); + auto distance = std::distance(self.annotationsNearbyLastTap.begin(), result); + newSelectedAnnotationID = self.annotationsNearbyLastTap[distance + 1]; + } + else + { + // no current selection; select the first one + newSelectedAnnotationID = self.annotationsNearbyLastTap.front(); + } + } + else + { + // start tracking a new set of nearby annotations + self.annotationsNearbyLastTap = nearbyAnnotations; + + // select the first one + newSelectedAnnotationID = self.annotationsNearbyLastTap.front(); + } + } + else + { + // there are no nearby annotations; deselect if necessary + newSelectedAnnotationID = -1; + } + + if (newSelectedAnnotationID >= 0) + { + // find & select model object for selection + NSEnumerator *enumerator = self.annotationsStore.keyEnumerator; + + while (id <MGLAnnotation> annotation = enumerator.nextObject) + { + if ([[[self.annotationsStore objectForKey:annotation] objectForKey:MGLAnnotationIDKey] integerValue] == newSelectedAnnotationID) + { + // only change selection status if not the currently selected annotation + if ( ! [annotation isEqual:self.selectedAnnotation]) + { + [self selectAnnotation:annotation animated:YES]; + } + + // either way, we should stop enumerating + break; + } + } + } + else + { + // deselect any selected annotation + if (self.selectedAnnotation) [self deselectAnnotation:self.selectedAnnotation animated:YES]; + } + + NSLog(@"%i (%@)", newSelectedAnnotationID, self.selectedAnnotation.title); +} + - (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap { if ( ! self.isZoomEnabled) return; @@ -857,7 +992,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; { CGFloat duration = (animated ? MGLAnimationDuration : 0); - mbglMap->setLatLng(mbgl::LatLng(coordinate.latitude, coordinate.longitude), secondsAsDuration(duration)); + mbglMap->setLatLng(coordinateToLatLng(coordinate), secondsAsDuration(duration)); } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate @@ -867,16 +1002,14 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (CLLocationCoordinate2D)centerCoordinate { - mbgl::LatLng latLng = mbglMap->getLatLng(); - - return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude); + return latLngToCoordinate(mbglMap->getLatLng()); } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated { CGFloat duration = (animated ? MGLAnimationDuration : 0); - mbglMap->setLatLngZoom(mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude), zoomLevel, secondsAsDuration(duration)); + mbglMap->setLatLngZoom(coordinateToLatLng(centerCoordinate), zoomLevel, secondsAsDuration(duration)); [self unrotateIfNeededAnimated:animated]; } @@ -932,9 +1065,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; // convertedPoint.y = self.bounds.size.height - convertedPoint.y; - mbgl::LatLng latLng = mbglMap->latLngForPixel(mbgl::vec2<double>(convertedPoint.x, convertedPoint.y)); - - return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude); + return latLngToCoordinate(mbglMap->latLngForPixel(mbgl::vec2<double>(convertedPoint.x, convertedPoint.y))); } - (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view @@ -953,6 +1084,32 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; return mbglMap->getMetersPerPixelAtLatitude(latitude, self.zoomLevel); } +mbgl::LatLng coordinateToLatLng(CLLocationCoordinate2D coordinate) +{ + return mbgl::LatLng(coordinate.latitude, coordinate.longitude); +} + +CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) +{ + return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude); +} + +- (mbgl::LatLngBounds)viewportBounds +{ + mbgl::LatLngBounds bounds; + + bounds.extend(coordinateToLatLng( + [self convertPoint:CGPointMake(0, 0) toCoordinateFromView:self])); + bounds.extend(coordinateToLatLng( + [self convertPoint:CGPointMake(self.bounds.size.width, 0) toCoordinateFromView:self])); + bounds.extend(coordinateToLatLng( + [self convertPoint:CGPointMake(0, self.bounds.size.height) toCoordinateFromView:self])); + bounds.extend(coordinateToLatLng( + [self convertPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height) toCoordinateFromView:self])); + + return bounds; +} + #pragma mark - Styling - - (NSDictionary *)getRawStyle @@ -1412,6 +1569,142 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; return MGLStyleAllowedTypes; } +#pragma mark - Annotations - + +- (NSArray *)annotations +{ + if ([_annotationsStore count]) + { + NSMutableArray *result = [NSMutableArray array]; + + NSEnumerator *keyEnumerator = [_annotationsStore keyEnumerator]; + id <MGLAnnotation> annotation; + + while (annotation = [keyEnumerator nextObject]) + { + [result addObject:annotation]; + } + + return [NSArray arrayWithArray:result]; + } + + return nil; +} + +- (void)addAnnotation:(id <MGLAnnotation>)annotation +{ + if ( ! annotation) return; + + // The core bulk add API is efficient with respect to indexing and + // screen refreshes, thus we should defer to it even for individual adds. + // + [self addAnnotations:@[ annotation ]]; +} + +- (void)addAnnotations:(NSArray *)annotations +{ + if ( ! annotations) return; + + std::vector<mbgl::LatLng> latLngs; + latLngs.reserve(annotations.count); + + std::vector<std::string> symbols; + symbols.reserve(annotations.count); + + BOOL delegateImplementsSymbolLookup = [self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)]; + + for (id <MGLAnnotation> annotation in annotations) + { + assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]); + + latLngs.push_back(coordinateToLatLng(annotation.coordinate)); + + NSString *symbolName = nil; + + if (delegateImplementsSymbolLookup) + { + symbolName = [self.delegate mapView:self symbolNameForAnnotation:annotation]; + } + + symbols.push_back((symbolName ? [symbolName cStringUsingEncoding:[NSString defaultCStringEncoding]] : "")); + } + + std::vector<uint32_t> annotationIDs = mbglMap->addPointAnnotations(latLngs, symbols); + + for (size_t i = 0; i < annotationIDs.size(); ++i) + { + [self.annotationsStore setObject:@{ MGLAnnotationIDKey : @(annotationIDs[i]) } + forKey:annotations[i]]; + } +} + +- (void)removeAnnotation:(id <MGLAnnotation>)annotation +{ + if ( ! annotation) return; + + // The core bulk deletion API is efficient with respect to indexing + // and screen refreshes, thus we should defer to it even for + // individual deletes. + // + [self removeAnnotations:@[ annotation ]]; +} + +- (void)removeAnnotations:(NSArray *)annotations +{ + if ( ! annotations) return; + + std::vector<uint32_t> annotationIDsToRemove; + annotationIDsToRemove.reserve(annotations.count); + + for (id <MGLAnnotation> annotation in annotations) + { + assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]); + + annotationIDsToRemove.push_back([[self.annotationsStore objectForKey:annotation] unsignedIntValue]); + [self.annotationsStore removeObjectForKey:annotation]; + } + + mbglMap->removeAnnotations(annotationIDsToRemove); +} + +- (NSArray *)selectedAnnotations +{ + return (self.selectedAnnotation ? @[ self.selectedAnnotation ] : @[]); +} + +- (void)setSelectedAnnotations:(NSArray *)selectedAnnotations +{ + if ( ! selectedAnnotations.count) return; + + id <MGLAnnotation> firstAnnotation = selectedAnnotations[0]; + + assert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)]); + + if ( ! [self viewportBounds].contains(coordinateToLatLng(firstAnnotation.coordinate))) return; + + self.selectedAnnotation = firstAnnotation; +} + +- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated +{ + (void)animated; + + if ( ! annotation) return; + + if ( ! [self viewportBounds].contains(coordinateToLatLng(annotation.coordinate))) return; + + self.selectedAnnotation = annotation; +} + +- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated +{ + (void)animated; + + if ( ! annotation) return; + + if ([self.selectedAnnotation isEqual:annotation]) self.selectedAnnotation = nil; +} + #pragma mark - Utility - + (CGFloat)degreesToRadians:(CGFloat)degrees diff --git a/scripts/package_ios.sh b/scripts/package_ios.sh index 89aac5ee79..588ff6d940 100755 --- a/scripts/package_ios.sh +++ b/scripts/package_ios.sh @@ -70,6 +70,7 @@ cp -pv include/mbgl/ios/* "${OUTPUT}/static/Headers" # complications between faked GYP bundles-as-executables, device build # dependencies, and code signing. step "Copying Resources..." +cp -pv LICENSE.md "${OUTPUT}/static" mkdir -p "${OUTPUT}/static/${NAME}.bundle" cp -pv platform/ios/resources/* "${OUTPUT}/static/${NAME}.bundle" cp -prv styles/styles "${OUTPUT}/static/${NAME}.bundle/styles" diff --git a/src/mbgl/geometry/buffer.hpp b/src/mbgl/geometry/buffer.hpp index 3649574bbf..4198425ecf 100644 --- a/src/mbgl/geometry/buffer.hpp +++ b/src/mbgl/geometry/buffer.hpp @@ -3,6 +3,7 @@ #include <mbgl/platform/gl.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/map/environment.hpp> #include <cstdlib> #include <cassert> @@ -21,7 +22,7 @@ public: ~Buffer() { cleanup(); if (buffer != 0) { - MBGL_CHECK_ERROR(glDeleteBuffers(1, &buffer)); + Environment::Get().abandonBuffer(buffer); buffer = 0; } } diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp index 8be1a8d53c..507cc7b087 100644 --- a/src/mbgl/geometry/line_atlas.cpp +++ b/src/mbgl/geometry/line_atlas.cpp @@ -1,3 +1,4 @@ +#include <mbgl/map/environment.hpp> #include <mbgl/geometry/line_atlas.hpp> #include <mbgl/platform/gl.hpp> #include <mbgl/platform/log.hpp> @@ -18,9 +19,10 @@ LineAtlas::LineAtlas(uint16_t w, uint16_t h) LineAtlas::~LineAtlas() { std::lock_guard<std::recursive_mutex> lock(mtx); - MBGL_CHECK_ERROR(glDeleteTextures(1, &texture)); - delete[] data; + Environment::Get().abandonTexture(texture); texture = 0; + + delete[] data; } LinePatternPos LineAtlas::getDashPosition(const std::vector<float> &dasharray, bool round) { diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp index dce772f2e4..bf31c6e38e 100644 --- a/src/mbgl/geometry/sprite_atlas.cpp +++ b/src/mbgl/geometry/sprite_atlas.cpp @@ -1,3 +1,4 @@ +#include <mbgl/map/environment.hpp> #include <mbgl/geometry/sprite_atlas.hpp> #include <mbgl/platform/gl.hpp> #include <mbgl/platform/log.hpp> @@ -293,8 +294,7 @@ void SpriteAtlas::bind(bool linear) { SpriteAtlas::~SpriteAtlas() { std::lock_guard<std::recursive_mutex> lock(mtx); - - MBGL_CHECK_ERROR(glDeleteTextures(1, &texture)); + Environment::Get().abandonTexture(texture); texture = 0; ::operator delete(data), data = nullptr; } diff --git a/src/mbgl/geometry/vao.cpp b/src/mbgl/geometry/vao.cpp index 00976b4d54..fef74396a9 100644 --- a/src/mbgl/geometry/vao.cpp +++ b/src/mbgl/geometry/vao.cpp @@ -1,6 +1,7 @@ #include <mbgl/geometry/vao.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/util/string.hpp> +#include <mbgl/map/environment.hpp> namespace mbgl { @@ -11,7 +12,7 @@ VertexArrayObject::~VertexArrayObject() { if (!gl::DeleteVertexArrays) return; if (vao) { - MBGL_CHECK_ERROR(gl::DeleteVertexArrays(1, &vao)); + Environment::Get().abandonVAO(vao); } } diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 0d6da5781e..83739ab46a 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -40,7 +40,9 @@ std::pair<std::vector<Tile::ID>, std::vector<uint32_t>> AnnotationManager::addPo uint16_t extent = 4096; - std::vector<uint32_t> annotationIDs(points.size()); + std::vector<uint32_t> annotationIDs; + annotationIDs.reserve(points.size()); + std::vector<Tile::ID> affectedTiles; for (uint32_t i = 0; i < points.size(); ++i) { diff --git a/src/mbgl/map/environment.cpp b/src/mbgl/map/environment.cpp index 1aea5aa0c9..469790501c 100644 --- a/src/mbgl/map/environment.cpp +++ b/src/mbgl/map/environment.cpp @@ -1,5 +1,6 @@ #include <mbgl/map/environment.hpp> #include <mbgl/storage/file_source.hpp> +#include <mbgl/platform/gl.hpp> #include <uv.h> @@ -76,12 +77,12 @@ ThreadInfoStore threadInfoStore; } // namespace -Environment::Scope::Scope(Environment& env, ThreadType type, const std::string& name) +EnvironmentScope::EnvironmentScope(Environment& env, ThreadType type, const std::string& name) : id(std::this_thread::get_id()) { threadInfoStore.registerThread(&env, type, name); } -Environment::Scope::~Scope() { +EnvironmentScope::~EnvironmentScope() { assert(id == std::this_thread::get_id()); threadInfoStore.unregisterThread(); } @@ -90,6 +91,12 @@ Environment::Environment(FileSource& fs) : id(makeEnvironmentID()), fileSource(fs), loop(uv_loop_new()) { } +Environment::~Environment() { + assert(abandonedVAOs.empty()); + assert(abandonedTextures.empty()); + assert(abandonedBuffers.empty()); +} + Environment& Environment::Get() { Environment* env = threadInfoStore.getThreadInfo().env; assert(env); @@ -129,6 +136,50 @@ void Environment::cancelRequest(Request* req) { fileSource.cancel(req); } +// ############################################################################################# + +#pragma mark - OpenGL cleanup + +void Environment::abandonVAO(uint32_t vao) { + assert(currentlyOn(ThreadType::Map)); + abandonedVAOs.emplace_back(vao); +} + +void Environment::abandonBuffer(uint32_t buffer) { + assert(currentlyOn(ThreadType::Map)); + abandonedBuffers.emplace_back(buffer); +} + +void Environment::abandonTexture(uint32_t texture) { + assert(currentlyOn(ThreadType::Map)); + abandonedTextures.emplace_back(texture); +} + +// Actually remove the objects we marked as abandoned with the above methods. +void Environment::performCleanup() { + assert(currentlyOn(ThreadType::Map)); + + if (!abandonedVAOs.empty()) { + MBGL_CHECK_ERROR(gl::DeleteVertexArrays(static_cast<GLsizei>(abandonedVAOs.size()), + abandonedVAOs.data())); + abandonedVAOs.clear(); + } + + if (!abandonedTextures.empty()) { + MBGL_CHECK_ERROR(glDeleteTextures(static_cast<GLsizei>(abandonedTextures.size()), + abandonedTextures.data())); + abandonedTextures.clear(); + } + + if (!abandonedBuffers.empty()) { + MBGL_CHECK_ERROR(glDeleteBuffers(static_cast<GLsizei>(abandonedBuffers.size()), + abandonedBuffers.data())); + abandonedBuffers.clear(); + } +} + +// ############################################################################################# + void Environment::terminate() { fileSource.abort(*this); } diff --git a/src/mbgl/map/environment.hpp b/src/mbgl/map/environment.hpp index b631abf13d..1cd33a5e6d 100644 --- a/src/mbgl/map/environment.hpp +++ b/src/mbgl/map/environment.hpp @@ -6,6 +6,7 @@ #include <thread> #include <functional> +#include <vector> typedef struct uv_loop_s uv_loop_t; @@ -25,16 +26,8 @@ enum class ThreadType : uint8_t { class Environment final : private util::noncopyable { public: - class Scope final { - public: - Scope(Environment&, ThreadType, const std::string& name); - ~Scope(); - - private: - std::thread::id id; - }; - Environment(FileSource&); + ~Environment(); static Environment& Get(); static bool inScope(); @@ -42,10 +35,27 @@ public: static std::string threadName(); unsigned getID() const; + + // ############################################################################################# + + // File request APIs void requestAsync(const Resource&, std::function<void(const Response&)>); Request* request(const Resource&, std::function<void(const Response&)>); void cancelRequest(Request*); + // ############################################################################################# + + // Mark OpenGL objects for deletion + void abandonVAO(uint32_t vao); + void abandonBuffer(uint32_t buffer); + void abandonTexture(uint32_t texture); + + // Actually remove the objects we marked as abandoned with the above methods. + // Only call this while the OpenGL context is exclusive to this thread. + void performCleanup(); + + // ############################################################################################# + // Request to terminate the environment. void terminate(); @@ -53,10 +63,24 @@ private: unsigned id; FileSource& fileSource; + // Stores OpenGL objects that we marked for deletion + std::vector<uint32_t> abandonedVAOs; + std::vector<uint32_t> abandonedBuffers; + std::vector<uint32_t> abandonedTextures; + public: uv_loop_t* const loop; }; +class EnvironmentScope final { +public: + EnvironmentScope(Environment&, ThreadType, const std::string& name); + ~EnvironmentScope(); + +private: + std::thread::id id; +}; + } #endif diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 22e6cbcebb..bc53d0d0fa 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -1,6 +1,7 @@ #include <mbgl/map/map.hpp> #include <mbgl/map/environment.hpp> #include <mbgl/map/view.hpp> +#include <mbgl/map/map_data.hpp> #include <mbgl/platform/platform.hpp> #include <mbgl/map/source.hpp> #include <mbgl/renderer/painter.hpp> @@ -61,6 +62,7 @@ using namespace mbgl; Map::Map(View& view_, FileSource& fileSource_) : env(util::make_unique<Environment>(fileSource_)), + scope(util::make_unique<EnvironmentScope>(*env, ThreadType::Main, "Main")), view(view_), transform(view_), fileSource(fileSource_), @@ -70,7 +72,8 @@ Map::Map(View& view_, FileSource& fileSource_) lineAtlas(util::make_unique<LineAtlas>(512, 512)), texturePool(std::make_shared<TexturePool>()), painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas, *lineAtlas)), - annotationManager(util::make_unique<AnnotationManager>()) + annotationManager(util::make_unique<AnnotationManager>()), + data(util::make_unique<MapData>()) { view.initialize(this); } @@ -80,15 +83,28 @@ Map::~Map() { stop(); } + // Extend the scope to include both Main and Map thread types to ease cleanup. + scope.reset(); + scope = util::make_unique<EnvironmentScope>( + *env, static_cast<ThreadType>(static_cast<uint8_t>(ThreadType::Main) | + static_cast<uint8_t>(ThreadType::Map)), + "MapandMain"); + // Explicitly reset all pointers. activeSources.clear(); sprite.reset(); glyphStore.reset(); style.reset(); - texturePool.reset(); workers.reset(); + painter.reset(); + annotationManager.reset(); + lineAtlas.reset(); + spriteAtlas.reset(); + glyphAtlas.reset(); uv_run(env->loop, UV_RUN_DEFAULT); + + env->performCleanup(); } uv::worker &Map::getWorker() { @@ -125,11 +141,7 @@ void Map::start(bool startPaused) { }); asyncUpdate = util::make_unique<uv::async>(env->loop, [this] { - assert(Environment::currentlyOn(ThreadType::Map)); - - if (state.hasSize()) { - prepare(); - } + update(); }); asyncRender = util::make_unique<uv::async>(env->loop, [this] { @@ -162,6 +174,8 @@ void Map::start(bool startPaused) { isStopped = true; view.notify(); }); + + triggerUpdate(); } void Map::stop(std::function<void ()> callback) { @@ -233,13 +247,15 @@ void Map::run() { threadName += "andMain"; } - Environment::Scope scope(*env, threadType, threadName); + EnvironmentScope mapScope(*env, threadType, threadName); if (mode == Mode::Continuous) { checkForPause(); } - if (mode == Mode::Static && !style && styleURL.empty()) { + auto styleInfo = data->getStyleInfo(); + + if (mode == Mode::Static && !style && (styleInfo.url.empty() && styleInfo.json.empty())) { throw util::Exception("Style is not set"); } @@ -284,7 +300,9 @@ void Map::renderSync() { rendered = false; } -void Map::triggerUpdate() { +void Map::triggerUpdate(const Update u) { + updated |= static_cast<UpdateType>(u); + if (mode == Mode::Static) { prepare(); } else if (asyncUpdate) { @@ -332,36 +350,27 @@ void Map::setup() { } void Map::setStyleURL(const std::string &url) { - // TODO: Make threadsafe. - - styleURL = url; - if (mode == Mode::Continuous) { - stop(); - start(); - } -} + assert(Environment::currentlyOn(ThreadType::Main)); -void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) { - // TODO: Make threadsafe. - styleJSON.swap(newStyleJSON); - sprite.reset(); - if (!style) { - style = std::make_shared<Style>(); + const size_t pos = url.rfind('/'); + std::string base = ""; + if (pos != std::string::npos) { + base = url.substr(0, pos + 1); } - style->base = base; - style->loadJSON((const uint8_t *)styleJSON.c_str()); - style->cascadeClasses(classes); - style->setDefaultTransitionDuration(defaultTransitionDuration); + data->setStyleInfo({ url, base, "" }); + triggerUpdate(Update::StyleInfo); +} - const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken()); - glyphStore->setURL(glyphURL); +void Map::setStyleJSON(const std::string& json, const std::string& base) { + assert(Environment::currentlyOn(ThreadType::Main)); - triggerUpdate(); + data->setStyleInfo({ "", base, json }); + triggerUpdate(Update::StyleInfo); } std::string Map::getStyleJSON() const { - return styleJSON; + return data->getStyleInfo().json; } util::ptr<Sprite> Map::getSprite() { @@ -522,11 +531,11 @@ void Map::stopRotating() { #pragma mark - Access Token void Map::setAccessToken(const std::string &token) { - accessToken = token; + data->setAccessToken(token); } -const std::string &Map::getAccessToken() const { - return accessToken; +std::string Map::getAccessToken() const { + return data->getAccessToken(); } #pragma mark - Annotations @@ -583,69 +592,58 @@ void Map::updateAnnotationTiles(std::vector<Tile::ID>& ids) { #pragma mark - Toggles void Map::setDebug(bool value) { - debug = value; - assert(painter); - painter->setDebug(debug); - triggerUpdate(); + data->setDebug(value); + triggerUpdate(Update::Debug); } void Map::toggleDebug() { - setDebug(!debug); + data->toggleDebug(); + triggerUpdate(Update::Debug); } bool Map::getDebug() const { - return debug; + return data->getDebug(); +} + +std::chrono::steady_clock::time_point Map::getTime() const { + return data->getAnimationTime(); } void Map::addClass(const std::string& klass) { - if (hasClass(klass)) return; - classes.push_back(klass); - if (style) { - style->cascadeClasses(classes); - if (style->hasTransitions()) { - triggerUpdate(); - } + if (data->addClass(klass)) { + triggerUpdate(Update::Classes); } } void Map::removeClass(const std::string& klass) { - if (!hasClass(klass)) return; - classes.erase(std::remove(classes.begin(), classes.end(), klass), classes.end()); - if (style) { - style->cascadeClasses(classes); - if (style->hasTransitions()) { - triggerUpdate(); - } + if (data->removeClass(klass)) { + triggerUpdate(Update::Classes); } } -void Map::setClasses(const std::vector<std::string>& classes_) { - classes = classes_; - if (style) { - style->cascadeClasses(classes); - if (style->hasTransitions()) { - triggerUpdate(); - } - } +void Map::setClasses(const std::vector<std::string>& classes) { + data->setClasses(classes); + triggerUpdate(Update::Classes); } bool Map::hasClass(const std::string& klass) const { - return std::find(classes.begin(), classes.end(), klass) != classes.end(); + return data->hasClass(klass); } std::vector<std::string> Map::getClasses() const { - return classes; + return data->getClasses(); } void Map::setDefaultTransitionDuration(std::chrono::steady_clock::duration duration) { - defaultTransitionDuration = duration; - if (style) { - style->setDefaultTransitionDuration(duration); - } + assert(Environment::currentlyOn(ThreadType::Main)); + + data->setDefaultTransitionDuration(duration); + triggerUpdate(Update::DefaultTransitionDuration); } std::chrono::steady_clock::duration Map::getDefaultTransitionDuration() { - return defaultTransitionDuration; + assert(Environment::currentlyOn(ThreadType::Main)); + return data->getDefaultTransitionDuration(); } void Map::updateSources() { @@ -657,7 +655,9 @@ void Map::updateSources() { } // Then, reenable all of those that we actually use when drawing this layer. - updateSources(style->layers); + if (style) { + updateSources(style->layers); + } // Then, construct or destroy the actual source object, depending on enabled state. for (const auto& source : activeSources) { @@ -702,45 +702,94 @@ void Map::updateTiles() { } } -void Map::prepare() { +void Map::update() { + assert(Environment::currentlyOn(ThreadType::Map)); + + if (state.hasSize()) { + prepare(); + } +} + +void Map::reloadStyle() { assert(Environment::currentlyOn(ThreadType::Map)); - if (!style) { - style = std::make_shared<Style>(); + style = std::make_shared<Style>(); - env->request({ Resource::Kind::JSON, styleURL}, [&](const Response &res) { + const auto styleInfo = data->getStyleInfo(); + + if (!styleInfo.url.empty()) { + // We have a style URL + env->request({ Resource::Kind::JSON, styleInfo.url }, [&](const Response &res) { if (res.status == Response::Successful) { - // Calculate the base - const size_t pos = styleURL.rfind('/'); - std::string base = ""; - if (pos != std::string::npos) { - base = styleURL.substr(0, pos + 1); - } - - setStyleJSON(res.data, base); + loadStyleJSON(res.data, styleInfo.base); } else { Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str()); } }); + } else { + // We got JSON data directly. + loadStyleJSON(styleInfo.json, styleInfo.base); + } +} + +void Map::loadStyleJSON(const std::string& json, const std::string& base) { + assert(Environment::currentlyOn(ThreadType::Map)); + + sprite.reset(); + style = std::make_shared<Style>(); + style->base = base; + style->loadJSON((const uint8_t *)json.c_str()); + style->cascadeClasses(data->getClasses()); + style->setDefaultTransitionDuration(data->getDefaultTransitionDuration()); + + const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken()); + glyphStore->setURL(glyphURL); + + triggerUpdate(); +} + +void Map::prepare() { + assert(Environment::currentlyOn(ThreadType::Map)); + + const auto u = updated.exchange(static_cast<UpdateType>(Update::Nothing)); + if (u & static_cast<UpdateType>(Update::StyleInfo)) { + reloadStyle(); + } + if (u & static_cast<UpdateType>(Update::Debug)) { + assert(painter); + painter->setDebug(data->getDebug()); + } + if (u & static_cast<UpdateType>(Update::DefaultTransitionDuration)) { + if (style) { + style->setDefaultTransitionDuration(data->getDefaultTransitionDuration()); + } + } + if (u & static_cast<UpdateType>(Update::Classes)) { + if (style) { + style->cascadeClasses(data->getClasses()); + } } // Update transform transitions. - animationTime = std::chrono::steady_clock::now(); + + const auto animationTime = std::chrono::steady_clock::now(); + data->setAnimationTime(animationTime); if (transform.needsTransition()) { transform.updateTransitions(animationTime); } state = transform.currentState(); - animationTime = std::chrono::steady_clock::now(); - updateSources(); - style->updateProperties(state.getNormalizedZoom(), animationTime); + if (style) { + updateSources(); + style->updateProperties(state.getNormalizedZoom(), animationTime); - // Allow the sprite atlas to potentially pull new sprite images if needed. - spriteAtlas->resize(state.getPixelRatio()); - spriteAtlas->setSprite(getSprite()); + // Allow the sprite atlas to potentially pull new sprite images if needed. + spriteAtlas->resize(state.getPixelRatio()); + spriteAtlas->setSprite(getSprite()); - updateTiles(); + updateTiles(); + } if (mode == Mode::Continuous) { view.invalidate(); @@ -749,9 +798,13 @@ void Map::prepare() { void Map::render() { assert(Environment::currentlyOn(ThreadType::Map)); + + // Cleanup OpenGL objects that we abandoned since the last render call. + env->performCleanup(); + assert(painter); painter->render(*style, activeSources, - state, animationTime); + state, data->getAnimationTime()); // Schedule another rerender when we definitely need a next frame. if (transform.needsTransition() || style->hasTransitions()) { triggerUpdate(); diff --git a/src/mbgl/map/map_data.cpp b/src/mbgl/map/map_data.cpp new file mode 100644 index 0000000000..23bc094990 --- /dev/null +++ b/src/mbgl/map/map_data.cpp @@ -0,0 +1,44 @@ +#include "map_data.hpp" + +#include <algorithm> + +namespace mbgl { + +// Adds the class if it's not yet set. Returns true when it added the class, and false when it +// was already present. +bool MapData::addClass(const std::string& klass) { + Lock lock(mtx); + if (std::find(classes.begin(), classes.end(), klass) != classes.end()) return false; + classes.push_back(klass); + return true; +} + +// Removes the class if it's present. Returns true when it remvoed the class, and false when it +// was not present. +bool MapData::removeClass(const std::string& klass) { + Lock lock(mtx); + const auto it = std::find(classes.begin(), classes.end(), klass); + if (it != classes.end()) { + classes.erase(it); + return true; + } + return false; +} + +// Returns true when class is present in the list of currently set classes. +bool MapData::hasClass(const std::string& klass) const { + Lock lock(mtx); + return std::find(classes.begin(), classes.end(), klass) != classes.end(); +} + +void MapData::setClasses(const std::vector<std::string>& klasses) { + Lock lock(mtx); + classes = klasses; +} + +std::vector<std::string> MapData::getClasses() const { + Lock lock(mtx); + return classes; +} + +}
\ No newline at end of file diff --git a/src/mbgl/map/map_data.hpp b/src/mbgl/map/map_data.hpp new file mode 100644 index 0000000000..c0d57134d9 --- /dev/null +++ b/src/mbgl/map/map_data.hpp @@ -0,0 +1,102 @@ +#ifndef MBGL_MAP_MAP_DATA +#define MBGL_MAP_MAP_DATA + +#include <string> +#include <mutex> +#include <atomic> +#include <chrono> +#include <vector> + +namespace mbgl { + +struct StyleInfo { + std::string url; + std::string base; + std::string json; +}; + +class MapData { + using Lock = std::lock_guard<std::mutex>; + +public: + inline MapData() { + setAnimationTime(std::chrono::steady_clock::time_point::min()); + setDefaultTransitionDuration(std::chrono::steady_clock::duration::zero()); + } + + inline StyleInfo getStyleInfo() const { + Lock lock(mtx); + return styleInfo; + } + inline void setStyleInfo(StyleInfo&& info) { + Lock lock(mtx); + styleInfo = info; + } + + inline std::string getAccessToken() const { + Lock lock(mtx); + return accessToken; + } + inline void setAccessToken(const std::string &token) { + Lock lock(mtx); + accessToken = token; + } + + // Adds the class if it's not yet set. Returns true when it added the class, and false when it + // was already present. + bool addClass(const std::string& klass); + + // Removes the class if it's present. Returns true when it remvoed the class, and false when it + // was not present. + bool removeClass(const std::string& klass); + + // Returns true when class is present in the list of currently set classes. + bool hasClass(const std::string& klass) const; + + // Changes the list of currently set classes to the new list. + void setClasses(const std::vector<std::string>& klasses); + + // Returns a list of all currently set classes. + std::vector<std::string> getClasses() const; + + + inline bool getDebug() const { + return debug; + } + inline bool toggleDebug() { + return debug ^= 1u; + } + inline void setDebug(bool value) { + debug = value; + } + + inline std::chrono::steady_clock::time_point getAnimationTime() const { + // We're casting the time_point to and from a duration because libstdc++ + // has a bug that doesn't allow time_points to be atomic. + return std::chrono::steady_clock::time_point(animationTime); + } + inline void setAnimationTime(std::chrono::steady_clock::time_point timePoint) { + animationTime = timePoint.time_since_epoch(); + }; + + inline std::chrono::steady_clock::duration getDefaultTransitionDuration() const { + return defaultTransitionDuration; + } + inline void setDefaultTransitionDuration(std::chrono::steady_clock::duration duration) { + defaultTransitionDuration = duration; + }; + +private: + mutable std::mutex mtx; + + StyleInfo styleInfo; + std::string accessToken; + std::vector<std::string> classes; + std::atomic<uint8_t> debug { false }; + std::atomic<std::chrono::steady_clock::time_point::duration> animationTime; + std::atomic<std::chrono::steady_clock::duration> defaultTransitionDuration; +}; + +} + +#endif diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index aed182671b..1832bd8f0c 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -97,7 +97,7 @@ void TileData::reparse(uv::worker& worker, std::function<void()> callback) new uv::work<util::ptr<TileData>>( worker, [this](util::ptr<TileData>& tile) { - Environment::Scope scope(env, ThreadType::TileWorker, "TileWorker_" + tile->name); + EnvironmentScope scope(env, ThreadType::TileWorker, "TileWorker_" + tile->name); tile->parse(); }, [callback](util::ptr<TileData>&) { diff --git a/src/mbgl/util/texture_pool.cpp b/src/mbgl/util/texture_pool.cpp index 33bca01c05..845796f5df 100644 --- a/src/mbgl/util/texture_pool.cpp +++ b/src/mbgl/util/texture_pool.cpp @@ -1,4 +1,5 @@ #include <mbgl/util/texture_pool.hpp> +#include <mbgl/map/environment.hpp> #include <vector> @@ -42,17 +43,9 @@ void TexturePool::removeTextureID(GLuint texture_id) { } void TexturePool::clearTextureIDs() { - std::vector<GLuint> ids_to_remove; - ids_to_remove.reserve(texture_ids.size()); - - for (std::set<GLuint>::iterator id_iterator = texture_ids.begin(); - id_iterator != texture_ids.end(); ++id_iterator) { - ids_to_remove.push_back(*id_iterator); - } - - if (!ids_to_remove.empty()) { - MBGL_CHECK_ERROR(glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0])); + auto& env = Environment::Get(); + for (auto texture : texture_ids) { + env.abandonTexture(texture); } - texture_ids.clear(); } diff --git a/styles b/styles -Subproject 25b1b7dff37a18151e3286144bc8013b432a886 +Subproject 26ba1edacdc4ce2295ca1aaeef28ba6c4c37dbb |