Merge pull request #1076 from mapbox/annotations-performance
annotations perf improvements & iOS app testing
This commit is contained in:
@@ -9,9 +9,10 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -29,7 +30,7 @@ public:
|
||||
void setDefaultPointAnnotationSymbol(const std::string& symbol);
|
||||
std::pair<std::vector<Tile::ID>, AnnotationIDs> addPointAnnotations(
|
||||
const std::vector<LatLng>&, const std::vector<std::string>& symbols, const Map&);
|
||||
std::vector<Tile::ID> removeAnnotations(const AnnotationIDs&);
|
||||
std::vector<Tile::ID> removeAnnotations(const AnnotationIDs&, const Map&);
|
||||
AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const Map&) const;
|
||||
LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const;
|
||||
|
||||
@@ -44,8 +45,8 @@ private:
|
||||
private:
|
||||
mutable std::mutex mtx;
|
||||
std::string defaultPointAnnotationSymbol;
|
||||
std::map<uint32_t, std::unique_ptr<Annotation>> annotations;
|
||||
std::map<Tile::ID, std::pair<AnnotationIDs, std::unique_ptr<LiveTile>>> annotationTiles;
|
||||
std::unordered_map<uint32_t, std::unique_ptr<Annotation>> annotations;
|
||||
std::unordered_map<Tile::ID, std::pair<std::unordered_set<uint32_t>, std::unique_ptr<LiveTile>>, Tile::ID::Hash> tiles;
|
||||
uint32_t nextID_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <forward_list>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -44,6 +45,12 @@ public:
|
||||
return ((std::pow(2, z) * y + x) * 32) + z;
|
||||
}
|
||||
|
||||
struct Hash {
|
||||
std::size_t operator()(ID const& i) const {
|
||||
return std::hash<uint64_t>()(i.to_uint64());
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const ID& rhs) const {
|
||||
return w == rhs.w && z == rhs.z && x == rhs.x && y == rhs.y;
|
||||
}
|
||||
|
||||
12
ios/app/MBXAnnotation.h
Normal file
12
ios/app/MBXAnnotation.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
#import <mbgl/ios/MGLAnnotation.h>
|
||||
|
||||
@interface MBXAnnotation : NSObject <MGLAnnotation>
|
||||
|
||||
+ (instancetype)annotationWithLocation:(CLLocationCoordinate2D)coordinate title:(NSString *)title subtitle:(NSString *)subtitle;
|
||||
|
||||
- (instancetype)initWithLocation:(CLLocationCoordinate2D)coordinate title:(NSString *)title subtitle:(NSString *)subtitle;
|
||||
|
||||
@end
|
||||
30
ios/app/MBXAnnotation.m
Normal file
30
ios/app/MBXAnnotation.m
Normal file
@@ -0,0 +1,30 @@
|
||||
#import "MBXAnnotation.h"
|
||||
|
||||
@interface MBXAnnotation ()
|
||||
|
||||
@property (nonatomic) CLLocationCoordinate2D coordinate;
|
||||
@property (nonatomic) NSString *title;
|
||||
@property (nonatomic) NSString *subtitle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MBXAnnotation
|
||||
|
||||
+ (instancetype)annotationWithLocation:(CLLocationCoordinate2D)coordinate title:(NSString *)title subtitle:(NSString *)subtitle
|
||||
{
|
||||
return [[self alloc] initWithLocation:coordinate title:title subtitle:subtitle];
|
||||
}
|
||||
|
||||
- (instancetype)initWithLocation:(CLLocationCoordinate2D)coordinate title:(NSString *)title subtitle:(NSString *)subtitle
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_coordinate = coordinate;
|
||||
_title = title;
|
||||
_subtitle = subtitle;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
#import "MBXAnnotation.h"
|
||||
|
||||
static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000];
|
||||
|
||||
static NSArray *const kStyleNames = @[
|
||||
@@ -134,7 +136,14 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reset North", @"Reset Position", @"Toggle Debug", nil];
|
||||
otherButtonTitles:@"Reset North",
|
||||
@"Reset Position",
|
||||
@"Toggle Debug",
|
||||
@"Add 100 Points",
|
||||
@"Add 1,000 Points",
|
||||
@"Add 10,000 Points",
|
||||
@"Remove Points",
|
||||
nil];
|
||||
|
||||
[sheet showFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
|
||||
}
|
||||
@@ -153,6 +162,67 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
{
|
||||
[self.mapView toggleDebug];
|
||||
}
|
||||
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 3)
|
||||
{
|
||||
[self parseFeaturesAddingCount:100];
|
||||
}
|
||||
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 4)
|
||||
{
|
||||
[self parseFeaturesAddingCount:1000];
|
||||
}
|
||||
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 5)
|
||||
{
|
||||
[self parseFeaturesAddingCount:10000];
|
||||
}
|
||||
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 6)
|
||||
{
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parseFeaturesAddingCount:(NSUInteger)featuresCount
|
||||
{
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
|
||||
{
|
||||
NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"features" ofType:@"geojson"]];
|
||||
|
||||
id features = [NSJSONSerialization JSONObjectWithData:featuresData
|
||||
options:0
|
||||
error:nil];
|
||||
|
||||
if ([features isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
NSMutableArray *annotations = [NSMutableArray array];
|
||||
|
||||
for (NSDictionary *feature in features[@"features"])
|
||||
{
|
||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([feature[@"geometry"][@"coordinates"][1] doubleValue],
|
||||
[feature[@"geometry"][@"coordinates"][0] doubleValue]);
|
||||
NSString *title = feature[@"properties"][@"NAME"];
|
||||
|
||||
MBXAnnotation *annotation = [MBXAnnotation annotationWithLocation:coordinate
|
||||
title:title
|
||||
subtitle:nil];
|
||||
|
||||
[annotations addObject:annotation];
|
||||
|
||||
if (annotations.count == featuresCount) break;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self.mapView addAnnotations:annotations];
|
||||
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(38.904722, -77.016389)
|
||||
zoomLevel:10
|
||||
animated:NO];
|
||||
|
||||
[self.mapView setDirection:0];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)cycleStyles
|
||||
|
||||
136346
ios/app/features.geojson
Normal file
136346
ios/app/features.geojson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
'mac_bundle': 1,
|
||||
'mac_bundle_resources': [
|
||||
'<!@(find ./img -type f)',
|
||||
'./features.geojson'
|
||||
],
|
||||
|
||||
'dependencies': [
|
||||
@@ -27,6 +28,8 @@
|
||||
'./MBXAppDelegate.m',
|
||||
'./MBXViewController.h',
|
||||
'./MBXViewController.mm',
|
||||
'./MBXAnnotation.h',
|
||||
'./MBXAnnotation.m',
|
||||
'../../platform/darwin/settings_nsuserdefaults.mm',
|
||||
],
|
||||
|
||||
|
||||
@@ -1643,7 +1643,7 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
|
||||
{
|
||||
assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]);
|
||||
|
||||
annotationIDsToRemove.push_back([[self.annotationsStore objectForKey:annotation] unsignedIntValue]);
|
||||
annotationIDsToRemove.push_back([[[self.annotationsStore objectForKey:annotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue]);
|
||||
[self.annotationsStore removeObjectForKey:annotation];
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ private:
|
||||
private:
|
||||
const AnnotationType type = AnnotationType::Point;
|
||||
const AnnotationSegments geometry;
|
||||
std::map<Tile::ID, std::vector<std::weak_ptr<const LiveTileFeature>>> tileFeatures;
|
||||
std::unordered_map<Tile::ID, std::weak_ptr<const LiveTileFeature>, Tile::ID::Hash> tileFeatures;
|
||||
const LatLngBounds bounds;
|
||||
};
|
||||
|
||||
@@ -136,8 +136,8 @@ std::pair<std::vector<Tile::ID>, AnnotationIDs> AnnotationManager::addPointAnnot
|
||||
auto feature =
|
||||
std::make_shared<const LiveTileFeature>(FeatureType::Point, geometries, properties);
|
||||
|
||||
auto tile_it = annotationTiles.find(tileID);
|
||||
if (tile_it != annotationTiles.end()) {
|
||||
auto tile_it = tiles.find(tileID);
|
||||
if (tile_it != tiles.end()) {
|
||||
//
|
||||
// We have this tile created already. Add this feature to it.
|
||||
//
|
||||
@@ -146,7 +146,7 @@ std::pair<std::vector<Tile::ID>, AnnotationIDs> AnnotationManager::addPointAnnot
|
||||
tile_it->second.second->getMutableLayer(layerID);
|
||||
layer->addFeature(feature);
|
||||
// record annotation association with tile
|
||||
tile_it->second.first.push_back(annotationID);
|
||||
tile_it->second.first.insert(annotationID);
|
||||
} else {
|
||||
//
|
||||
// We need to create a new tile for this feature.
|
||||
@@ -155,8 +155,8 @@ std::pair<std::vector<Tile::ID>, AnnotationIDs> AnnotationManager::addPointAnnot
|
||||
util::ptr<LiveTileLayer> layer = std::make_shared<LiveTileLayer>();
|
||||
layer->addFeature(feature);
|
||||
// create tile & record annotation association
|
||||
auto tile_pos = annotationTiles.emplace(
|
||||
tileID, std::make_pair(AnnotationIDs({ annotationID }),
|
||||
auto tile_pos = tiles.emplace(
|
||||
tileID, std::make_pair(std::unordered_set<uint32_t>({ annotationID }),
|
||||
util::make_unique<LiveTile>()));
|
||||
// add point layer to tile
|
||||
tile_pos.first->second.second->addLayer(layerID, layer);
|
||||
@@ -165,7 +165,7 @@ std::pair<std::vector<Tile::ID>, AnnotationIDs> AnnotationManager::addPointAnnot
|
||||
// Record annotation association with tile and tile feature. This is used to determine stale tiles,
|
||||
// as well as to remove the feature from the tile upon annotation deletion.
|
||||
anno_it.first->second->tileFeatures.emplace(
|
||||
tileID, std::vector<std::weak_ptr<const LiveTileFeature>>({ feature }));
|
||||
tileID, std::weak_ptr<const LiveTileFeature>(feature));
|
||||
|
||||
// get ready for the next-lower zoom number
|
||||
z2 /= 2;
|
||||
@@ -180,30 +180,45 @@ std::pair<std::vector<Tile::ID>, AnnotationIDs> AnnotationManager::addPointAnnot
|
||||
return std::make_pair(affectedTiles, annotationIDs);
|
||||
}
|
||||
|
||||
std::vector<Tile::ID> AnnotationManager::removeAnnotations(const AnnotationIDs& ids) {
|
||||
std::vector<Tile::ID> AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const Map& map) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
std::vector<Tile::ID> affectedTiles;
|
||||
|
||||
for (auto& annotationID : ids) {
|
||||
const auto annotation_it = annotations.find(annotationID);
|
||||
std::vector<uint32_t> z2s;
|
||||
uint8_t zoomCount = map.getMaxZoom() + 1;
|
||||
z2s.reserve(zoomCount);
|
||||
for (uint8_t z = 0; z < zoomCount; ++z) {
|
||||
z2s.emplace_back(1<< z);
|
||||
}
|
||||
|
||||
LatLng latLng;
|
||||
vec2<double> p;
|
||||
uint32_t x, y;
|
||||
|
||||
// iterate annotation id's passed
|
||||
for (const auto& annotationID : ids) {
|
||||
// grab annotation object
|
||||
const auto& annotation_it = annotations.find(annotationID);
|
||||
if (annotation_it != annotations.end()) {
|
||||
auto& annotation = annotation_it->second;
|
||||
for (auto& tile_it : annotationTiles) {
|
||||
// clean up tile -> annotation mapping used for bounds queries
|
||||
auto& tileAnnotations = tile_it.second.first;
|
||||
util::erase_if(tileAnnotations, tileAnnotations.begin(), tileAnnotations.end(),
|
||||
[&](const uint32_t annotationID_)
|
||||
-> bool { return (annotationID_ == annotationID); });
|
||||
// remove annotation feature from tile layer
|
||||
auto features_it = annotation->tileFeatures.find(tile_it.first);
|
||||
const auto& annotation = annotation_it->second;
|
||||
// calculate annotation's affected tile for each zoom
|
||||
for (uint8_t z = 0; z < zoomCount; ++z) {
|
||||
latLng = annotation->getPoint();
|
||||
p = projectPoint(latLng);
|
||||
x = z2s[z] * p.x;
|
||||
y = z2s[z] * p.y;
|
||||
Tile::ID tid(z, x, y);
|
||||
// erase annotation from tile's list
|
||||
auto& tileAnnotations = tiles[tid].first;
|
||||
tileAnnotations.erase(annotationID);
|
||||
// remove annotation's features from tile
|
||||
const auto& features_it = annotation->tileFeatures.find(tid);
|
||||
if (features_it != annotation->tileFeatures.end()) {
|
||||
const auto layer =
|
||||
tile_it.second.second->getMutableLayer(layerID);
|
||||
const auto& features = features_it->second;
|
||||
assert(!features.empty());
|
||||
layer->removeFeature(features[0]);
|
||||
affectedTiles.push_back(tile_it.first);
|
||||
const auto& layer =
|
||||
tiles[tid].second->getMutableLayer(layerID);
|
||||
layer->removeFeature(features_it->second);
|
||||
affectedTiles.push_back(tid);
|
||||
}
|
||||
}
|
||||
annotations.erase(annotationID);
|
||||
@@ -229,7 +244,7 @@ std::vector<uint32_t> AnnotationManager::getAnnotationsInBounds(const LatLngBoun
|
||||
|
||||
std::vector<uint32_t> matchingAnnotations;
|
||||
|
||||
for (auto& tile : annotationTiles) {
|
||||
for (auto& tile : tiles) {
|
||||
Tile::ID id = tile.first;
|
||||
if (id.z == z) {
|
||||
if (id.x >= nwTile.x && id.x <= seTile.x && id.y >= nwTile.y && id.y <= seTile.y) {
|
||||
@@ -280,8 +295,8 @@ LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids
|
||||
const LiveTile* AnnotationManager::getTile(Tile::ID const& id) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
const auto tile_it = annotationTiles.find(id);
|
||||
if (tile_it != annotationTiles.end()) {
|
||||
const auto tile_it = tiles.find(id);
|
||||
if (tile_it != tiles.end()) {
|
||||
return tile_it->second.second.get();
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -21,7 +21,7 @@ private:
|
||||
GeometryCollection geometries;
|
||||
};
|
||||
|
||||
class LiveTileLayer : public GeometryTileLayer {
|
||||
class LiveTileLayer : public GeometryTileLayer {
|
||||
public:
|
||||
LiveTileLayer();
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ void Map::removeAnnotation(uint32_t annotation) {
|
||||
|
||||
void Map::removeAnnotations(const std::vector<uint32_t>& annotations) {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
auto result = annotationManager->removeAnnotations(annotations);
|
||||
auto result = annotationManager->removeAnnotations(annotations, *this);
|
||||
updateAnnotationTiles(result);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user