Cocoa custom marker imagery API

This commit is contained in:
Justin R. Miller
2015-07-06 18:08:16 -07:00
committed by Konstantin Käfer
parent 1f4b5e7736
commit 5ab58deb44
7 changed files with 179 additions and 13 deletions

View File

@@ -50,6 +50,8 @@
'../platform/ios/MGLPolygon.m',
'../include/mbgl/ios/MGLShape.h',
'../platform/ios/MGLShape.m',
'../include/mbgl/ios/MGLAnnotationImage.h',
'../platform/ios/MGLAnnotationImage.m',
'../platform/ios/NSBundle+MGLAdditions.h',
'../platform/ios/NSBundle+MGLAdditions.m',
'../platform/ios/NSException+MGLAdditions.h',

View File

@@ -0,0 +1,10 @@
#import <UIKit/UIKit.h>
@interface MGLAnnotationImage : NSObject
@property (nonatomic, readonly) UIImage *image;
@property (nonatomic, readonly) NSString *reuseIdentifier;
+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier;
@end

View File

@@ -5,6 +5,7 @@
NS_ASSUME_NONNULL_BEGIN
@class MGLAnnotationImage;
@class MGLUserLocation;
@class MGLPolyline;
@class MGLPolygon;
@@ -258,6 +259,18 @@ IB_DESIGNABLE
* @param annotations The array of annotations to remove. Objects in the array must conform to the MGLAnnotation protocol. */
- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;
/** Returns a reusable annotation image object located by its identifier.
*
* For performance reasons, you should generally reuse MGLAnnotationImage objects for annotations in your map views. Dequeueing saves time and memory during performance-critical operations such as scrolling.
*
* @param identifier A string identifying the annotation image to be reused. This string is the same one you specify when initially returning the annotation image object using the mapView:imageForAnnotation: method.
* @return An annotation image object with the specified identifier, or `nil` if no such object exists in the reuse queue. */
- (MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;
#pragma mark - Managing Annotation Selections
/** @name Managing Annotation Selections */
/** The annotations that are currently selected.
*
* Assigning a new array to this property selects only the first annotation in the array. */
@@ -336,7 +349,13 @@ IB_DESIGNABLE
* @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. */
- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation;
- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation __attribute__((unavailable("Use -mapView:imageForAnnotation:.")));
/** Returns an image object to use for the marker for the specified point annotation object.
* @param mapView The map view that requested the annotation image.
* @param annotation The object representing the annotation that is about to be displayed.
* @return The image object to display for the specified annotation or `nil` if you want to display the default marker image. */
- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation;
/** Returns the alpha value to use when rendering a shape annotation. Defaults to `1.0`.
* @param mapView The map view rendering the shape annotation.

View File

@@ -1,5 +1,6 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"
#import "MGLGeometry.h"
#import "MGLMapView.h"
#import "MGLMultiPoint.h"

View File

@@ -340,6 +340,39 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
#pragma mark - MGLMapViewDelegate
- (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotation:(id <MGLAnnotation> __nonnull)annotation
{
if ([[((MGLPointAnnotation *)annotation) title] hasSuffix:@"7"])
{
NSLog(@"iOS: %p annotation title ends in '7'; using default sprite", annotation);
return nil;
}
static NSString *identifier = @"red_square";
MGLAnnotationImage *image = [mapView dequeueReusableAnnotationImageWithIdentifier:identifier];
if ( ! image)
{
NSLog(@"iOS: creating sprite for %p / %@", annotation, identifier);
CGRect rect = CGRectMake(0, 0, 20, 20);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [[[UIColor redColor] colorWithAlphaComponent:0.25] CGColor]);
CGContextFillEllipseInRect(ctx, rect);
image = [MGLAnnotationImage annotationImageWithImage:UIGraphicsGetImageFromCurrentImageContext() reuseIdentifier:identifier];
UIGraphicsEndImageContext();
}
else
{
NSLog(@"iOS: reusing sprite for %p / %@", annotation, identifier);
}
return image;
}
- (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id <MGLAnnotation>)annotation
{
return YES;

View File

@@ -0,0 +1,30 @@
#import "MGLAnnotationImage.h"
@interface MGLAnnotationImage ()
@property (nonatomic) UIImage *image;
@property (nonatomic) NSString *reuseIdentifier;
@end
@implementation MGLAnnotationImage
+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
{
return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier];
}
- (instancetype)initWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super init];
if (self)
{
_image = image;
_reuseIdentifier = [reuseIdentifier copy];
}
return self;
}
@end

View File

@@ -10,6 +10,7 @@
#include <mbgl/mbgl.hpp>
#include <mbgl/annotation/point_annotation.hpp>
#include <mbgl/annotation/shape_annotation.hpp>
#include <mbgl/annotation/sprite_image.hpp>
#include <mbgl/platform/platform.hpp>
#include <mbgl/platform/darwin/reachability.h>
#include <mbgl/storage/default_file_source.hpp>
@@ -33,6 +34,7 @@
#import "SMCalloutView.h"
#import <algorithm>
#import <cstdlib>
class MBGLView;
@@ -46,6 +48,7 @@ const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
const CGFloat MGLMinimumZoom = 3;
NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey";
NSString *const MGLAnnotationSymbolKey = @"MGLAnnotationSymbolKey";
static NSURL *MGLURLForBundledStyleNamed(NSString *styleName)
{
@@ -81,6 +84,7 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
@property (nonatomic) UIRotationGestureRecognizer *rotate;
@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
@property (nonatomic) NSMapTable *annotationIDsByAnnotation;
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImages;
@property (nonatomic) std::vector<uint32_t> annotationsNearbyLastTap;
@property (nonatomic, weak) id <MGLAnnotation> selectedAnnotation;
@property (nonatomic) SMCalloutView *selectedAnnotationCalloutView;
@@ -275,6 +279,9 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
// setup annotations
//
_annotationIDsByAnnotation = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
_annotationImages = [NSMutableDictionary new];
std::string defaultSymbolName([MGLDefaultStyleMarkerSymbolName UTF8String]);
_mbglMap->setDefaultPointAnnotationSymbol(defaultSymbolName);
@@ -1772,12 +1779,14 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
std::vector<mbgl::PointAnnotation> points;
std::vector<mbgl::ShapeAnnotation> shapes;
BOOL delegateImplementsSymbolLookup = [self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)];
BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
BOOL delegateImplementsAlphaForShape = [self.delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
BOOL delegateImplementsStrokeColorForShape = [self.delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
BOOL delegateImplementsFillColorForPolygon = [self.delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
BOOL delegateImplementsLineWidthForPolyline = [self.delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
const std::string spritePrefix = "com.mapbox.sprites.";
for (id <MGLAnnotation> annotation in annotations)
{
assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]);
@@ -1856,11 +1865,69 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
}
else
{
MGLAnnotationImage *annotationImage = nil;
NSString *symbolName = nil;
if (delegateImplementsSymbolLookup)
if (delegateImplementsImageForPoint)
{
symbolName = [self.delegate mapView:self symbolNameForAnnotation:annotation];
annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
if (annotationImage)
{
const std::string cSymbolName = spritePrefix + std::string(annotationImage.reuseIdentifier.UTF8String);
symbolName = [NSString stringWithUTF8String:cSymbolName.c_str()];
if ( ! [self.annotationImages objectForKey:annotationImage.reuseIdentifier])
{
// store image & symbol name
[self.annotationImages setObject:annotationImage forKey:annotationImage.reuseIdentifier];
// manually draw random-colored sprite for now
float pixelRatio = [[UIScreen mainScreen] scale];
float width = 20;
float height = 20;
const int r = 255 * (double(std::rand()) / RAND_MAX);
const int g = 255 * (double(std::rand()) / RAND_MAX);
const int b = 255 * (double(std::rand()) / RAND_MAX);
const int w = std::ceil(pixelRatio * width);
const int h = std::ceil(pixelRatio * height);
std::string pixels(w * h * 4, '\x00');
auto data = reinterpret_cast<uint32_t*>(const_cast<char*>(pixels.data()));
const int dist = (w / 2) * (w / 2);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
const int dx = x - w / 2;
const int dy = y - h / 2;
const int diff = dist - (dx * dx + dy * dy);
if (diff > 0) {
const int a = std::min(0xFF, diff) * 0xFF / dist;
// Premultiply the rgb values with alpha
data[w * y + x] =
(a << 24) | ((a * r / 0xFF) << 16) | ((a * g / 0xFF) << 8) | (a * b / 0xFF);
}
}
}
// add sprite
auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(
width,
height,
pixelRatio,
std::move(pixels));
// sprite upload
_mbglMap->setSprite(cSymbolName, cSpriteImage);
NSLog(@"GL: uploaded sprite with name '%@'", [NSString stringWithUTF8String:cSymbolName.c_str()]);
}
}
else
{
NSLog(@"GL: using default symbol");
}
}
points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : "");
@@ -1873,8 +1940,10 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
for (size_t i = 0; i < pointAnnotationIDs.size(); ++i)
{
[self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(pointAnnotationIDs[i]) }
forKey:annotations[i]];
[self.annotationIDsByAnnotation setObject:@{
MGLAnnotationIDKey : @(pointAnnotationIDs[i]),
MGLAnnotationSymbolKey : [NSString stringWithUTF8String:points[i].icon.c_str()]
} forKey:annotations[i]];
}
}
@@ -1925,6 +1994,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
_mbglMap->removeAnnotations(annotationIDsToRemove);
}
- (MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier
{
return [self.annotationImages objectForKey:identifier];
}
- (NS_ARRAY_OF(id <MGLAnnotation>) *)selectedAnnotations
{
return (self.selectedAnnotation ? @[ self.selectedAnnotation ] : @[]);
@@ -1977,16 +2051,13 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
else
{
// determine symbol in use for point
NSString *symbol = MGLDefaultStyleMarkerSymbolName;
if ([self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)])
{
symbol = [self.delegate mapView:self symbolNameForAnnotation:annotation];
}
std::string symbolName([symbol UTF8String]);
NSString *customSymbol = [[self.annotationIDsByAnnotation objectForKey:annotation] objectForKey:MGLAnnotationSymbolKey];
NSString *symbolName = [customSymbol length] ? customSymbol : MGLDefaultStyleMarkerSymbolName;
std::string cSymbolName([symbolName UTF8String]);
// determine anchor point based on symbol
CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
double y = _mbglMap->getTopOffsetPixelsForAnnotationSymbol(symbolName);
double y = _mbglMap->getTopOffsetPixelsForAnnotationSymbol(cSymbolName);
calloutBounds = CGRectMake(calloutAnchorPoint.x - 1, calloutAnchorPoint.y + y, 0, 0);
}