Merge branch 'master' of github.com:mapbox/mapbox-gl-native into 1033-metrics
Conflicts: gyp/platform-ios.gypi platform/ios/MGLMapView.mm styles
This commit is contained in:
@@ -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',
|
||||
|
||||
28
include/mbgl/ios/MGLAnnotation.h
Normal file
28
include/mbgl/ios/MGLAnnotation.h
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
7
include/mbgl/ios/MapboxGL.h
Normal file
7
include/mbgl/ios/MapboxGL.h
Normal file
@@ -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"
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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++' }
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 \
|
||||
.
|
||||
../../include/mbgl/ios
|
||||
|
||||
@@ -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-*
|
||||
rm -rfv ~/Library/Developer/Shared/Documentation/DocSets/com.mapbox.Mapbox-GL-*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
|
||||
styleURL = url;
|
||||
if (mode == Mode::Continuous) {
|
||||
stop();
|
||||
start();
|
||||
const size_t pos = url.rfind('/');
|
||||
std::string base = "";
|
||||
if (pos != std::string::npos) {
|
||||
base = url.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
data->setStyleInfo({ url, base, "" });
|
||||
triggerUpdate(Update::StyleInfo);
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
void Map::setStyleJSON(const std::string& json, const std::string& base) {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
|
||||
style->base = base;
|
||||
style->loadJSON((const uint8_t *)styleJSON.c_str());
|
||||
style->cascadeClasses(classes);
|
||||
style->setDefaultTransitionDuration(defaultTransitionDuration);
|
||||
|
||||
const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken());
|
||||
glyphStore->setURL(glyphURL);
|
||||
|
||||
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 (!style) {
|
||||
style = std::make_shared<Style>();
|
||||
if (state.hasSize()) {
|
||||
prepare();
|
||||
}
|
||||
}
|
||||
|
||||
env->request({ Resource::Kind::JSON, styleURL}, [&](const Response &res) {
|
||||
void Map::reloadStyle() {
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
|
||||
style = std::make_shared<Style>();
|
||||
|
||||
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();
|
||||
|
||||
44
src/mbgl/map/map_data.cpp
Normal file
44
src/mbgl/map/map_data.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
102
src/mbgl/map/map_data.hpp
Normal file
102
src/mbgl/map/map_data.hpp
Normal file
@@ -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
|
||||
@@ -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>&) {
|
||||
|
||||
@@ -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);
|
||||
auto& env = Environment::Get();
|
||||
for (auto texture : texture_ids) {
|
||||
env.abandonTexture(texture);
|
||||
}
|
||||
|
||||
if (!ids_to_remove.empty()) {
|
||||
MBGL_CHECK_ERROR(glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0]));
|
||||
}
|
||||
|
||||
texture_ids.clear();
|
||||
}
|
||||
|
||||
2
styles
2
styles
Submodule styles updated: 25b1b7dff3...26ba1edacd
Reference in New Issue
Block a user