squash of #1655: shape annotations support for core & iOS

This commit is contained in:
Justin R. Miller
2015-06-15 18:30:39 -07:00
parent e75e538ddb
commit bd0bf294bf
43 changed files with 17079 additions and 267 deletions

4
.gitmodules vendored
View File

@@ -17,3 +17,7 @@
[submodule "platform/ios/vendor/SMCalloutView"]
path = platform/ios/vendor/SMCalloutView
url = https://github.com/nfarina/calloutview.git
[submodule "src/mbgl/util/geojsonvt"]
path = src/mbgl/util/geojsonvt
url = https://github.com/mapbox/geojson-vt-cpp

View File

@@ -39,6 +39,9 @@ else
SMCalloutView:
endif
geojsonvt:
git submodule update --init src/mbgl/util/geojsonvt
KIF:
git submodule update --init test/ios/KIF
@@ -46,11 +49,11 @@ KIF:
#### Build files ###############################################################
.PRECIOUS: Makefile/project
Makefile/project: config/$(HOST).gypi styles/styles SMCalloutView
Makefile/project: config/$(HOST).gypi styles/styles SMCalloutView geojsonvt
deps/run_gyp gyp/$(HOST).gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
.PRECIOUS: Xcode/project
Xcode/project: config/$(HOST).gypi styles/styles SMCalloutView
Xcode/project: config/$(HOST).gypi styles/styles SMCalloutView geojsonvt
deps/run_gyp gyp/$(HOST).gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f xcode
@@ -115,7 +118,7 @@ xproj: xosx-proj
#### iOS application builds ####################################################
.PRECIOUS: Xcode/ios
Xcode/ios: gyp/ios.gyp config/ios.gypi styles/styles SMCalloutView
Xcode/ios: gyp/ios.gyp config/ios.gypi styles/styles SMCalloutView geojsonvt
deps/run_gyp gyp/ios.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode
.PHONY: ios-proj ios isim ipackage
@@ -172,7 +175,7 @@ run-xlinux: xlinux
.PRECIOUS: Makefile/android-%
Makefile/android-%: CMD = deps/run_gyp android/mapboxgl-app.gyp $(CONFIG_android-$*) $(LIBS_android) --generator-output=./build/android-$* -f make-android
Makefile/android-%: config/android-%.gypi styles/styles
Makefile/android-%: config/android-%.gypi styles/styles geojsonvt
@echo $(CMD)
@$(ENV_android-$*) $(CMD)

View File

@@ -36,6 +36,18 @@
'../platform/ios/MGLUserLocationAnnotationView.m',
'../include/mbgl/ios/MGLTypes.h',
'../platform/ios/MGLTypes.m',
'../include/mbgl/ios/MGLMultiPoint.h',
'../platform/ios/MGLMultiPoint_Private.h',
'../platform/ios/MGLMultiPoint.mm',
'../include/mbgl/ios/MGLOverlay.h',
'../include/mbgl/ios/MGLPointAnnotation.h',
'../platform/ios/MGLPointAnnotation.m',
'../include/mbgl/ios/MGLPolyline.h',
'../platform/ios/MGLPolyline.m',
'../include/mbgl/ios/MGLPolygon.h',
'../platform/ios/MGLPolygon.m',
'../include/mbgl/ios/MGLShape.h',
'../platform/ios/MGLShape.m',
'../platform/ios/NSBundle+MGLAdditions.h',
'../platform/ios/NSBundle+MGLAdditions.m',
'../platform/ios/NSException+MGLAdditions.h',

View File

@@ -4,7 +4,7 @@
NS_ASSUME_NONNULL_BEGIN
/** 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.
/** 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>

View File

@@ -6,6 +6,9 @@
NS_ASSUME_NONNULL_BEGIN
@class MGLUserLocation;
@class MGLPolyline;
@class MGLPolygon;
@class MGLShape;
@protocol MGLMapViewDelegate;
@protocol MGLAnnotation;
@@ -304,6 +307,30 @@ IB_DESIGNABLE
* @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;
/** Returns the alpha value to use when rendering a shape annotation. Defaults to `1.0`.
* @param mapView The map view rendering the shape annotation.
* @param annotation The annotation being rendered.
* @return An alpha value between `0` and `1.0`. */
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
/** Returns the stroke color to use when rendering a shape annotation. Defaults to black.
* @param mapView The map view rendering the shape annotation.
* @param annotation The annotation being rendered.
* @return A color to use for the shape outline. */
- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation;
/** Returns the fill color to use when rendering a polygon annotation. Defaults to blue.
* @param mapView The map view rendering the polygon annotation.
* @param annotation The annotation being rendered.
* @return A color to use for the polygon interior. */
- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
/** Returns the line width to use when rendering a polyline annotation. Defaults to `3.0`.
* @param mapView The map view rendering the polygon annotation.
* @param annotation The annotation being rendered.
* @return A line width for the polyline. */
- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
/** Returns a Boolean value indicating whether the annotation is able to display extra information in a callout bubble.
*
* If the value returned is `YES`, a standard callout bubble is shown when the user taps a selected annotation. The callout uses the title and subtitle text from the associated annotation object. If there is no title text, though, the annotation will not show a callout. The callout also displays any custom callout views returned by the delegate for the left and right callout accessory views.
@@ -371,6 +398,8 @@ IB_DESIGNABLE
#pragma mark - Tracking the User Location
/** @name Tracking the User Location */
/** Tells the delegate that the map view will begin tracking the user’s location.
*
* This method is called when the value of the showsUserLocation property changes to `YES`.

View File

@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MGLShape.h"
/** The `MGLMultiPoint` class is an abstract superclass used to define shapes composed of multiple points. You should not create instances of this class directly. Instead, you should create instances of the `MGLPolyline` or `MGLPolygon` classes. However, you can use the method and properties of this class to access information about the specific points associated with the line or polygon. */
@interface MGLMultiPoint : MGLShape
/** The number of points associated with the shape. (read-only) */
@property (nonatomic, readonly) NSUInteger pointCount;
/** Retrieves one or more coordinates associated with the shape.
* @param coords On input, you must provide a C array of structures large enough to hold the desired number of coordinates. On output, this structure contains the requested coordinate data.
* @param range The range of points you want. The `location` field indicates the first point you are requesting, with `0` being the first point, `1` being the second point, and so on. The `length` field indicates the number of points you want. The array in _`coords`_ must be large enough to accommodate the number of requested coordinates. */
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range;
@end

View File

@@ -0,0 +1,31 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MGLAnnotation.h"
#import "MGLTypes.h"
/** The `MGLOverlay` protocol defines a specific type of annotation that represents both a point and an area on a map. Overlay objects are essentially data objects that contain the geographic data needed to represent the map area. For example, overlays can take the form of common shapes such as rectangles and circles. They can also describe polygons and other complex shapes.
*
* You use overlays to layer more sophisticated content on top of a map view. For example, you could use an overlay to show the boundaries of a national park or trace a bus route along city streets. Mapbox GL defines several concrete classes that conform to this protocol and define standard shapes.
*
* Because overlays are also annotations, they have similar usage pattern to annotations. When added to a map view using the `addOverlay:` method, that view detects whenever the overlay’s defined region intersects the visible portion of the map. At that point, the map view asks its delegate to provide a special overlay view to draw the visual representation of the overlay. If you add an overlay to a map view as an annotation instead, it is treated as an annotation with a single point. */
@protocol MGLOverlay <MGLAnnotation>
/* The approximate center point of the overlay area. (required) (read-only)
*
* This point is typically set to the center point of the map’s bounding rectangle. It is used as the anchor point for any callouts displayed for the annotation. */
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
/** The cooordinate rectangle that encompasses the overlay. (required) (read-only)
*
* This property contains the smallest rectangle that completely encompasses the overlay. Implementers of this protocol must set this area when implementing their overlay class, and after setting it, you must not change it. */
@property (nonatomic, readonly) MGLCoordinateBounds overlayBounds;
/** Returns a Boolean indicating whether the specified rectangle intersects the receiver’s shape.
*
* You can implement this method to provide more specific bounds checking for an overlay. If you do not implement it, the bounding rectangle is used to detect intersections.
* @param overlayBounds The rectangle to intersect with the receiver’s area.
* @return `YES` if any part of the map rectangle intersects the receiver’s shape or `NO` if it does not. */
- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds;
@end

View File

@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MGLShape.h"
/** The `MGLPointAnnotation` class defines a concrete annotation object located at a specified point. You can use this class, rather than define your own, in situations where all you want to do is associate a point on the map with a title. */
@interface MGLPointAnnotation : MGLShape
/** The coordinate point of the annotation, specified as a latitude and longitude. */
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@end

View File

@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MGLMultiPoint.h"
#import "MGLOverlay.h"
/** The `MGLPolygon` class represents a shape consisting of one or more points that define a closed polygon. The points are connected end-to-end in the order they are provided. The first and last points are connected to each other to create the closed shape. */
@interface MGLPolygon : MGLMultiPoint <MGLOverlay>
/** Creates and returns an `MGLPolygon` object from the specified set of coordinates.
* @param coords The array of coordinates defining the shape. The data in this array is copied to the new object.
* @param count The number of items in the _`coords`_ array.
* @return A new polygon object. */
+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords
count:(NSUInteger)count;
@end

View File

@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MGLMultiPoint.h"
#import "MGLOverlay.h"
/** The `MGLPolyline` class represents a shape consisting of one or more points that define connecting line segments. The points are connected end-to-end in the order they are provided. The first and last points are not connected to each other. */
@interface MGLPolyline : MGLMultiPoint <MGLOverlay>
/** Creates and returns an `MGLPolygon` object from the specified set of coordinates.
* @param coords The array of coordinates defining the shape. The data in this array is copied to the new object.
* @param count The number of items in the _`coords`_ array.
* @return A new polyline object. */
+ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords
count:(NSUInteger)count;
@end

View File

@@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
#import "MGLAnnotation.h"
/** The `MGLShape` class is an abstract class that defines the basic properties for all shape-based annotation objects. This class must be subclassed and cannot be used as is. Subclasses are responsible for defining the geometry of the shape and providing an appropriate value for the coordinate property inherited from the `MGLAnnotation` protocol. */
@interface MGLShape : NSObject <MGLAnnotation>
/** The title of the shape annotation. The default value of this property is `nil`. */
@property (nonatomic, copy) NSString *title;
/** The subtitle of the shape annotation. The default value of this property is `nil`. */
@property (nonatomic, copy) NSString *subtitle;
@end

View File

@@ -1,4 +1,5 @@
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#if !__has_feature(nullability)
#define NS_ASSUME_NONNULL_BEGIN
@@ -13,8 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString * const MGLErrorDomain;
/** The mode used to track the user location on the map. */
typedef NS_ENUM(NSUInteger, MGLUserTrackingMode)
{
typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
/** The map does not follow the user location. */
MGLUserTrackingModeNone = 0,
/** The map follows the user location. */
@@ -23,4 +23,9 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode)
MGLUserTrackingModeFollowWithHeading
};
typedef struct {
CLLocationCoordinate2D sw;
CLLocationCoordinate2D ne;
} MGLCoordinateBounds;
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,11 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLMapView.h"
#import "MGLMultiPoint.h"
#import "MGLOverlay.h"
#import "MGLPointAnnotation.h"
#import "MGLPolygon.h"
#import "MGLPolyline.h"
#import "MGLShape.h"
#import "MGLTypes.h"
#import "MGLUserLocation.h"

View File

@@ -4,6 +4,7 @@
#include <mbgl/util/chrono.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/style/style_properties.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/vec.hpp>
@@ -13,6 +14,7 @@
#include <functional>
#include <vector>
#include <memory>
#include <unordered_map>
namespace mbgl {
@@ -26,6 +28,16 @@ namespace util {
template <class T> class Thread;
}
enum class AnnotationType : uint8_t {
Any = 0,
Point = 1 << 0,
Shape = 1 << 1,
};
using AnnotationIDs = std::vector<uint32_t>;
using AnnotationSegment = std::vector<LatLng>;
using AnnotationSegments = std::vector<AnnotationSegment>;
class Map : private util::noncopyable {
friend class View;
@@ -112,12 +124,16 @@ public:
void setDefaultPointAnnotationSymbol(const std::string&);
double getTopOffsetPixelsForAnnotationSymbol(const std::string&);
uint32_t addPointAnnotation(const LatLng&, const std::string& symbol);
std::vector<uint32_t> addPointAnnotations(const std::vector<LatLng>&,
const std::vector<std::string>& symbols);
AnnotationIDs addPointAnnotations(const AnnotationSegment&,
const std::vector<std::string>& symbols);
uint32_t addShapeAnnotation(const AnnotationSegments&,
const StyleProperties&);
AnnotationIDs addShapeAnnotations(const std::vector<AnnotationSegments>&,
const std::vector<StyleProperties>&);
void removeAnnotation(uint32_t);
void removeAnnotations(const std::vector<uint32_t>&);
std::vector<uint32_t> getAnnotationsInBounds(const LatLngBounds&);
LatLngBounds getBoundsForAnnotations(const std::vector<uint32_t>&);
void removeAnnotations(const AnnotationIDs&);
AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const AnnotationType& = AnnotationType::Any);
LatLngBounds getBoundsForAnnotations(const AnnotationIDs&);
// Memory
void setSourceTileCacheSize(size_t);

View File

@@ -3,7 +3,6 @@
#include <mbgl/util/variant.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/style/piecewisefunction_properties.hpp>
#include <array>
#include <string>

View File

@@ -41,12 +41,24 @@ struct LatLngBounds {
if (point.longitude > ne.longitude) ne.longitude = point.longitude;
}
inline bool contains(const LatLng& point) {
inline void extend(const LatLngBounds& bounds) {
extend(bounds.sw);
extend(bounds.ne);
}
inline bool contains(const LatLng& point) const {
return (point.latitude >= sw.latitude &&
point.latitude <= ne.latitude &&
point.longitude >= sw.longitude &&
point.longitude <= ne.longitude);
}
inline bool intersects(const LatLngBounds area) const {
return (area.ne.latitude > sw.latitude &&
area.sw.latitude < ne.latitude &&
area.ne.longitude > sw.longitude &&
area.sw.longitude < ne.longitude);
}
};
}

View File

@@ -1,12 +0,0 @@
#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

View File

@@ -1,30 +0,0 @@
#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,8 +6,6 @@
#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 = @[
@@ -130,7 +128,8 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
@"Add 100 Points",
@"Add 1,000 Points",
@"Add 10,000 Points",
@"Remove Points",
@"Add Test Shapes",
@"Remove Annotations",
nil];
[sheet showFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
@@ -167,6 +166,73 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
[self parseFeaturesAddingCount:10000];
}
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 7)
{
// PNW triangle
//
CLLocationCoordinate2D triangleCoordinates[3] =
{
CLLocationCoordinate2DMake(44, -122),
CLLocationCoordinate2DMake(46, -122),
CLLocationCoordinate2DMake(46, -121)
};
MGLPolygon *triangle = [MGLPolygon polygonWithCoordinates:triangleCoordinates count:3];
[self.mapView addAnnotation:triangle];
// Orcas Island hike
//
NSDictionary *hike = [NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]]
options:0
error:nil];
NSArray *hikeCoordinatePairs = hike[@"features"][0][@"geometry"][@"coordinates"];
CLLocationCoordinate2D *polylineCoordinates = (CLLocationCoordinate2D *)malloc([hikeCoordinatePairs count] * sizeof(CLLocationCoordinate2D));
for (NSUInteger i = 0; i < [hikeCoordinatePairs count]; i++)
{
polylineCoordinates[i] = CLLocationCoordinate2DMake([hikeCoordinatePairs[i][1] doubleValue], [hikeCoordinatePairs[i][0] doubleValue]);
}
MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:polylineCoordinates
count:[hikeCoordinatePairs count]];
[self.mapView addAnnotation:polyline];
free(polylineCoordinates);
// PA/NJ/DE polys
//
NSDictionary *threestates = [NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:@"threestates" ofType:@"geojson"]]
options:0
error:nil];
for (NSDictionary *feature in threestates[@"features"])
{
NSArray *stateCoordinatePairs = feature[@"geometry"][@"coordinates"];
while ([stateCoordinatePairs count] == 1) stateCoordinatePairs = stateCoordinatePairs[0];
CLLocationCoordinate2D *polygonCoordinates = (CLLocationCoordinate2D *)malloc([stateCoordinatePairs count] * sizeof(CLLocationCoordinate2D));
for (NSUInteger i = 0; i < [stateCoordinatePairs count]; i++)
{
polygonCoordinates[i] = CLLocationCoordinate2DMake([stateCoordinatePairs[i][1] doubleValue], [stateCoordinatePairs[i][0] doubleValue]);
}
MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]];
[self.mapView addAnnotation:polygon];
free(polygonCoordinates);
}
}
else if (buttonIndex == actionSheet.firstOtherButtonIndex + 8)
{
[self.mapView removeAnnotations:self.mapView.annotations];
}
@@ -178,7 +244,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"features" ofType:@"geojson"]];
NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]];
id features = [NSJSONSerialization JSONObjectWithData:featuresData
options:0
@@ -194,9 +260,9 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
[feature[@"geometry"][@"coordinates"][0] doubleValue]);
NSString *title = feature[@"properties"][@"NAME"];
MBXAnnotation *annotation = [MBXAnnotation annotationWithLocation:coordinate
title:title
subtitle:nil];
MGLPointAnnotation *annotation = [MGLPointAnnotation new];
annotation.coordinate = coordinate;
annotation.title = title;
[annotations addObject:annotation];
@@ -279,6 +345,21 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
return YES;
}
- (CGFloat)mapView:(__unused MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation
{
return ([annotation isKindOfClass:[MGLPolygon class]] ? 0.5 : 1.0);
}
- (UIColor *)mapView:(__unused MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation
{
return ([annotation isKindOfClass:[MGLPolyline class]] ? [UIColor purpleColor] : [UIColor blackColor]);
}
- (UIColor *)mapView:(__unused MGLMapView *)mapView fillColorForPolygonAnnotation:(__unused MGLPolygon *)annotation
{
return (annotation.pointCount > 3 ? [UIColor greenColor] : [UIColor redColor]);
}
- (void)mapView:(__unused MGLMapView *)mapView didChangeUserTrackingMode:(MGLUserTrackingMode)mode animated:(__unused BOOL)animated
{
UIImage *newButtonImage;

View File

@@ -10,7 +10,9 @@
'mac_bundle': 1,
'mac_bundle_resources': [
'<!@(find ../ios/app/img -type f)',
'./features.geojson',
'./points.geojson',
'./polyline.geojson',
'./threestates.geojson',
'./Settings.bundle/'
],
@@ -29,8 +31,6 @@
'./MBXAppDelegate.m',
'./MBXViewController.h',
'./MBXViewController.mm',
'./MBXAnnotation.h',
'./MBXAnnotation.m',
'../../platform/darwin/settings_nsuserdefaults.mm',
],

14187
ios/app/polyline.geojson Normal file

File diff suppressed because it is too large Load Diff

1612
ios/app/threestates.geojson Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1015,7 +1015,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
tapBounds.extend(MGLLatLngFromLocationCoordinate2D(coordinate));
// query for nearby annotations
std::vector<uint32_t> nearbyAnnotations = _mbglMap->getAnnotationsInBounds(tapBounds);
std::vector<uint32_t> nearbyAnnotations = _mbglMap->getAnnotationsInBounds(tapBounds, mbgl::AnnotationType::Point);
int32_t newSelectedAnnotationID = -1;
@@ -1669,36 +1669,131 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
{
if ( ! annotations) return;
std::vector<mbgl::LatLng> latLngs;
latLngs.reserve(annotations.count);
std::vector<mbgl::LatLng> points;
std::vector<std::string> symbols;
symbols.reserve(annotations.count);
std::vector<mbgl::AnnotationSegments> shapes;
std::vector<mbgl::StyleProperties> shapesProperties;
BOOL delegateImplementsSymbolLookup = [self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)];
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:)];
for (id <MGLAnnotation> annotation in annotations)
{
assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]);
latLngs.push_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate));
NSString *symbolName = nil;
if (delegateImplementsSymbolLookup)
if ([annotation isKindOfClass:[MGLMultiPoint class]])
{
symbolName = [self.delegate mapView:self symbolNameForAnnotation:annotation];
}
CGFloat alpha = (delegateImplementsAlphaForShape ?
[self.delegate mapView:self alphaForShapeAnnotation:annotation] :
1.0);
symbols.push_back((symbolName ? [symbolName UTF8String] : ""));
UIColor *strokeColor = (delegateImplementsStrokeColorForShape ?
[self.delegate mapView:self strokeColorForShapeAnnotation:annotation] :
[UIColor blackColor]);
assert(strokeColor);
CGFloat r,g,b,a;
[strokeColor getRed:&r green:&g blue:&b alpha:&a];
mbgl::Color strokeNativeColor({{ (float)r, (float)g, (float)b, (float)a }});
mbgl::StyleProperties shapeProperties;
if ([annotation isKindOfClass:[MGLPolyline class]])
{
CGFloat lineWidth = (delegateImplementsLineWidthForPolyline ?
[self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation] :
3.0);
mbgl::LineProperties lineProperties;
lineProperties.opacity = alpha;
lineProperties.color = strokeNativeColor;
lineProperties.width = lineWidth;
shapeProperties.set<mbgl::LineProperties>(lineProperties);
}
else if ([annotation isKindOfClass:[MGLPolygon class]])
{
UIColor *fillColor = (delegateImplementsFillColorForPolygon ?
[self.delegate mapView:self fillColorForPolygonAnnotation:(MGLPolygon *)annotation] :
[UIColor blueColor]);
assert(fillColor);
[fillColor getRed:&r green:&g blue:&b alpha:&a];
mbgl::Color fillNativeColor({{ (float)r, (float)g, (float)b, (float)a }});
mbgl::FillProperties fillProperties;
fillProperties.opacity = alpha;
fillProperties.stroke_color = strokeNativeColor;
fillProperties.fill_color = fillNativeColor;
shapeProperties.set<mbgl::FillProperties>(fillProperties);
}
else
{
[[NSException exceptionWithName:@"MGLUnknownShapeClassException"
reason:[NSString stringWithFormat:@"%@ is an unknown shape class", [annotation class]]
userInfo:nil] raise];
}
shapesProperties.push_back(shapeProperties);
NSUInteger count = [(MGLMultiPoint *)annotation pointCount];
CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D));
[(MGLMultiPoint *)annotation getCoordinates:coordinates range:NSMakeRange(0, count)];
mbgl::AnnotationSegment shape;
shape.reserve(count);
for (NSUInteger i = 0; i < count; i++)
{
shape.push_back(mbgl::LatLng(coordinates[i].latitude, coordinates[i].longitude));
}
free(coordinates);
shapes.push_back({{ shape }});
}
else
{
points.push_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate));
NSString *symbolName = nil;
if (delegateImplementsSymbolLookup)
{
symbolName = [self.delegate mapView:self symbolNameForAnnotation:annotation];
}
symbols.push_back((symbolName ? [symbolName UTF8String] : ""));
}
}
std::vector<uint32_t> annotationIDs = _mbglMap->addPointAnnotations(latLngs, symbols);
for (size_t i = 0; i < annotationIDs.size(); ++i)
if (points.size())
{
[self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(annotationIDs[i]) }
forKey:annotations[i]];
std::vector<uint32_t> pointAnnotationIDs = _mbglMap->addPointAnnotations(points, symbols);
for (size_t i = 0; i < pointAnnotationIDs.size(); ++i)
{
[self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(pointAnnotationIDs[i]) }
forKey:annotations[i]];
}
}
if (shapes.size())
{
std::vector<uint32_t> shapeAnnotationIDs = _mbglMap->addShapeAnnotations(shapes, shapesProperties);
for (size_t i = 0; i < shapeAnnotationIDs.size(); ++i)
{
[self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(shapeAnnotationIDs[i]) }
forKey:annotations[i]];
}
}
}
@@ -1750,6 +1845,8 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
assert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)]);
if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) return;
if ( ! [self viewportBounds].contains(MGLLatLngFromLocationCoordinate2D(firstAnnotation.coordinate))) return;
[self selectAnnotation:firstAnnotation animated:NO];
@@ -1759,6 +1856,8 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
{
if ( ! annotation) return;
if ([annotation isKindOfClass:[MGLMultiPoint class]]) return;
if ( ! [self viewportBounds].contains(MGLLatLngFromLocationCoordinate2D(annotation.coordinate))) return;
if (annotation == self.selectedAnnotation) return;
@@ -1865,12 +1964,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
// clean up
self.selectedAnnotationCalloutView = nil;
self.selectedAnnotation = nil;
}
// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)])
{
[self.delegate mapView:self didDeselectAnnotation:annotation];
// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)])
{
[self.delegate mapView:self didDeselectAnnotation:annotation];
}
}
}

View File

@@ -0,0 +1,102 @@
#import "MGLMultiPoint.h"
#import "MGLTypes.h"
#import <mbgl/util/geo.hpp>
@implementation MGLMultiPoint
{
CLLocationCoordinate2D *_coords;
size_t _count;
mbgl::LatLngBounds _bounds;
}
- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords
count:(NSUInteger)count
{
self = [super init];
if (self)
{
_count = count;
_coords = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D));
for (NSUInteger i = 0; i < _count; i++)
{
_coords[i] = coords[i];
_bounds.extend(mbgl::LatLng(coords[i].latitude, coords[i].longitude));
}
}
return self;
}
- (void)dealloc
{
free(_coords);
}
- (CLLocationCoordinate2D)coordinate
{
if ([self isMemberOfClass:[MGLMultiPoint class]])
{
[[NSException exceptionWithName:@"MGLAbstractClassException"
reason:@"MGLMultiPoint is an abstract class"
userInfo:nil] raise];
}
assert(_count > 0);
return CLLocationCoordinate2DMake(_coords[0].latitude, _coords[0].longitude);
}
- (NSUInteger)pointCount
{
if ([self isMemberOfClass:[MGLMultiPoint class]])
{
[[NSException exceptionWithName:@"MGLAbstractClassException"
reason:@"MGLMultiPoint is an abstract class"
userInfo:nil] raise];
}
return _count;
}
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range
{
if ([self isMemberOfClass:[MGLMultiPoint class]])
{
[[NSException exceptionWithName:@"MGLAbstractClassException"
reason:@"MGLMultiPoint is an abstract class"
userInfo:nil] raise];
}
assert(range.location + range.length <= _count);
NSUInteger index = 0;
for (NSUInteger i = range.location; i < range.location + range.length; i++)
{
coords[index] = _coords[i];
index++;
}
}
- (MGLCoordinateBounds)overlayBounds
{
return {
CLLocationCoordinate2DMake(_bounds.sw.latitude, _bounds.sw.longitude),
CLLocationCoordinate2DMake(_bounds.ne.longitude, _bounds.ne.longitude)
};
}
- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds
{
mbgl::LatLngBounds area(
mbgl::LatLng(overlayBounds.sw.latitude, overlayBounds.sw.longitude),
mbgl::LatLng(overlayBounds.ne.latitude, overlayBounds.ne.longitude)
);
return _bounds.intersects(area);
}
@end

View File

@@ -0,0 +1,8 @@
#import "MGLMultiPoint.h"
@interface MGLMultiPoint (Private)
- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds;
@end

View File

@@ -0,0 +1,7 @@
#import "MGLPointAnnotation.h"
@implementation MGLPointAnnotation
@synthesize coordinate;
@end

15
platform/ios/MGLPolygon.m Normal file
View File

@@ -0,0 +1,15 @@
#import "MGLPolygon.h"
#import "MGLMultiPoint_Private.h"
@implementation MGLPolygon
@synthesize overlayBounds;
+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords
count:(NSUInteger)count
{
return [[self alloc] initWithCoordinates:coords count:count];
}
@end

View File

@@ -0,0 +1,15 @@
#import "MGLPolyline.h"
#import "MGLMultiPoint_Private.h"
@implementation MGLPolyline
@synthesize overlayBounds;
+ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords
count:(NSUInteger)count
{
return [[self alloc] initWithCoordinates:coords count:count];
}
@end

14
platform/ios/MGLShape.m Normal file
View File

@@ -0,0 +1,14 @@
#import "MGLShape.h"
@implementation MGLShape
- (CLLocationCoordinate2D)coordinate
{
[[NSException exceptionWithName:@"MGLAbstractClassException"
reason:@"MGLShape is an abstract class"
userInfo:nil] raise];
return CLLocationCoordinate2DMake(MAXFLOAT, MAXFLOAT);
}
@end

View File

@@ -4,6 +4,7 @@
#include <mbgl/map/live_tile.hpp>
#include <mbgl/map/map_data.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/geojsonvt/geojsonvt_convert.hpp>
#include <mbgl/util/ptr.hpp>
#include <algorithm>
@@ -11,35 +12,15 @@
namespace mbgl {
enum class AnnotationType : uint8_t {
Point,
Shape
};
using AnnotationSegment = std::vector<LatLng>;
using AnnotationSegments = std::vector<AnnotationSegment>;
class Annotation : private util::noncopyable {
friend class AnnotationManager;
public:
Annotation(AnnotationType, const AnnotationSegments&);
private:
LatLng getPoint() const;
LatLngBounds getBounds() const { return bounds; }
private:
const AnnotationType type = AnnotationType::Point;
const AnnotationSegments geometry;
std::unordered_map<TileID, std::weak_ptr<const LiveTileFeature>, TileID::Hash> tileFeatures;
const LatLngBounds bounds;
};
Annotation::Annotation(AnnotationType type_, const AnnotationSegments& geometry_)
: type(type_),
Annotation::Annotation(AnnotationType type_,
const AnnotationSegments& geometry_,
const StyleProperties& styleProperties_)
: styleProperties(styleProperties_),
type(type_),
geometry(geometry_),
bounds([this] {
LatLngBounds bounds_;
assert(type != AnnotationType::Any);
if (type == AnnotationType::Point) {
bounds_ = { getPoint(), getPoint() };
} else {
@@ -67,6 +48,16 @@ AnnotationManager::~AnnotationManager() {
// Annotation so we can't destruct the object with just the header file.
}
void AnnotationManager::markStaleTiles(std::unordered_set<TileID, TileID::Hash> ids) {
std::lock_guard<std::mutex> lock(mtx);
std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin()));
}
std::unordered_set<TileID, TileID::Hash> AnnotationManager::resetStaleTiles() {
std::lock_guard<std::mutex> lock(mtx);
return std::move(staleTiles);
}
void AnnotationManager::setDefaultPointAnnotationSymbol(const std::string& symbol) {
std::lock_guard<std::mutex> lock(mtx);
defaultPointAnnotationSymbol = symbol;
@@ -77,118 +68,314 @@ uint32_t AnnotationManager::nextID() {
}
vec2<double> AnnotationManager::projectPoint(const LatLng& point) {
// Clamp to the latitude limits of Mercator.
const double constrainedLatitude = std::fmin(std::fmax(point.latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX);
// Project a coordinate into unit space in a square map.
const double sine = std::sin(point.latitude * M_PI / 180.0);
const double sine = std::sin(constrainedLatitude * M_PI / 180.0);
const double x = point.longitude / 360.0 + 0.5;
const double y = 0.5 - 0.25 * std::log((1.0 + sine) / (1.0 - sine)) / M_PI;
return { x, y };
}
std::pair<std::vector<TileID>, AnnotationIDs>
AnnotationManager::addPointAnnotations(const std::vector<LatLng>& points,
const std::vector<std::string>& symbols,
const MapData& data) {
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs>
AnnotationManager::addAnnotations(const AnnotationType type,
const std::vector<AnnotationSegments>& segments,
const std::vector<StyleProperties>& styleProperties,
const AnnotationsProperties& annotationsProperties,
const MapData& data) {
std::lock_guard<std::mutex> lock(mtx);
assert(type != AnnotationType::Any);
// We pre-generate tiles to contain each annotation up to the map's max zoom.
// We do this for fast rendering without projection conversions on the fly, as well as
// to simplify bounding box queries of annotations later. Tiles get invalidated when
// annotations are added or removed in order to refresh the map render without
// touching the base map underneath.
const uint16_t extent = 4096;
AnnotationIDs annotationIDs;
annotationIDs.reserve((type == AnnotationType::Shape ?
segments.size() : // shapes
segments[0][0].size())); // points
std::vector<uint32_t> annotationIDs;
annotationIDs.reserve(points.size());
std::unordered_set<TileID, TileID::Hash> affectedTiles;
std::vector<TileID> affectedTiles;
const uint8_t maxZoom = data.transform.getMaxZoom();
for (size_t i = 0; i < points.size(); ++i) {
const uint32_t annotationID = nextID();
for (size_t s = 0; s < segments.size(); ++s) {
// track the annotation global ID and its geometry
auto anno_it = annotations.emplace(
annotationID,
std::make_unique<Annotation>(AnnotationType::Point,
AnnotationSegments({ { points[i] } })));
if (type == AnnotationType::Point) {
const uint8_t maxZoom = data.transform.getMaxZoom();
for (size_t l = 0; l < segments[s].size(); ++l) {
// side length of map at this zoom
uint32_t z2 = 1 << maxZoom;
for (size_t p = 0; p < segments[s][l].size(); ++p) {
// projection conversion into unit space
const vec2<double> p = projectPoint(points[i]);
auto& point = segments[s][l][p];
uint32_t x = p.x * z2;
uint32_t y = p.y * z2;
// projection conversion into unit space
const auto pp = projectPoint(point);
for (int8_t z = maxZoom; z >= 0; z--) {
affectedTiles.emplace_back(z, x, y, z);
TileID tileID = affectedTiles.back();
const uint32_t pointAnnotationID = nextID();
// calculate tile coordinate
const Coordinate coordinate(extent * (p.x * z2 - x), extent * (p.y * z2 - y));
// at render time we style the point according to its {sprite} field
std::unordered_map<std::string, std::string> pointFeatureProperties;
const std::string& symbol = annotationsProperties.at("symbols")[p];
if (symbol.length()) {
pointFeatureProperties.emplace("sprite", symbol);
} else {
pointFeatureProperties.emplace("sprite", defaultPointAnnotationSymbol);
}
const GeometryCollection geometries({ { { { coordinate } } } });
// add individual point tile feature
auto featureAffectedTiles = addTileFeature(
pointAnnotationID,
AnnotationSegments({{ point }}),
std::vector<std::vector<vec2<double>>>({{ pp }}),
AnnotationType::Point,
{{ }},
pointFeatureProperties,
maxZoom
);
// at render time we style the annotation according to its {sprite} field
const std::map<std::string, std::string> properties = {
{ "sprite", (symbols[i].length() ? symbols[i] : defaultPointAnnotationSymbol) }
};
std::copy(featureAffectedTiles.begin(), featureAffectedTiles.end(), std::inserter(affectedTiles, affectedTiles.begin()));
auto feature =
std::make_shared<const LiveTileFeature>(FeatureType::Point, geometries, properties);
auto tile_it = tiles.find(tileID);
if (tile_it != tiles.end()) {
//
// We have this tile created already. Add this feature to it.
//
// get point layer & add feature
auto layer =
tile_it->second.second->getMutableLayer(layerID);
layer->addFeature(feature);
// record annotation association with tile
tile_it->second.first.insert(annotationID);
} else {
//
// We need to create a new tile for this feature.
//
// create point layer & add feature
util::ptr<LiveTileLayer> layer = std::make_shared<LiveTileLayer>();
layer->addFeature(feature);
// create tile & record annotation association
auto tile_pos = tiles.emplace(
tileID, std::make_pair(std::unordered_set<uint32_t>({ annotationID }),
std::make_unique<LiveTile>()));
// add point layer to tile
tile_pos.first->second.second->addLayer(layerID, layer);
annotationIDs.push_back(pointAnnotationID);
}
}
} else {
// 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::weak_ptr<const LiveTileFeature>(feature));
const uint32_t shapeAnnotationID = nextID();
// get ready for the next-lower zoom number
z2 /= 2;
x /= 2;
y /= 2;
// current shape tiles are on-the-fly, so we don't get any "affected tiles"
// and just expire all annotation tiles for shape adds
addTileFeature(
shapeAnnotationID,
segments[s],
{{ }},
AnnotationType::Shape,
styleProperties[s],
{{ }},
maxZoom
);
annotationIDs.push_back(shapeAnnotationID);
}
annotationIDs.push_back(annotationID);
}
// Tile:IDs that need refreshed and the annotation identifiers held onto by the client.
return std::make_pair(affectedTiles, annotationIDs);
}
std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& ids,
const MapData& data) {
std::unordered_set<TileID, TileID::Hash>
AnnotationManager::addTileFeature(const uint32_t annotationID,
const AnnotationSegments& segments,
const std::vector<std::vector<vec2<double>>>& projectedFeature,
const AnnotationType& type,
const StyleProperties& styleProperties,
const std::unordered_map<std::string, std::string>& featureProperties,
const uint8_t maxZoom) {
assert(type != AnnotationType::Any);
// track the annotation global ID and its original geometry
auto anno_it = annotations.emplace(annotationID,
std::make_unique<Annotation>(type, segments, styleProperties));
std::unordered_set<TileID, TileID::Hash> affectedTiles;
if (type == AnnotationType::Shape) {
orderedShapeAnnotations.push_back(annotationID);
using namespace mapbox::util::geojsonvt;
const uint32_t z2 = 1 << maxZoom;
const double baseTolerance = 3;
const uint16_t extent = 4096;
const double tolerance = baseTolerance / (z2 * extent);
ProjectedGeometryContainer rings;
std::vector<LonLat> points;
for (size_t i = 0; i < segments[0].size(); ++i) { // first segment for now (no holes)
const double constraintedLatitude = std::fmin(std::fmax(segments[0][i].latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX);
points.push_back(LonLat(segments[0][i].longitude, constraintedLatitude));
}
ProjectedFeatureType featureType;
if (styleProperties.is<FillProperties>()) {
featureType = ProjectedFeatureType::Polygon;
if (points.front().lon != points.back().lon || points.front().lat != points.back().lat) {
points.push_back(LonLat(points.front().lon, points.front().lat));
}
} else {
featureType = ProjectedFeatureType::LineString;
}
ProjectedGeometryContainer ring = Convert::project(points, tolerance);
rings.members.push_back(ring);
std::vector<ProjectedFeature> features;
features.push_back(Convert::create(Tags(), featureType, rings));
shapeTilers.emplace(annotationID, std::make_unique<GeoJSONVT>(features, maxZoom));
} else {
// side length of map at max zoom
uint32_t z2 = 1 << maxZoom;
const uint16_t extent = 4096;
uint32_t x = 0;
uint32_t y = 0;
for (int8_t z = maxZoom; z >= 0; z--) {
std::unordered_map<TileID, GeometryCollection, TileID::Hash> featureTiles;
if (type == AnnotationType::Point) {
auto& pp = projectedFeature[0][0];
x = pp.x * z2;
y = pp.y * z2;
const Coordinate coordinate(extent * (pp.x * z2 - x), extent * (pp.y * z2 - y));
GeometryCollection geometries = {{ {{ coordinate }} }};
featureTiles.emplace(TileID(z, x, y, z), geometries);
} else {
for (size_t l = 0; l < projectedFeature.size(); ++l) {
std::vector<Coordinate> line;
for (size_t p = 0; p < projectedFeature[l].size(); ++p) {
auto& pp = projectedFeature[l][p];
x = pp.x * z2;
y = pp.y * z2;
const Coordinate coordinate(extent * (pp.x * z2 - x), extent * (pp.y * z2 - y));
auto tile_it = featureTiles.find(TileID(z, x, y, z));
if (tile_it != featureTiles.end()) {
GeometryCollection& geometries = featureTiles.find(TileID(z, x, y, z))->second;
if (geometries.size()) {
geometries.back().push_back(coordinate);
} else {
geometries.push_back({{ coordinate }});
}
} else {
GeometryCollection geometries = {{ {{ coordinate }} }};
featureTiles.emplace(TileID(z, x, y, z), geometries);
}
}
}
}
for (auto& featureTile : featureTiles) {
// determine feature type
FeatureType featureType;
if (type == AnnotationType::Point) {
featureType = FeatureType::Point;
} else if (styleProperties.is<LineProperties>()) {
featureType = FeatureType::LineString;
} else if (styleProperties.is<FillProperties>()) {
featureType = FeatureType::Polygon;
} else {
throw std::runtime_error("Invalid feature type");
}
// create tile feature
auto feature = std::make_shared<const LiveTileFeature>(
featureType,
featureTile.second,
featureProperties
);
// check for tile & create if necessary
auto tile_pos = tiles.emplace(featureTile.first,
std::make_pair(std::unordered_set<uint32_t>({ annotationID }),
std::make_unique<LiveTile>()));
// check for annotation layer & create if necessary
util::ptr<LiveTileLayer> layer;
std::string layerID = "";
if (type == AnnotationType::Point) {
layerID = PointLayerID;
} else {
layerID = ShapeLayerID + "." + std::to_string(annotationID);
}
if (tile_pos.second || tile_pos.first->second.second->getMutableLayer(layerID) == nullptr) {
layer = std::make_shared<LiveTileLayer>();
tile_pos.first->second.second->addLayer(layerID, layer);
} else {
layer = tile_pos.first->second.second->getMutableLayer(layerID);
// associate annotation with tile
tile_pos.first->second.first.insert(annotationID);
}
// add feature to layer
layer->addFeature(feature);
// 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->tilePointFeatures.emplace(featureTile.first, std::weak_ptr<const LiveTileFeature>(feature));
// track affected tile
affectedTiles.insert(featureTile.first);
}
// get ready for the next-lower zoom number
z2 /= 2;
x /= 2;
y /= 2;
}
}
return affectedTiles;
}
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs>
AnnotationManager::addPointAnnotations(const AnnotationSegment& points,
const AnnotationsProperties& annotationsProperties,
const MapData& data) {
return addAnnotations(AnnotationType::Point,
{{ points }},
{{ }},
annotationsProperties,
data);
}
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs>
AnnotationManager::addShapeAnnotations(const std::vector<AnnotationSegments>& shapes,
const std::vector<StyleProperties>& styleProperties,
const AnnotationsProperties& annotationsProperties,
const MapData& data) {
return addAnnotations(AnnotationType::Shape,
shapes,
styleProperties,
annotationsProperties,
data);
}
std::unordered_set<TileID, TileID::Hash> AnnotationManager::removeAnnotations(const AnnotationIDs& ids,
const MapData& data) {
std::lock_guard<std::mutex> lock(mtx);
std::vector<TileID> affectedTiles;
std::unordered_set<TileID, TileID::Hash> affectedTiles;
std::vector<uint32_t> z2s;
const uint8_t zoomCount = data.transform.getMaxZoom() + 1;
@@ -207,25 +394,44 @@ std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& id
const auto& annotation_it = annotations.find(annotationID);
if (annotation_it != annotations.end()) {
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;
TileID tid(z, x, y, z);
// 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 =
tiles[tid].second->getMutableLayer(layerID);
layer->removeFeature(features_it->second);
affectedTiles.push_back(tid);
// remove feature(s) from relevant tiles
if (annotation->type == AnnotationType::Point) {
// 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;
TileID tid(z, x, y, z);
// 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->tilePointFeatures.find(tid);
if (features_it != annotation->tilePointFeatures.end()) {
// points share a layer; remove feature
auto layer = tiles[tid].second->getMutableLayer(PointLayerID);
layer->removeFeature(features_it->second);
affectedTiles.insert(tid);
}
}
} else {
// remove shape layer from tiles if relevant
for (auto tile_it = tiles.begin(); tile_it != tiles.end(); ++tile_it) {
if (tile_it->second.first.count(annotationID)) {
tile_it->second.second->removeLayer(ShapeLayerID + "." + std::to_string(annotationID));
affectedTiles.insert(tile_it->first);
}
}
// clear shape from render order
auto shape_it = std::find(orderedShapeAnnotations.begin(), orderedShapeAnnotations.end(), annotationID);
orderedShapeAnnotations.erase(shape_it);
// clear shape tiler
shapeTilers.erase(annotationID);
}
annotations.erase(annotationID);
}
}
@@ -234,8 +440,18 @@ std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& id
return affectedTiles;
}
std::vector<uint32_t> AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds,
const MapData& data) const {
const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t annotationID) const {
std::lock_guard<std::mutex> lock(mtx);
auto anno_it = annotations.find(annotationID);
assert(anno_it != annotations.end());
return anno_it->second->styleProperties;
}
AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds,
const MapData& data,
const AnnotationType& type) const {
std::lock_guard<std::mutex> lock(mtx);
const uint8_t z = data.transform.getMaxZoom();
@@ -247,7 +463,7 @@ std::vector<uint32_t> AnnotationManager::getAnnotationsInBounds(const LatLngBoun
const TileID nwTile(z, swPoint.x * z2, nePoint.y * z2, z);
const TileID seTile(z, nePoint.x * z2, swPoint.y * z2, z);
std::vector<uint32_t> matchingAnnotations;
std::unordered_set<uint32_t> matchingAnnotations;
for (auto& tile : tiles) {
TileID id = tile.first;
@@ -255,32 +471,50 @@ std::vector<uint32_t> AnnotationManager::getAnnotationsInBounds(const LatLngBoun
if (id.x >= nwTile.x && id.x <= seTile.x && id.y >= nwTile.y && id.y <= seTile.y) {
if (id.x > nwTile.x && id.x < seTile.x && id.y > nwTile.y && id.y < seTile.y) {
// Trivial accept; this tile is completely inside the query bounds, so
// we'll return all of its annotations.
std::copy(tile.second.first.begin(), tile.second.first.end(),
std::back_inserter(matchingAnnotations));
// we'll return all of its annotations that match type (if specified).
if (type != AnnotationType::Any) {
std::copy_if(tile.second.first.begin(), tile.second.first.end(),
std::inserter(matchingAnnotations, matchingAnnotations.begin()),
[&](const uint32_t annotationID) -> bool {
const auto it = annotations.find(annotationID);
if (it != annotations.end()) {
return (it->second->type == type);
} else {
return false;
}
});
} else {
std::copy(tile.second.first.begin(), tile.second.first.end(),
std::inserter(matchingAnnotations, matchingAnnotations.begin()));
}
} else {
// This tile is intersected by the query bounds. We need to check the
// tile's annotations' bounding boxes individually.
std::copy_if(tile.second.first.begin(), tile.second.first.end(),
std::back_inserter(matchingAnnotations),
std::inserter(matchingAnnotations, matchingAnnotations.begin()),
[&](const uint32_t annotationID) -> bool {
const auto it = annotations.find(annotationID);
if (it != annotations.end()) {
const LatLngBounds annoBounds = it->second->getBounds();
return (annoBounds.sw.latitude >= queryBounds.sw.latitude &&
annoBounds.ne.latitude <= queryBounds.ne.latitude &&
annoBounds.sw.longitude >= queryBounds.sw.longitude &&
annoBounds.ne.longitude <= queryBounds.ne.longitude);
} else {
return false;
// check type
if (type != AnnotationType::Any && it->second->type != type) {
return false;
}
// check bounds
if (it->second->type == AnnotationType::Point) {
return queryBounds.contains(it->second->getPoint());
} else if (it->second->type == AnnotationType::Shape) {
return queryBounds.intersects(it->second->getBounds());
}
}
return false;
});
}
}
}
}
return matchingAnnotations;
return AnnotationIDs(matchingAnnotations.begin(), matchingAnnotations.end());
}
LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids) const {
@@ -290,7 +524,7 @@ LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids
for (auto id : ids) {
const auto annotation_it = annotations.find(id);
if (annotation_it != annotations.end()) {
bounds.extend(annotation_it->second->getPoint());
bounds.extend(annotation_it->second->getBounds());
}
}
@@ -300,13 +534,89 @@ LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids
const LiveTile* AnnotationManager::getTile(const TileID& id) {
std::lock_guard<std::mutex> lock(mtx);
const auto tile_it = tiles.find(id);
if (tile_it != tiles.end()) {
return tile_it->second.second.get();
// look up any existing annotation tile
LiveTile *renderTile = nullptr;
const auto tile_lookup_it = tiles.find(id);
if (tile_lookup_it != tiles.end()) {
// it exists and may have annotations already
renderTile = tile_lookup_it->second.second.get();
} else if (orderedShapeAnnotations.size()) {
// it needs created, but only for on-demand shapes
renderTile = tiles.emplace(id,
std::make_pair(std::unordered_set<uint32_t>(), std::make_unique<LiveTile>())
).first->second.second.get();
}
return nullptr;
if (renderTile != nullptr && orderedShapeAnnotations.size()) {
// create shape tile layers from GeoJSONVT queries
for (auto& tiler_it : shapeTilers) {
const auto annotationID = tiler_it.first;
const std::string layerID = ShapeLayerID + "." + std::to_string(annotationID);
// check for existing render layer
auto renderLayer = renderTile->getMutableLayer(layerID);
if (renderLayer == nullptr) {
// we might need to create a tile layer for this shape
const auto& shapeTile = tiler_it.second->getTile(id.z, id.x, id.y);
if (shapeTile) {
// shape exists on this tile; let's make a layer
renderLayer = std::make_shared<LiveTileLayer>();
// convert the features and add to render layer
for (auto& shapeFeature : shapeTile.features) {
using namespace mapbox::util::geojsonvt;
FeatureType renderType = FeatureType::Unknown;
if (shapeFeature.type == TileFeatureType::LineString) {
renderType = FeatureType::LineString;
} else if (shapeFeature.type == TileFeatureType::Polygon) {
renderType = FeatureType::Polygon;
}
assert(renderType != FeatureType::Unknown);
GeometryCollection renderGeometry;
for (auto& shapeGeometry : shapeFeature.geometry) {
std::vector<Coordinate> renderLine;
auto& shapeRing = shapeGeometry.get<TileRing>();
for (auto& shapePoint : shapeRing.points) {
renderLine.emplace_back(shapePoint.x, shapePoint.y);
}
renderGeometry.push_back(renderLine);
}
auto renderFeature = std::make_shared<LiveTileFeature>(renderType, renderGeometry);
renderLayer->addFeature(renderFeature);
}
// move the layer to the render tile
renderTile->addLayer(layerID, renderLayer);
// associate the annotation with the tile
auto tile_update_it = tiles.find(id);
assert(tile_update_it != tiles.end());
tile_update_it->second.first.insert(annotationID);
}
}
}
}
return renderTile;
}
const std::string AnnotationManager::layerID = "com.mapbox.annotations.points";
const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points";
const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape";
}

View File

@@ -1,8 +1,13 @@
#ifndef MBGL_MAP_ANNOTATIONS
#define MBGL_MAP_ANNOTATIONS
#include <mbgl/map/map.hpp>
#include <mbgl/map/geometry_tile.hpp>
#include <mbgl/map/tile_id.hpp>
#include <mbgl/style/style_properties.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/geojsonvt/geojsonvt.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/vec.hpp>
@@ -18,35 +23,87 @@ namespace mbgl {
class Annotation;
class Map;
class LiveTile;
class LiveTileFeature;
class MapData;
using AnnotationIDs = std::vector<uint32_t>;
using AnnotationsProperties = std::unordered_map<std::string, std::vector<std::string>>;
using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT;
class Annotation : private util::noncopyable {
friend class AnnotationManager;
public:
Annotation(AnnotationType, const AnnotationSegments&, const StyleProperties&);
public:
const StyleProperties styleProperties;
private:
LatLng getPoint() const;
LatLngBounds getBounds() const { return bounds; }
private:
const AnnotationType type = AnnotationType::Point;
const AnnotationSegments geometry;
std::unordered_map<TileID, std::weak_ptr<const LiveTileFeature>, TileID::Hash> tilePointFeatures;
const LatLngBounds bounds;
};
class AnnotationManager : private util::noncopyable {
public:
AnnotationManager();
~AnnotationManager();
void markStaleTiles(std::unordered_set<TileID, TileID::Hash>);
std::unordered_set<TileID, TileID::Hash> resetStaleTiles();
void setDefaultPointAnnotationSymbol(const std::string& symbol);
std::pair<std::vector<TileID>, AnnotationIDs> addPointAnnotations(
const std::vector<LatLng>&, const std::vector<std::string>& symbols, const MapData&);
std::vector<TileID> removeAnnotations(const AnnotationIDs&, const MapData&);
AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const MapData&) const;
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs> addPointAnnotations(
const AnnotationSegment&,
const AnnotationsProperties&,
const MapData&);
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs> addShapeAnnotations(
const std::vector<AnnotationSegments>&,
const std::vector<StyleProperties>&,
const AnnotationsProperties&,
const MapData&);
std::unordered_set<TileID, TileID::Hash> removeAnnotations(const AnnotationIDs&, const MapData&);
AnnotationIDs getOrderedShapeAnnotations() const { return orderedShapeAnnotations; }
const StyleProperties getAnnotationStyleProperties(uint32_t) const;
AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const MapData&, const AnnotationType& = AnnotationType::Any) const;
LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const;
const LiveTile* getTile(const TileID& id);
static const std::string layerID;
static const std::string PointLayerID;
static const std::string ShapeLayerID;
private:
inline uint32_t nextID();
static vec2<double> projectPoint(const LatLng& point);
std::pair<std::unordered_set<TileID, TileID::Hash>, AnnotationIDs> addAnnotations(
const AnnotationType,
const std::vector<AnnotationSegments>&,
const std::vector<StyleProperties>&,
const AnnotationsProperties&,
const MapData&);
std::unordered_set<TileID, TileID::Hash> addTileFeature(
const uint32_t annotationID,
const AnnotationSegments&,
const std::vector<std::vector<vec2<double>>>& projectedFeature,
const AnnotationType&,
const StyleProperties&,
const std::unordered_map<std::string, std::string>& featureProperties,
const uint8_t maxZoom);
private:
mutable std::mutex mtx;
std::string defaultPointAnnotationSymbol;
std::unordered_map<uint32_t, std::unique_ptr<Annotation>> annotations;
std::vector<uint32_t> orderedShapeAnnotations;
std::unordered_map<TileID, std::pair<std::unordered_set<uint32_t>, std::unique_ptr<LiveTile>>, TileID::Hash> tiles;
std::unordered_map<uint32_t, std::unique_ptr<GeoJSONVT>> shapeTilers;
std::unordered_set<TileID, TileID::Hash> staleTiles;
uint32_t nextID_ = 0;
};

View File

@@ -3,7 +3,8 @@
namespace mbgl {
LiveTileFeature::LiveTileFeature(FeatureType type_, GeometryCollection geometries_, std::map<std::string, std::string> properties_)
LiveTileFeature::LiveTileFeature(FeatureType type_, GeometryCollection geometries_,
std::unordered_map<std::string, std::string> properties_)
: type(type_),
properties(properties_),
geometries(geometries_) {}
@@ -41,6 +42,10 @@ void LiveTile::addLayer(const std::string& name, util::ptr<LiveTileLayer> layer)
layers.emplace(name, std::move(layer));
}
void LiveTile::removeLayer(const std::string& name) {
layers.erase(name);
}
util::ptr<GeometryTileLayer> LiveTile::getLayer(const std::string& name) const {
return getMutableLayer(name);
}

View File

@@ -2,6 +2,7 @@
#define MBGL_MAP_LIVE_TILE
#include <map>
#include <unordered_map>
#include <mbgl/map/geometry_tile.hpp>
@@ -9,7 +10,8 @@ namespace mbgl {
class LiveTileFeature : public GeometryTileFeature {
public:
LiveTileFeature(FeatureType, GeometryCollection, std::map<std::string, std::string> properties = {{}});
LiveTileFeature(FeatureType, GeometryCollection,
std::unordered_map<std::string, std::string> properties = {{}});
FeatureType getType() const override { return type; }
mapbox::util::optional<Value> getValue(const std::string&) const override;
@@ -17,7 +19,7 @@ public:
private:
FeatureType type = FeatureType::Unknown;
std::map<std::string, std::string> properties;
std::unordered_map<std::string, std::string> properties;
GeometryCollection geometries;
};
@@ -40,6 +42,7 @@ public:
LiveTile();
void addLayer(const std::string&, util::ptr<LiveTileLayer>);
void removeLayer(const std::string&);
util::ptr<GeometryTileLayer> getLayer(const std::string&) const override;
util::ptr<LiveTileLayer> getMutableLayer(const std::string&) const;

View File

@@ -6,6 +6,8 @@
#include <mbgl/util/projection.hpp>
#include <mbgl/util/thread.hpp>
#include <unordered_map>
namespace mbgl {
Map::Map(View& view, FileSource& fileSource, MapMode mode)
@@ -239,8 +241,22 @@ uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol)
return addPointAnnotations({ point }, { symbol }).front();
}
std::vector<uint32_t> Map::addPointAnnotations(const std::vector<LatLng>& points, const std::vector<std::string>& symbols) {
auto result = data->annotationManager.addPointAnnotations(points, symbols, *data);
AnnotationIDs Map::addPointAnnotations(const AnnotationSegment& points,
const std::vector<std::string>& symbols) {
AnnotationsProperties properties = { { "symbols", symbols } };
auto result = data->annotationManager.addPointAnnotations(points, properties, *data);
context->invoke(&MapContext::updateAnnotationTiles, result.first);
return result.second;
}
uint32_t Map::addShapeAnnotation(const AnnotationSegments& shape,
const StyleProperties& styleProperties) {
return addShapeAnnotations({ shape }, { styleProperties }).front();
}
AnnotationIDs Map::addShapeAnnotations(const std::vector<AnnotationSegments>& shapes,
const std::vector<StyleProperties>& styleProperties) {
auto result = data->annotationManager.addShapeAnnotations(shapes, styleProperties, {{}}, *data);
context->invoke(&MapContext::updateAnnotationTiles, result.first);
return result.second;
}
@@ -254,8 +270,8 @@ void Map::removeAnnotations(const std::vector<uint32_t>& annotations) {
context->invoke(&MapContext::updateAnnotationTiles, result);
}
std::vector<uint32_t> Map::getAnnotationsInBounds(const LatLngBounds& bounds) {
return data->annotationManager.getAnnotationsInBounds(bounds, *data);
std::vector<uint32_t> Map::getAnnotationsInBounds(const LatLngBounds& bounds, const AnnotationType& type) {
return data->annotationManager.getAnnotationsInBounds(bounds, *data, type);
}
LatLngBounds Map::getBoundsForAnnotations(const std::vector<uint32_t>& annotations) {

View File

@@ -3,6 +3,7 @@
#include <mbgl/map/view.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/still_image.hpp>
#include <mbgl/map/annotation.hpp>
#include <mbgl/platform/log.hpp>
@@ -12,12 +13,16 @@
#include <mbgl/storage/response.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/style_bucket.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <mbgl/util/worker.hpp>
#include <mbgl/util/texture_pool.hpp>
#include <mbgl/util/exception.hpp>
#include <algorithm>
namespace mbgl {
MapContext::MapContext(uv_loop_t* loop, View& view_, FileSource& fileSource, MapData& data_)
@@ -112,6 +117,11 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base)
style->setObserver(this);
triggerUpdate(Update::Zoom);
auto staleTiles = data.annotationManager.resetStaleTiles();
if (staleTiles.size()) {
updateAnnotationTiles(staleTiles);
}
}
void MapContext::updateTiles() {
@@ -120,15 +130,111 @@ void MapContext::updateTiles() {
style->update(data, transformState, *texturePool);
}
void MapContext::updateAnnotationTiles(const std::vector<TileID>& ids) {
void MapContext::updateAnnotationTiles(const std::unordered_set<TileID, TileID::Hash>& ids) {
assert(Environment::currentlyOn(ThreadType::Map));
data.annotationManager.markStaleTiles(ids);
if (!style) return;
// grab existing, single shape annotations source
const auto& shapeID = AnnotationManager::ShapeLayerID;
const auto source_it = std::find_if(style->sources.begin(), style->sources.end(),
[&shapeID](util::ptr<Source> source) {
return (source->info.source_id == shapeID);
});
assert(source_it != style->sources.end());
source_it->get()->enabled = true;
// create (if necessary) layers and buckets for each shape
for (const auto &shapeAnnotationID : data.annotationManager.getOrderedShapeAnnotations()) {
const std::string shapeLayerID = shapeID + "." + std::to_string(shapeAnnotationID);
const auto layer_it = std::find_if(style->layers.begin(), style->layers.end(),
[&shapeLayerID](util::ptr<StyleLayer> layer) {
return (layer->id == shapeLayerID);
});
if (layer_it == style->layers.end()) {
// query shape styling
auto& shapeStyle = data.annotationManager.getAnnotationStyleProperties(shapeAnnotationID);
// apply shape paint properties
ClassProperties paintProperties;
if (shapeStyle.is<LineProperties>()) {
// opacity
PropertyValue lineOpacity = ConstantFunction<float>(shapeStyle.get<LineProperties>().opacity);
paintProperties.set(PropertyKey::LineOpacity, lineOpacity);
// line width
PropertyValue lineWidth = ConstantFunction<float>(shapeStyle.get<LineProperties>().width);
paintProperties.set(PropertyKey::LineWidth, lineWidth);
// stroke color
PropertyValue strokeColor = ConstantFunction<Color>(shapeStyle.get<LineProperties>().color);
paintProperties.set(PropertyKey::LineColor, strokeColor);
} else if (shapeStyle.is<FillProperties>()) {
// opacity
PropertyValue fillOpacity = ConstantFunction<float>(shapeStyle.get<FillProperties>().opacity);
paintProperties.set(PropertyKey::FillOpacity, fillOpacity);
// fill color
PropertyValue fillColor = ConstantFunction<Color>(shapeStyle.get<FillProperties>().fill_color);
paintProperties.set(PropertyKey::FillColor, fillColor);
// stroke color
PropertyValue strokeColor = ConstantFunction<Color>(shapeStyle.get<FillProperties>().stroke_color);
paintProperties.set(PropertyKey::FillOutlineColor, strokeColor);
}
std::map<ClassID, ClassProperties> shapePaints;
shapePaints.emplace(ClassID::Default, std::move(paintProperties));
// create shape layer
util::ptr<StyleLayer> shapeLayer = std::make_shared<StyleLayer>(shapeLayerID, std::move(shapePaints));
shapeLayer->type = (shapeStyle.is<LineProperties>() ? StyleLayerType::Line : StyleLayerType::Fill);
// add to end of other shape layers just before (last) point layer
style->layers.emplace((style->layers.end() - 2), shapeLayer);
// create shape bucket & connect to source
util::ptr<StyleBucket> shapeBucket = std::make_shared<StyleBucket>(shapeLayer->type);
shapeBucket->name = shapeLayer->id;
shapeBucket->source_layer = shapeLayer->id;
shapeBucket->source = *source_it;
// apply line layout properties to bucket
if (shapeStyle.is<LineProperties>()) {
shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction<JoinType>(JoinType::Round));
}
// connect layer to bucket
shapeLayer->bucket = shapeBucket;
}
}
// invalidate annotations layer tiles
for (const auto &source : style->sources) {
if (source->info.type == SourceType::Annotations) {
source->invalidateTiles(ids);
}
}
triggerUpdate();
cascadeClasses();
triggerUpdate(Update::Classes);
data.annotationManager.resetStaleTiles();
}
void MapContext::cascadeClasses() {
style->cascade(data.getClasses());
}
void MapContext::recalculateStyle(TimePoint now) {
style->recalculate(transformState.getNormalizedZoom(), now);
}
void MapContext::update() {
@@ -146,12 +252,12 @@ void MapContext::update() {
}
if (updated & static_cast<UpdateType>(Update::Classes)) {
style->cascade(data.getClasses());
cascadeClasses();
}
if (updated & static_cast<UpdateType>(Update::Classes) ||
updated & static_cast<UpdateType>(Update::Zoom)) {
style->recalculate(transformState.getNormalizedZoom(), now);
recalculateStyle(now);
}
updateTiles();

View File

@@ -49,7 +49,7 @@ public:
std::string getStyleJSON() const { return styleJSON; }
double getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol);
void updateAnnotationTiles(const std::vector<TileID>&);
void updateAnnotationTiles(const std::unordered_set<TileID, TileID::Hash>&);
void setSourceTileCacheSize(size_t size);
void onLowMemory();
@@ -63,6 +63,10 @@ public:
private:
void updateTiles();
// Style-related updates.
void cascadeClasses();
void recalculateStyle(TimePoint);
// Update the state indicated by the accumulated Update flags, then render.
void update();

View File

@@ -521,11 +521,16 @@ bool Source::update(MapData& data,
return allTilesUpdated;
}
void Source::invalidateTiles(const std::vector<TileID>& ids) {
void Source::invalidateTiles(const std::unordered_set<TileID, TileID::Hash>& ids) {
cache.clear();
for (auto& id : ids) {
tiles.erase(id);
tile_data.erase(id);
if (ids.size()) {
for (auto& id : ids) {
tiles.erase(id);
tile_data.erase(id);
}
} else {
tiles.clear();
tile_data.clear();
}
updateTilePtrs();
}

View File

@@ -17,6 +17,7 @@
#include <forward_list>
#include <iosfwd>
#include <map>
#include <unordered_set>
namespace mbgl {
@@ -46,6 +47,7 @@ public:
std::string attribution;
std::array<float, 3> center = {{0, 0, 0}};
std::array<float, 4> bounds = {{-180, -90, 180, 90}};
std::string source_id = "";
void parseTileJSONProperties(const rapidjson::Value&);
std::string tileURL(const TileID& id, float pixelRatio) const;
@@ -86,7 +88,7 @@ public:
TexturePool&,
bool shouldReparsePartialTiles);
void invalidateTiles(const std::vector<TileID>&);
void invalidateTiles(const std::unordered_set<TileID, TileID::Hash>&);
void updateMatrices(const mat4 &projMatrix, const TransformState &transform);
void drawClippingMasks(Painter &painter);

View File

@@ -36,20 +36,30 @@ void StyleParser::parse(JSVal document) {
if (document.HasMember("layers")) {
parseLayers(document["layers"]);
// create shape annotations source
const std::string& shapeID = AnnotationManager::ShapeLayerID;
util::ptr<Source> shapeAnnotationsSource = std::make_shared<Source>();
sourcesMap.emplace(shapeID, shapeAnnotationsSource);
sources.emplace_back(shapeAnnotationsSource);
shapeAnnotationsSource->info.type = SourceType::Annotations;
shapeAnnotationsSource->info.source_id = shapeID;
// create point annotations layer
//
const std::string& id = AnnotationManager::layerID;
const std::string& pointID = AnnotationManager::PointLayerID;
std::map<ClassID, ClassProperties> paints;
util::ptr<StyleLayer> annotations = std::make_shared<StyleLayer>(id, std::move(paints));
annotations->type = StyleLayerType::Symbol;
layersMap.emplace(id, std::pair<JSVal, util::ptr<StyleLayer>> { JSVal(id), annotations });
layers.emplace_back(annotations);
std::map<ClassID, ClassProperties> pointPaints;
util::ptr<StyleLayer> pointAnnotationsLayer = std::make_shared<StyleLayer>(pointID, std::move(pointPaints));
pointAnnotationsLayer->type = StyleLayerType::Symbol;
layersMap.emplace(pointID, std::pair<JSVal, util::ptr<StyleLayer>> { JSVal(pointID), pointAnnotationsLayer });
layers.emplace_back(pointAnnotationsLayer);
util::ptr<StyleBucket> pointBucket = std::make_shared<StyleBucket>(annotations->type);
pointBucket->name = annotations->id;
pointBucket->source_layer = annotations->id;
// create point annotations symbol bucket
util::ptr<StyleBucket> pointBucket = std::make_shared<StyleBucket>(pointAnnotationsLayer->type);
pointBucket->name = pointAnnotationsLayer->id;
pointBucket->source_layer = pointAnnotationsLayer->id;
// build up point annotations style
rapidjson::Document d;
rapidjson::Value iconImage(rapidjson::kObjectType);
iconImage.AddMember("icon-image", "{sprite}", d.GetAllocator());
@@ -58,14 +68,14 @@ void StyleParser::parse(JSVal document) {
iconOverlap.AddMember("icon-allow-overlap", true, d.GetAllocator());
parseLayout(iconOverlap, pointBucket);
util::ptr<Source> source = std::make_shared<Source>();
sourcesMap.emplace(id, source);
sources.emplace_back(source);
source->info.type = SourceType::Annotations;
pointBucket->source = source;
annotations->bucket = pointBucket;
//
// end point annotations
// create point annotations source & connect to bucket & layer
util::ptr<Source> pointAnnotationsSource = std::make_shared<Source>();
sourcesMap.emplace(pointID, pointAnnotationsSource);
sources.emplace_back(pointAnnotationsSource);
pointAnnotationsSource->info.type = SourceType::Annotations;
pointAnnotationsSource->info.source_id = pointID;
pointBucket->source = pointAnnotationsSource;
pointAnnotationsLayer->bucket = pointBucket;
}
if (document.HasMember("sprite")) {

View File

@@ -1,4 +1,5 @@
#include <mbgl/style/style_properties.hpp>
#include <mbgl/style/piecewisefunction_properties.hpp>
namespace mbgl {