Merge pull request #1076 from mapbox/annotations-performance

annotations perf improvements & iOS app testing
This commit is contained in:
Justin R. Miller
2015-03-23 21:21:58 -07:00
11 changed files with 136520 additions and 36 deletions

View File

@@ -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;
};

View File

@@ -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
View 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
View 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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',
],

View File

@@ -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];
}

View File

@@ -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;

View File

@@ -21,7 +21,7 @@ private:
GeometryCollection geometries;
};
class LiveTileLayer : public GeometryTileLayer {
class LiveTileLayer : public GeometryTileLayer {
public:
LiveTileLayer();

View File

@@ -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);
}