mirror of
https://github.com/status-im/react-native-mapbox-gl.git
synced 2026-08-30 12:31:11 +00:00
746 lines
30 KiB
Objective-C
746 lines
30 KiB
Objective-C
//
|
|
// RCTMapboxGLManager.m
|
|
// RCTMapboxGL
|
|
//
|
|
// Created by Bobby Sudekum on 4/30/15.
|
|
// Copyright (c) 2015 Mapbox. All rights reserved.
|
|
//
|
|
|
|
#import "RCTMapboxGLManager.h"
|
|
#import "RCTMapboxGL.h"
|
|
#import <Mapbox/Mapbox.h>
|
|
#import <React/RCTConvert+CoreLocation.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/UIView+React.h>
|
|
#import <React/RCTUIManager.h>
|
|
#import "RCTMapboxGLConversions.h"
|
|
#import "MGLPolygon+RCTAdditions.h"
|
|
#import "MGLPolyline+RCTAdditions.h"
|
|
|
|
@implementation RCTMapboxGLManager
|
|
|
|
- (UIView *)view
|
|
{
|
|
return [[RCTMapboxGL alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return _bridge.uiManager.methodQueue;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE();
|
|
|
|
// Props
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(initialCenterCoordinate, CLLocationCoordinate2D);
|
|
RCT_EXPORT_VIEW_PROPERTY(initialZoomLevel, double);
|
|
RCT_EXPORT_VIEW_PROPERTY(initialDirection, double);
|
|
RCT_EXPORT_VIEW_PROPERTY(clipsToBounds, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(debugActive, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(minimumZoomLevel, double);
|
|
RCT_EXPORT_VIEW_PROPERTY(maximumZoomLevel, double);
|
|
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(styleURL, NSURL);
|
|
RCT_EXPORT_VIEW_PROPERTY(userTrackingMode, int);
|
|
RCT_EXPORT_VIEW_PROPERTY(attributionButtonIsHidden, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(logoIsHidden, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(compassIsHidden, BOOL);
|
|
RCT_EXPORT_VIEW_PROPERTY(userLocationVerticalAlignment, int);
|
|
RCT_EXPORT_VIEW_PROPERTY(annotationsPopUpEnabled, BOOL);
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onRegionDidChange, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onRegionWillChange, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onChangeUserTrackingMode, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onOpenAnnotation, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onCloseAnnotation, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onRightAnnotationTapped, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onUpdateUserLocation, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onTap, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onFinishLoadingMap, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onStartLoadingMap, RCTDirectEventBlock);
|
|
RCT_EXPORT_VIEW_PROPERTY(onLocateUserFailed, RCTDirectEventBlock);
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(contentInset, UIEdgeInsetsMake, RCTMapboxGL)
|
|
{
|
|
int top = [json[0] doubleValue];
|
|
int left = [json[3] doubleValue];
|
|
int bottom = [json[2] doubleValue];
|
|
int right = [json[1] doubleValue];
|
|
UIEdgeInsets inset = UIEdgeInsetsMake(top, left, bottom, right);
|
|
view.contentInset = inset;
|
|
}
|
|
|
|
// Constants
|
|
|
|
- (NSDictionary *)constantsToExport
|
|
{
|
|
return @{
|
|
@"mapStyles": @{
|
|
@"light": [[MGLStyle lightStyleURL] absoluteString],
|
|
@"dark": [[MGLStyle darkStyleURL] absoluteString],
|
|
@"streets": [[MGLStyle streetsStyleURL] absoluteString],
|
|
@"emerald": [[MGLStyle emeraldStyleURL] absoluteString],
|
|
@"satellite": [[MGLStyle satelliteStyleURL] absoluteString],
|
|
@"hybrid": [[MGLStyle hybridStyleURL] absoluteString],
|
|
},
|
|
@"userTrackingMode": @{
|
|
@"none": [NSNumber numberWithUnsignedInt:MGLUserTrackingModeNone],
|
|
@"follow": [NSNumber numberWithUnsignedInt:MGLUserTrackingModeFollow],
|
|
@"followWithCourse": [NSNumber numberWithUnsignedInt:MGLUserTrackingModeFollowWithCourse],
|
|
@"followWithHeading": [NSNumber numberWithUnsignedInt:MGLUserTrackingModeFollowWithHeading]
|
|
},
|
|
@"userLocationVerticalAlignment" : @{
|
|
@"top": @(MGLAnnotationVerticalAlignmentTop),
|
|
@"center": @(MGLAnnotationVerticalAlignmentCenter),
|
|
@"bottom": @(MGLAnnotationVerticalAlignmentBottom)
|
|
},
|
|
@"unknownResourceCount": @(UINT64_MAX),
|
|
@"metricsEnabled": @([RCTMapboxGLManager metricsEnabled])
|
|
};
|
|
};
|
|
|
|
// Metrics
|
|
|
|
+ (BOOL)metricsEnabled
|
|
{
|
|
NSUserDefaults * ud = [NSUserDefaults standardUserDefaults];
|
|
NSNumber * nr = [ud valueForKey:@"MGLMapboxMetricsEnabled"];
|
|
if (!nr || ![nr isKindOfClass:[NSNumber class]]) {
|
|
return YES;
|
|
}
|
|
return nr.boolValue;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setMetricsEnabled:(BOOL)enabled)
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"MGLMapboxMetricsEnabled"];
|
|
}
|
|
|
|
// Access token
|
|
|
|
RCT_EXPORT_METHOD(setAccessToken:(nonnull NSString *)accessToken
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (!accessToken || ![accessToken length] || [accessToken isEqual:@"your-mapbox.com-access-token"]) {
|
|
reject(nil, @"Mapbox api token is not valid.", nil);
|
|
return;
|
|
}
|
|
[MGLAccountManager setAccessToken:accessToken];
|
|
resolve(nil);
|
|
});
|
|
}
|
|
|
|
// Offline
|
|
|
|
- (id)init
|
|
{
|
|
if (!(self = [super init])) { return nil; }
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[MGLOfflineStorage sharedOfflineStorage] removeObserver:self forKeyPath:@"packs"];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
- (void)offlinePacksDidFinishLoading
|
|
{
|
|
_loadedPacks = YES;
|
|
|
|
NSArray * packs = [MGLOfflineStorage sharedOfflineStorage].packs;
|
|
|
|
if ([_packRequests count]) {
|
|
NSArray * callbackArray = [self serializePacksArray:packs];
|
|
for (RCTPromiseResolveBlock callback in _packRequests) {
|
|
callback(callbackArray);
|
|
}
|
|
[_packRequests removeAllObjects];
|
|
}
|
|
|
|
for (MGLOfflinePack * pack in packs) {
|
|
if (pack.state != MGLOfflinePackStateComplete) {
|
|
[pack resume];
|
|
}
|
|
}
|
|
[_bridge.eventDispatcher sendAppEventWithName:@"MapboxOfflinePacksLoaded" body:@{}];
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary *)change
|
|
context:(void *)context
|
|
{
|
|
NSNumber * changeKind = change[NSKeyValueChangeKindKey];
|
|
if (changeKind == [NSNull null]) { return; }
|
|
if ([changeKind integerValue] != NSKeyValueChangeSetting) { return; }
|
|
|
|
NSArray * packs = [[MGLOfflineStorage sharedOfflineStorage] packs];
|
|
|
|
if (!packs) { return; }
|
|
if (_loadedPacks) { return; }
|
|
|
|
[_loadingPacks addObjectsFromArray:packs];
|
|
|
|
for (MGLOfflinePack * pack in packs) {
|
|
[pack requestProgress];
|
|
}
|
|
|
|
if (!packs.count) {
|
|
[self offlinePacksDidFinishLoading];
|
|
}
|
|
}
|
|
|
|
- (void)firePackProgress:(MGLOfflinePack*)pack {
|
|
if (pack.state == MGLOfflinePackStateInvalid) {
|
|
return;
|
|
}
|
|
NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
|
|
MGLOfflinePackProgress progress = pack.progress;
|
|
|
|
NSDictionary *event = @{ @"name": userInfo[@"name"],
|
|
@"metadata": userInfo[@"metadata"],
|
|
@"countOfResourcesCompleted": @(progress.countOfResourcesCompleted),
|
|
@"countOfResourcesExpected": @(progress.countOfResourcesExpected),
|
|
@"countOfBytesCompleted": @(progress.countOfBytesCompleted),
|
|
@"maximumResourcesExpected": @(progress.maximumResourcesExpected) };
|
|
|
|
[_bridge.eventDispatcher sendAppEventWithName:@"MapboxOfflineProgressDidChange" body:event];
|
|
|
|
[_recentPacks addObject:pack];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, _throttleInterval * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
|
[_recentPacks removeObject:pack];
|
|
if ([_throttledPacks containsObject:pack]) {
|
|
[_throttledPacks removeObject:pack];
|
|
[self firePackProgress:pack];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)flushThrottleForPack:(MGLOfflinePack*)pack {
|
|
if ([_throttledPacks containsObject:pack]) {
|
|
[_throttledPacks removeObject:pack];
|
|
[self firePackProgress:pack];
|
|
}
|
|
}
|
|
|
|
- (void)discardThrottleForPack:(MGLOfflinePack*)pack {
|
|
if ([_throttledPacks containsObject:pack]) {
|
|
[_throttledPacks removeObject:pack];
|
|
}
|
|
}
|
|
|
|
- (void)offlinePackProgressDidChange:(NSNotification *)notification {
|
|
MGLOfflinePack *pack = notification.object;
|
|
|
|
if (!_loadedPacks && [_loadingPacks containsObject:pack]) {
|
|
[_loadingPacks removeObject:pack];
|
|
if ([_loadingPacks count] == 0) {
|
|
[self offlinePacksDidFinishLoading];
|
|
}
|
|
}
|
|
|
|
if ([_removedPacks containsObject:pack]) {
|
|
return;
|
|
}
|
|
|
|
if ([_recentPacks containsObject:pack]) {
|
|
[_throttledPacks addObject:pack];
|
|
return;
|
|
}
|
|
|
|
[self firePackProgress:pack];
|
|
}
|
|
|
|
- (void)offlinePackDidReceiveMaximumAllowedMapboxTiles:(NSNotification *)notification {
|
|
MGLOfflinePack *pack = notification.object;
|
|
[self flushThrottleForPack:pack];
|
|
NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
|
|
uint64_t maximumCount = [notification.userInfo[MGLOfflinePackMaximumCountUserInfoKey] unsignedLongLongValue];
|
|
|
|
NSDictionary *event = @{ @"name": userInfo[@"name"],
|
|
@"maxTiles": @(maximumCount) };
|
|
|
|
[_bridge.eventDispatcher sendAppEventWithName:@"MapboxOfflineMaxAllowedTiles" body:event];
|
|
}
|
|
|
|
- (void)offlinePackDidReceiveError:(NSNotification *)notification {
|
|
MGLOfflinePack *pack = notification.object;
|
|
[self flushThrottleForPack:pack];
|
|
NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
|
|
NSError *error = notification.userInfo[MGLOfflinePackErrorUserInfoKey];
|
|
|
|
NSDictionary *event = @{ @"name": userInfo[@"name"],
|
|
@"error": [error localizedDescription] };
|
|
|
|
[_bridge.eventDispatcher sendAppEventWithName:@"MapboxOfflineError" body:event];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(initializeOfflinePacks)
|
|
{
|
|
_recentPacks = [NSMutableSet new];
|
|
_throttledPacks = [NSMutableSet new];
|
|
_removedPacks = [NSMutableSet new];
|
|
_throttleInterval = 300;
|
|
|
|
_loadingPacks = [NSMutableSet new];
|
|
_loadedPacks = NO;
|
|
|
|
// Setup pack array loading notifications
|
|
[[MGLOfflineStorage sharedOfflineStorage] addObserver:self forKeyPath:@"packs" options:NSKeyValueObservingOptionInitial context:NULL];
|
|
_packRequests = [NSMutableArray new];
|
|
|
|
// Setup offline pack notification handlers.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackProgressDidChange:) name:MGLOfflinePackProgressChangedNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveError:) name:MGLOfflinePackErrorNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveMaximumAllowedMapboxTiles:) name:MGLOfflinePackMaximumMapboxTilesReachedNotification object:nil];
|
|
}
|
|
|
|
RCT_REMAP_METHOD(addOfflinePack,
|
|
pack:(NSDictionary*)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
if (options[@"name"] == nil) {
|
|
reject(@"invalid_arguments", @"addOfflinePack(): name is required.", nil);
|
|
return;
|
|
}
|
|
if (options[@"minZoomLevel"] == nil) {
|
|
reject(@"invalid_arguments", @"addOfflinePack(): minZoomLevel is required.", nil);
|
|
return;
|
|
}
|
|
if (options[@"maxZoomLevel"] == nil) {
|
|
reject(@"invalid_arguments", @"addOfflinePack(): maxZoomLevel is required.", nil);
|
|
return;
|
|
}
|
|
if (options[@"bounds"] == nil) {
|
|
reject(@"invalid_arguments", @"addOfflinePack(): bounds is required.", nil);
|
|
return;
|
|
}
|
|
if (options[@"styleURL"] == nil) {
|
|
reject(@"invalid_arguments", @"addOfflinePack(): styleURL is required.", nil);
|
|
return;
|
|
}
|
|
if (!([options[@"type"] isEqualToString:@"bbox"])) {
|
|
reject(@"invalid_arguments",
|
|
[NSString stringWithFormat:@"addOfflinePack(): Offline type %@ not supported. Only type \"bbox\" supported.", options[@"type"]]
|
|
, nil);
|
|
return;
|
|
}
|
|
|
|
NSArray *b = [options valueForKey:@"bounds"];
|
|
MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake([b[0] floatValue], [b[1] floatValue]), CLLocationCoordinate2DMake([b[2] floatValue], [b[3] floatValue]));
|
|
|
|
NSURL * styleURL = [NSURL URLWithString:[options valueForKey:@"styleURL"]];
|
|
float fromZoomLevel = [[options valueForKey:@"minZoomLevel"] floatValue];
|
|
float toZoomLevel = [[options valueForKey:@"maxZoomLevel"] floatValue];
|
|
NSString * name = [options valueForKey:@"name"];
|
|
NSString * type = [options valueForKey:@"type"];
|
|
NSDictionary * metadata = [options valueForKey:@"metadata"];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
id <MGLOfflineRegion> region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:fromZoomLevel toZoomLevel:toZoomLevel];
|
|
|
|
NSMutableDictionary *userInfo = @{ @"name": name,
|
|
@"metadata": metadata ? metadata : [NSNull null] };
|
|
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:userInfo];
|
|
|
|
[[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack *pack, NSError *error) {
|
|
if (error != nil) {
|
|
reject(@"add_pack_failed", error.localizedFailureReason, error);
|
|
} else {
|
|
[pack resume];
|
|
resolve([NSNull null]);
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (NSArray*)serializePacksArray:(NSArray<MGLOfflinePack*>*)packs
|
|
{
|
|
NSMutableArray* callbackArray = [NSMutableArray new];
|
|
|
|
for (MGLOfflinePack *pack in packs) {
|
|
NSMutableDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
|
|
[callbackArray addObject:@{ @"name": userInfo[@"name"],
|
|
@"metadata": userInfo[@"metadata"],
|
|
@"countOfBytesCompleted": @(pack.progress.countOfBytesCompleted),
|
|
@"countOfResourcesCompleted": @(pack.progress.countOfResourcesCompleted),
|
|
@"countOfResourcesExpected": @(pack.progress.countOfResourcesExpected),
|
|
@"maximumResourcesExpected": @(pack.progress.maximumResourcesExpected) }];
|
|
}
|
|
|
|
return callbackArray;
|
|
}
|
|
|
|
RCT_REMAP_METHOD(getOfflinePacks,
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
NSMutableArray* callbackArray = [NSMutableArray new];
|
|
|
|
if (!_loadedPacks) {
|
|
[_packRequests addObject:resolve];
|
|
} else {
|
|
MGLOfflinePack *packs = [MGLOfflineStorage sharedOfflineStorage].packs;
|
|
resolve([self serializePacksArray:packs]);
|
|
}
|
|
});
|
|
}
|
|
|
|
RCT_REMAP_METHOD(removeOfflinePack,
|
|
name:(NSString*)packName
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
MGLOfflinePack *packs = [MGLOfflineStorage sharedOfflineStorage].packs;
|
|
MGLOfflinePack *tempPack;
|
|
|
|
for (MGLOfflinePack *pack in packs) {
|
|
NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
|
|
if ([packName isEqualToString:userInfo[@"name"]]) {
|
|
tempPack = pack;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tempPack == nil) {
|
|
return resolve(@{});
|
|
}
|
|
|
|
NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:tempPack.context];
|
|
|
|
|
|
// Workaround for https://github.com/mapbox/mapbox-gl-native/issues/5508
|
|
|
|
[_removedPacks addObject:tempPack];
|
|
[self discardThrottleForPack:tempPack];
|
|
[tempPack suspend];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
|
[_removedPacks removeObject:tempPack];
|
|
[[MGLOfflineStorage sharedOfflineStorage] removePack:tempPack withCompletionHandler:^(NSError * _Nullable error) {
|
|
if (error != nil) {
|
|
reject(@"remove_pack_failed", error.localizedFailureReason, error);
|
|
} else {
|
|
resolve(@{ @"deleted": userInfo[@"name"] });
|
|
}
|
|
}];
|
|
});
|
|
});
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setOfflinePackProgressThrottleInterval:(nonnull NSNumber *)milis)
|
|
{
|
|
_throttleInterval = [milis intValue];
|
|
}
|
|
|
|
// View methods
|
|
|
|
RCT_EXPORT_METHOD(spliceAnnotations:(nonnull NSNumber *)reactTag
|
|
deleteAll:(BOOL)deleteAll
|
|
toDelete:(nonnull NSArray<NSString *> *)toDelete
|
|
toAdd:(nonnull NSArray *)toAdd)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
|
|
if (deleteAll) {
|
|
[mapView removeAllAnnotations];
|
|
} else {
|
|
for (NSString * key in toDelete) {
|
|
[mapView removeAnnotation:key];
|
|
}
|
|
}
|
|
|
|
for (NSObject * annotationObject in toAdd) {
|
|
[mapView upsertAnnotation:convertToMGLAnnotation(annotationObject)];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(getCenterCoordinateZoomLevel:(nonnull NSNumber *)reactTag
|
|
callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
CLLocationCoordinate2D region = [mapView centerCoordinate];
|
|
double zoom = [mapView zoomLevel];
|
|
|
|
callback(@[ @{ @"latitude": @(region.latitude),
|
|
@"longitude": @(region.longitude),
|
|
@"zoomLevel": @(zoom) } ]);
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(getBounds:(nonnull NSNumber *)reactTag
|
|
callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
MGLCoordinateBounds bounds = [mapView visibleCoordinateBounds];
|
|
NSMutableArray *callbackArray = [[NSMutableArray alloc] init];
|
|
|
|
[callbackArray addObject:@(bounds.sw.latitude)];
|
|
[callbackArray addObject:@(bounds.sw.longitude)];
|
|
[callbackArray addObject:@(bounds.ne.latitude)];
|
|
[callbackArray addObject:@(bounds.ne.longitude)];
|
|
|
|
callback(@[callbackArray]);
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(getDirection:(nonnull NSNumber *)reactTag
|
|
callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
double direction = [mapView direction];
|
|
|
|
callback(@[ @(direction) ]);
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(getPitch:(nonnull NSNumber *)reactTag
|
|
callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
double pitch = [mapView pitch];
|
|
|
|
callback(@[ @(pitch) ]);
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(easeTo:(nonnull NSNumber *)reactTag
|
|
options:(NSDictionary *)options
|
|
animated:(BOOL)animated
|
|
callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
if ([mapView isKindOfClass:[RCTMapboxGL class]]) {
|
|
|
|
NSNumber * latitude = options[@"latitude"];
|
|
NSNumber * longitude = options[@"longitude"];
|
|
NSNumber * zoom = options[@"zoomLevel"];
|
|
NSNumber * direction = options[@"direction"];
|
|
NSNumber * pitch = options[@"pitch"];
|
|
NSNumber * altitude = options[@"altitude"];
|
|
|
|
if (pitch && zoom) {
|
|
RCTLogError(@"Pitch and zoomLevel can't be set together with MapView.easeTo() on iOS. Use altitude instead of zoomLevel");
|
|
return;
|
|
}
|
|
|
|
if (zoom && altitude) {
|
|
RCTLogError(@"Altitude and zoomLevel are mutually exclusive with MapView.easeTo()");
|
|
return;
|
|
}
|
|
|
|
CLLocationCoordinate2D _center = (latitude && longitude)
|
|
? CLLocationCoordinate2DMake([latitude doubleValue], [longitude doubleValue])
|
|
: mapView.centerCoordinate;
|
|
|
|
double _direction = direction ? [direction doubleValue] : mapView.direction;
|
|
|
|
if (pitch || altitude) {
|
|
MGLMapCamera * oldCamera = (!pitch || !altitude) ? mapView.camera : nil;
|
|
double _altitude = altitude ? [altitude doubleValue] : oldCamera ? oldCamera.altitude : 0;
|
|
double _pitch = pitch ? [pitch doubleValue] : oldCamera ? oldCamera.pitch : 0;
|
|
|
|
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:_center
|
|
fromDistance:_altitude
|
|
pitch:_pitch
|
|
heading:_direction];
|
|
|
|
[mapView setCamera: camera
|
|
withDuration: 0.3
|
|
animationTimingFunction: [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
|
|
completionHandler: ^{
|
|
callback(@[[NSNull null]]);
|
|
}];
|
|
|
|
} else {
|
|
double _zoomLevel = zoom ? [zoom doubleValue] : mapView.zoomLevel;
|
|
|
|
[mapView setCenterCoordinate: _center
|
|
zoomLevel: _zoomLevel
|
|
direction: _direction
|
|
animated: animated
|
|
completionHandler: ^{
|
|
callback(@[[NSNull null]]);
|
|
}];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setVisibleCoordinateBounds:(nonnull NSNumber *)reactTag
|
|
latitudeSW:(float) latitudeSW
|
|
longitudeSW:(float) longitudeSW
|
|
latitudeNE:(float) latitudeNE
|
|
longitudeNE:(float) longitudeNE
|
|
paddingTop:(double) paddingTop
|
|
paddingRight:(double) paddingRight
|
|
paddingBottom:(double) paddingBottom
|
|
paddingLeft:(double) paddingLeft
|
|
animated:(BOOL) animated)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
if ([mapView isKindOfClass:[RCTMapboxGL class]]) {
|
|
MGLCoordinateBounds coordinatesBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(latitudeSW, longitudeSW), CLLocationCoordinate2DMake(latitudeNE, longitudeNE));
|
|
[mapView setVisibleCoordinateBounds:coordinatesBounds edgePadding:UIEdgeInsetsMake(paddingTop, paddingLeft, paddingBottom, paddingRight) animated:animated];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(selectAnnotation:(nonnull NSNumber *) reactTag
|
|
selectedIdentifier:(NSString*)selectedIdentifier
|
|
animated:(BOOL)animated)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
if ([mapView isKindOfClass:[RCTMapboxGL class]]) {
|
|
[mapView selectAnnotation:selectedIdentifier animated:animated];
|
|
}
|
|
}];
|
|
}
|
|
|
|
|
|
RCT_EXPORT_METHOD(deselectAnnotation:(nonnull NSNumber *) reactTag)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
if ([mapView isKindOfClass:[RCTMapboxGL class]]) {
|
|
[mapView deselectAnnotation];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(queryRenderedFeatures:(nonnull NSNumber *)reactTag
|
|
options:(NSDictionary *)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMapboxGL *> *viewRegistry) {
|
|
RCTMapboxGL *mapView = viewRegistry[reactTag];
|
|
if ([mapView isKindOfClass:[RCTMapboxGL class]]) {
|
|
NSDictionary *pointDict = options[@"point"];
|
|
NSDictionary *rectDict = options[@"rect"];
|
|
if ((!pointDict && !rectDict) || (pointDict && rectDict)) {
|
|
reject(@"invalid_arguments", @"queryRenderedFeatures(): one of 'point' or 'rect' is required.", nil);
|
|
return;
|
|
}
|
|
|
|
NSArray<id<MGLFeature>> *features;
|
|
NSArray<NSString *> *styleLayerIdentifiersArray = options[@"layers"];
|
|
NSSet<NSString *> *styleLayerIdentifiers;
|
|
if (styleLayerIdentifiersArray) {
|
|
styleLayerIdentifiers = [NSSet setWithArray:styleLayerIdentifiersArray];
|
|
}
|
|
|
|
if (pointDict) {
|
|
NSNumber *screenCoordX = pointDict[@"screenCoordX"];
|
|
NSNumber *screenCoordY = pointDict[@"screenCoordY"];
|
|
CGPoint point = CGPointMake(screenCoordX.floatValue, screenCoordY.floatValue);
|
|
features = [mapView visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:styleLayerIdentifiers];
|
|
} else {
|
|
NSNumber *left = rectDict[@"left"];
|
|
NSNumber *top = rectDict[@"top"];
|
|
NSNumber *right = rectDict[@"right"];
|
|
NSNumber *bottom = rectDict[@"bottom"];
|
|
CGFloat width = right.floatValue - left.floatValue;
|
|
CGFloat height = bottom.floatValue - top.floatValue;
|
|
CGRect rect = CGRectMake(left.floatValue, top.floatValue, width, height);
|
|
features = [mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:styleLayerIdentifiers];
|
|
}
|
|
|
|
NSMutableArray *geoJSONFeatures = [NSMutableArray arrayWithCapacity:features.count];
|
|
for (id <MGLFeature> feature in features) {
|
|
NSDictionary *geoJSONGeometry = [self geoJSONGeometryFromMGLFeature:feature];
|
|
NSDictionary *geoJSON = @{ @"type": @"Feature",
|
|
@"id": feature.identifier ? feature.identifier : [NSNull null],
|
|
@"properties": feature.attributes,
|
|
@"geometry": geoJSONGeometry };
|
|
[geoJSONFeatures addObject:geoJSON];
|
|
}
|
|
|
|
resolve(geoJSONFeatures);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (NSDictionary*)geoJSONGeometryFromMGLFeature:(id <MGLFeature>)feature
|
|
{
|
|
NSString *geometryType;
|
|
|
|
if ([feature isKindOfClass:[MGLShapeCollectionFeature class]]) {
|
|
geometryType = @"GeometryCollection";
|
|
MGLShapeCollectionFeature *shapeCollection = (MGLShapeCollectionFeature *) feature;
|
|
NSMutableArray *geometries = [[NSMutableArray alloc] init];
|
|
for (MGLShape <MGLFeature> *shape in shapeCollection.shapes) {
|
|
[geometries addObject:[self geoJSONGeometryFromMGLFeature:shape]];
|
|
}
|
|
return @{ @"type": geometryType,
|
|
@"geometries": geometries };
|
|
}
|
|
|
|
NSMutableArray *coordinates = [[NSMutableArray alloc] init];
|
|
|
|
if ([feature isKindOfClass:[MGLPointFeature class]]) {
|
|
geometryType = @"Point";
|
|
coordinates = [[NSMutableArray alloc] initWithArray:@[@(feature.coordinate.longitude), @(feature.coordinate.latitude)]];
|
|
} else if ([feature isKindOfClass:[MGLPolylineFeature class]]) {
|
|
geometryType = @"LineString";
|
|
MGLPolylineFeature *polyline = (MGLPolylineFeature *)feature;
|
|
coordinates = polyline.coordinateArray;
|
|
} else if ([feature isKindOfClass:[MGLPolygonFeature class]]) {
|
|
geometryType = @"Polygon";
|
|
MGLPolygonFeature *polygon = (MGLPolygonFeature *)feature;
|
|
coordinates = polygon.coordinateArray;
|
|
} else if ([feature isKindOfClass:[MGLMultiPolylineFeature class]]) {
|
|
geometryType = @"MultiLineString";
|
|
MGLMultiPolylineFeature *multiPolyline = (MGLMultiPolylineFeature *)feature;
|
|
for (MGLPolyline *polyline in multiPolyline.polylines) {
|
|
[coordinates addObject:polyline.coordinateArray];
|
|
}
|
|
} else if ([feature isKindOfClass:[MGLMultiPolygonFeature class]]) {
|
|
geometryType = @"MultiPolygon";
|
|
MGLMultiPolygonFeature *multiPolygon = (MGLMultiPolygonFeature *)feature;
|
|
for (MGLPolygon *polygon in multiPolygon.polygons) {
|
|
[coordinates addObject:polygon.coordinateArray];
|
|
}
|
|
} else if ([feature isKindOfClass:[MGLMultiPointFeature class]]) {
|
|
// this is checked last since MGLPolyline and MGLPolygon inherit from MGLMultiPoint
|
|
geometryType = @"MultiPoint";
|
|
MGLMultiPointFeature *multiPoint = (MGLMultiPointFeature *)feature;
|
|
for (int index = 0; index < multiPoint.pointCount; index++) {
|
|
CLLocationCoordinate2D coord = multiPoint.coordinates[index];
|
|
[coordinates addObject:[[NSMutableArray alloc] initWithArray:@[@(coord.longitude), @(coord.latitude)]]];
|
|
}
|
|
}
|
|
|
|
return @{ @"type": geometryType,
|
|
@"coordinates": coordinates };
|
|
}
|
|
|
|
@end
|