Support extra annotations for snapshotter for iOS (#3963)

Co-authored-by: Wei Chen <wei.chen@grabtaxi.com>
Co-authored-by: Stefan Karschti <4198736+stefankarschti@users.noreply.github.com>
This commit is contained in:
Wei Chen
2025-11-25 18:20:38 +08:00
committed by GitHub
parent 5a0c5f9f97
commit 7f81af1d0b
6 changed files with 300 additions and 1 deletions

View File

@@ -6,7 +6,13 @@
NS_ASSUME_NONNULL_BEGIN
@class MLNAnnotationImage;
@class MLNShape;
@class MLNPolyline;
@class MLNPolygon;
@protocol MLNMapSnapshotterDelegate;
@protocol MLNAnnotation;
/**
An overlay that is placed within a ``MLNMapSnapshot``.
@@ -249,6 +255,35 @@ MLN_EXPORT
*/
- (instancetype)initWithOptions:(MLNMapSnapshotOptions *)options NS_DESIGNATED_INITIALIZER;
/**
Adds an annotation to the snapshotter.
> Note: ``MLNMultiPolyline``, ``MLNMultiPolyline``, ``MLNMultiPolyline``, and
``MLNPointCollection`` objects cannot be added to the snapshotter at this time.
Any multipoint, multipolyline, multipolygon, shape or point collection
object that is specified is silently ignored.
@param annotation The annotation object to add to the receiver. This object
must conform to the ``MLNAnnotation`` protocol. The snapshotter retains the
annotation object.
*/
- (void)addAnnotation:(id<MLNAnnotation>)annotation;
/**
Adds an array of annotations to the snapshotter.
> Note: ``MLNMultiPolyline``, ``MLNMultiPolyline``, and ``MLNMultiPolyline`` objects
cannot be added to the snapshotter at this time. Nor can ``MLNMultiPoint``
objects that are not instances of ``MLNPolyline`` or ``MLNPolyline``. Any
multipoint, multipolyline, multipolygon, or shape collection objects that
are specified are silently ignored.
@param annotations An array of annotation objects. Each object in the array
must conform to the ``MLNAnnotation`` protocol. The snapshotter retains each
individual annotation object.
*/
- (void)addAnnotations:(NSArray<id<MLNAnnotation>> *)annotations;
/**
Starts the snapshot creation and executes the specified block with the result.
@@ -367,6 +402,89 @@ MLN_EXPORT
- (void)mapSnapshotter:(MLNMapSnapshotter *)snapshotter didFailLoadingImageNamed:(NSString *)name;
/**
Returns an annotation image object to mark the given point annotation object on
the map.
Implement this method to mark a point annotation with a static image.
Static annotation images use less memory and draw more quickly than annotation
views. On the other hand, annotation views are compatible with UIKit, Core
Animation, and other Cocoa Touch frameworks.
@param snapshotter The snapshotter that has just loaded a style.
@param annotation The object representing the annotation that is about to be
displayed.
@return The annotation image object to display for the given annotation or
`nil` if you want to display the default marker image or an annotation view.
*/
- (nullable MLNAnnotationImage *)mapSnapshotter:(MLNMapSnapshotter *)snapshotter
imageForAnnotation:(id<MLNAnnotation>)annotation;
/**
Returns the alpha value to use when rendering a shape annotation.
A value of `0.0` results in a completely transparent shape. A value of `1.0`,
the default, results in a completely opaque shape.
This method sets the opacity of an entire shape, inclusive of its stroke and
fill. To independently set the values for stroke or fill, specify an alpha
component in the color returned by `-mapSnapshotter:strokeColorForShapeAnnotation:` or
`-mapSnapshotter:fillColorForPolygonAnnotation:`.
@param snapshotter The snapshotter that has just loaded a style.
@param annotation The annotation being rendered.
@return An alpha value between `0` and `1.0`.
*/
- (CGFloat)mapSnapshotter:(MLNMapSnapshotter *)snapshotter
alphaForShapeAnnotation:(MLNShape *)annotation;
/**
Returns the color to use when rendering the outline of a shape annotation.
The default stroke color is MLNColor.whiteColor.
Opacity may be set by specifying an alpha component. The default alpha value is
`1.0` and results in a completely opaque stroke.
@param snapshotter The snapshotter that has just loaded a style.
@param annotation The annotation being rendered.
@return A color to use for the shape outline.
*/
- (MLNColor *)mapSnapshotter:(MLNMapSnapshotter *)snapshotter
strokeColorForShapeAnnotation:(MLNShape *)annotation;
/**
Returns the color to use when rendering the fill of a polygon annotation.
The default fill color is the MLNColor.whiteColor.
Opacity may be set by specifying an alpha component. The default alpha value is
`1.0` and results in a completely opaque shape.
@param snapshotter The snapshotter that has just loaded a style.
@param annotation The annotation being rendered.
@return The polygon’s interior fill color.
*/
- (MLNColor *)mapSnapshotter:(MLNMapSnapshotter *)snapshotter
fillColorForPolygonAnnotation:(MLNPolygon *)annotation;
/**
Returns the line width in points to use when rendering the outline of a
polyline annotation.
By default, the polyline is outlined with a line `3.0` points wide.
@param snapshotter The snapshotter that has just loaded a style.
@param annotation The annotation being rendered.
@return A line width for the polyline, measured in points.
*/
- (CGFloat)mapSnapshotter:(MLNMapSnapshotter *)snapshotter
lineWidthForPolylineAnnotation:(MLNPolyline *)annotation;
@end
NS_ASSUME_NONNULL_END

View File

@@ -18,11 +18,20 @@
#import "MLNRendererConfiguration.h"
#import "MLNMapSnapshotter_Private.h"
#import "MLNSettings_Private.h"
#import "MLNShape.h"
#import "MLNPolyline.h"
#import "MLNPolygon.h"
#import "MLNMultiPoint_Private.h"
#import "MLNShapeCollection.h"
#import "MLNPointCollection.h"
#import "MLNAnnotationImage_Private.h"
#if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h"
#import "UIColor+MLNAdditions.h"
#else
#import "NSImage+MLNAdditions.h"
#import "NSColor+MLNAdditions.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreImage/CIContext.h>
#import <CoreImage/CIFilter.h>
@@ -33,6 +42,7 @@
const CGPoint MLNLogoImagePosition = CGPointMake(8, 8);
const CGFloat MLNSnapshotterMinimumPixelSize = 64;
NSString * const MLNSnapshotterAnnotationSpritePrefix = @"org.maplibre.sprites.";
MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOptions *options, MLNMapSnapshotOverlayHandler overlayHandler);
MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError);
@@ -224,7 +234,7 @@ private:
@end
@interface MLNMapSnapshotter()
@interface MLNMapSnapshotter() <MLNMultiPointDelegate>
@property (nonatomic) BOOL loading;
@property (nonatomic) BOOL terminated;
@end
@@ -232,6 +242,8 @@ private:
@implementation MLNMapSnapshotter {
std::unique_ptr<mbgl::MapSnapshotter> _mbglMapSnapshotter;
std::unique_ptr<MLNMapSnapshotterDelegateHost> _delegateHost;
NSMutableArray<id<MLNAnnotation>> *_annotationsToInstall;
NSMutableSet<NSString *> *_installedImages;
}
- (void)dealloc {
@@ -251,6 +263,7 @@ private:
self = [super init];
if (self) {
self.options = options;
_installedImages = [NSMutableSet set];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
#else
@@ -266,6 +279,79 @@ private:
self.terminated = YES;
}
- (void)addAnnotation:(id<MLNAnnotation>)annotation
{
MLNLogDebug(@"Adding annotation: %@", annotation);
if ( ! annotation) return;
return [self addAnnotations:@[annotation]];
}
- (void)addAnnotations:(NSArray<id<MLNAnnotation>> *)annotations {
MLNLogDebug(@"Adding: %lu annotations", annotations.count);
if (!_annotationsToInstall) {
_annotationsToInstall = [NSMutableArray array];
}
[_annotationsToInstall addObjectsFromArray:annotations];
}
- (void)installAnnotations:(NSArray<id<MLNAnnotation>> *)annotations
{
MLNLogDebug(@"install: %lu annotations", annotations.count);
if ( ! annotations) return;
for (id <MLNAnnotation> annotation in annotations)
{
MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation");
if ([annotation isKindOfClass:[MLNMultiPoint class]])
{
MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation;
if (!multiPoint.pointCount) {
continue;
}
_mbglMapSnapshotter->addAnnotation([multiPoint annotationObjectWithDelegate:self]);
} else if (! [annotation isKindOfClass:[MLNMultiPolyline class]]
&& ![annotation isKindOfClass:[MLNMultiPolygon class]]
&& ![annotation isKindOfClass:[MLNShapeCollection class]]
&& ![annotation isKindOfClass:[MLNPointCollection class]]) {
NSString *symbolName;
NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
MLNAnnotationImage *annotationImage;
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:imageForAnnotation:)]) {
annotationImage = [self.delegate mapSnapshotter:self imageForAnnotation:annotation];
}
if (!annotationImage) {
// doesn't support no icon point for snapshotter
continue;
}
symbolName = annotationImage.styleIconIdentifier;
if ( ! symbolName)
{
symbolName = [MLNSnapshotterAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
annotationImage.styleIconIdentifier = symbolName;
}
[self installAnnotationImage:annotationImage];
_mbglMapSnapshotter->addAnnotation(mbgl::SymbolAnnotation {
MLNPointFromLocationCoordinate2D(annotation.coordinate),
symbolName.UTF8String
});
}
}
}
- (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage {
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
if (![_installedImages containsObject:iconIdentifier]) {
_mbglMapSnapshotter->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]);
}
}
- (void)startWithCompletionHandler:(MLNMapSnapshotCompletionHandler)completion
{
MLNLogDebug(@"Starting withCompletionHandler: %@", completion);
@@ -749,6 +835,11 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
if (!MLNCoordinateBoundsIsEmpty(options.coordinateBounds)) {
_mbglMapSnapshotter->setRegion(MLNLatLngBoundsFromCoordinateBounds(options.coordinateBounds));
}
// Annotations
if (_annotationsToInstall) {
[self installAnnotations:_annotationsToInstall];
}
}
- (MLNStyle *)style {
@@ -758,4 +849,40 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return [[MLNStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self];
}
- (double)alphaForShapeAnnotation:(MLNShape *)annotation
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:alphaForShapeAnnotation:)]) {
return [self.delegate mapSnapshotter:self alphaForShapeAnnotation:annotation];
}
return 1.0;
}
- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:strokeColorForShapeAnnotation:)]) {
MLNColor *color = [self.delegate mapSnapshotter:self strokeColorForShapeAnnotation:annotation];
return color.mgl_color;
}
return mbgl::Color::white();
}
- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:fillColorForPolygonAnnotation:)]){
MLNColor *color = [self.delegate mapSnapshotter:self fillColorForPolygonAnnotation:annotation];
return color.mgl_color;
}
return mbgl::Color::white();
}
- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:lineWidthForPolylineAnnotation:)]) {
return [self.delegate mapSnapshotter:self lineWidthForPolylineAnnotation:annotation];
}
return 3.0;
}
@end

View File

@@ -3,6 +3,8 @@
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/annotation/annotation.hpp>
#include <mbgl/style/image.hpp>
#include <exception>
#include <memory>
@@ -63,6 +65,9 @@ public:
void setPadding(const mbgl::EdgeInsets&);
mbgl::EdgeInsets getPadding() const;
void addAnnotationImage(std::unique_ptr<style::Image>);
void addAnnotation(const Annotation&);
style::Style& getStyle();
const style::Style& getStyle() const;

View File

@@ -210,6 +210,10 @@ public:
mbgl::EdgeInsets getPadding() const { return regionInsets; }
void addAnnotationImage(std::unique_ptr<style::Image> image) { map.addAnnotationImage(std::move(image)); }
void addAnnotation(const Annotation& annotation) { map.addAnnotation(annotation); }
void snapshot(MapSnapshotter::Callback callback) {
if (!callback) {
Log::Error(Event::General, "MapSnapshotter::Callback is not set");
@@ -366,6 +370,14 @@ mbgl::EdgeInsets MapSnapshotter::getPadding() const {
return impl->getPadding();
}
void MapSnapshotter::addAnnotationImage(std::unique_ptr<style::Image> image) {
impl->addAnnotationImage(std::move(image));
}
void MapSnapshotter::addAnnotation(const Annotation& annotation) {
impl->addAnnotation(annotation);
}
style::Style& MapSnapshotter::getStyle() {
return impl->getMap().getStyle();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -7,9 +7,18 @@
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/timer.hpp>
#include <mbgl/util/io.hpp>
using namespace mbgl;
static PremultipliedImage namedImage(const std::string& name) {
return decodeImage(util::read_file("test/fixtures/sprites/" + name + ".png"));
}
static std::unique_ptr<style::Image> namedMarker(const std::string& name) {
return std::make_unique<style::Image>(name, namedImage(name), 1.0f);
}
class SnapshotterObserver final : public MapSnapshotterObserver {
public:
~SnapshotterObserver() = default;
@@ -137,3 +146,31 @@ TEST(MapSnapshotter, TEST_REQUIRES_SERVER(runtimeStyling)) {
runLoop.run();
}
TEST(MapSnapshotter, TEST_REQUIRES_SERVER(annotation)) {
util::RunLoop runLoop;
MapSnapshotter snapshotter(Size{256, 128}, 1.0f, ResourceOptions(), ClientOptions());
snapshotter.setStyleURL("http://127.0.0.1:3000/online/style.json");
snapshotter.addAnnotationImage(namedMarker("default_marker"));
snapshotter.addAnnotation(SymbolAnnotation{Point<double>{0, 0}, "default_marker"});
FillAnnotation fillAnnotation{Polygon<double>{{{{0, 0}, {0, 45}, {45, 45}, {45, 0}}}}};
fillAnnotation.color = Color::red();
snapshotter.addAnnotation(fillAnnotation);
LineAnnotation lineAnnotation{LineString<double>{{{0, 0}, {45, 45}, {30, 0}}}};
lineAnnotation.color = Color::blue();
lineAnnotation.width = {5};
snapshotter.addAnnotation(lineAnnotation);
snapshotter.snapshot([&runLoop](std::exception_ptr ptr,
mbgl::PremultipliedImage image,
mbgl::MapSnapshotter::Attributions,
mbgl::MapSnapshotter::PointForFn,
mbgl::MapSnapshotter::LatLngForFn) {
EXPECT_EQ(nullptr, ptr);
EXPECT_EQ(256, image.size.width);
EXPECT_EQ(128, image.size.height);
test::checkImage("test/fixtures/map/map_snapshotter_annotation", image, 0.005, 0.1);
runLoop.stop();
});
runLoop.run();
}