1//
2//  AIRGoogleMapManager.m
3//  AirMaps
4//
5//  Created by Gil Birman on 9/1/16.
6//
7
8#ifdef HAVE_GOOGLE_MAPS
9
10
11#import "AIRGoogleMapManager.h"
12#import <React/RCTViewManager.h>
13#import <React/RCTBridge.h>
14#import <React/RCTUIManager.h>
15#import <React/RCTConvert+CoreLocation.h>
16#import <React/RCTEventDispatcher.h>
17#import <React/RCTViewManager.h>
18#import <React/RCTConvert.h>
19#import <React/UIView+React.h>
20#import "RCTConvert+GMSMapViewType.h"
21#import "AIRGoogleMap.h"
22#import "AIRMapMarker.h"
23#import "AIRMapPolyline.h"
24#import "AIRMapPolygon.h"
25#import "AIRMapCircle.h"
26#import "SMCalloutView.h"
27#import "AIRGoogleMapMarker.h"
28#import "RCTConvert+AirMap.h"
29
30#import <MapKit/MapKit.h>
31#import <QuartzCore/QuartzCore.h>
32
33static NSString *const RCTMapViewKey = @"MapView";
34
35
36@interface AIRGoogleMapManager() <GMSMapViewDelegate>
37{
38  BOOL didCallOnMapReady;
39}
40@end
41
42@implementation AIRGoogleMapManager
43
44RCT_EXPORT_MODULE()
45
46- (UIView *)view
47{
48  [GMSServices setMetalRendererEnabled:YES];
49
50  AIRGoogleMap *map = [AIRGoogleMap new];
51  map.bridge = self.bridge;
52  map.delegate = self;
53  map.isAccessibilityElement = NO;
54  map.accessibilityElementsHidden = NO;
55  map.settings.consumesGesturesInView = NO;
56
57  UIPanGestureRecognizer *drag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapDrag:)];
58  [drag setMinimumNumberOfTouches:1];
59  [drag setMaximumNumberOfTouches:1];
60  [map addGestureRecognizer:drag];
61
62  UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapDrag:)];
63  [map addGestureRecognizer:pinch];
64
65  return map;
66}
67
68RCT_EXPORT_VIEW_PROPERTY(isAccessibilityElement, BOOL)
69RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)
70RCT_EXPORT_VIEW_PROPERTY(initialCamera, GMSCameraPosition)
71RCT_REMAP_VIEW_PROPERTY(camera, cameraProp, GMSCameraPosition)
72RCT_EXPORT_VIEW_PROPERTY(initialRegion, MKCoordinateRegion)
73RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
74RCT_EXPORT_VIEW_PROPERTY(showsBuildings, BOOL)
75RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL)
76//RCT_EXPORT_VIEW_PROPERTY(showsScale, BOOL)  // Not supported by GoogleMaps
77RCT_EXPORT_VIEW_PROPERTY(showsTraffic, BOOL)
78RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
79RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL)
80RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
81RCT_EXPORT_VIEW_PROPERTY(scrollDuringRotateOrZoomEnabled, BOOL)
82RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
83RCT_EXPORT_VIEW_PROPERTY(zoomTapEnabled, BOOL)
84RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL)
85RCT_EXPORT_VIEW_PROPERTY(showsMyLocationButton, BOOL)
86RCT_EXPORT_VIEW_PROPERTY(showsIndoors, BOOL)
87RCT_EXPORT_VIEW_PROPERTY(showsIndoorLevelPicker, BOOL)
88RCT_EXPORT_VIEW_PROPERTY(customMapStyleString, NSString)
89RCT_EXPORT_VIEW_PROPERTY(mapPadding, UIEdgeInsets)
90RCT_REMAP_VIEW_PROPERTY(paddingAdjustmentBehavior, paddingAdjustmentBehaviorString, NSString)
91RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTBubblingEventBlock)
92RCT_EXPORT_VIEW_PROPERTY(onMapLoaded, RCTBubblingEventBlock)
93RCT_EXPORT_VIEW_PROPERTY(onKmlReady, RCTBubblingEventBlock)
94RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
95RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock)
96RCT_EXPORT_VIEW_PROPERTY(onPanDrag, RCTBubblingEventBlock)
97RCT_EXPORT_VIEW_PROPERTY(onUserLocationChange, RCTBubblingEventBlock)
98RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
99RCT_EXPORT_VIEW_PROPERTY(onMarkerPress, RCTDirectEventBlock)
100RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTDirectEventBlock)
101RCT_EXPORT_VIEW_PROPERTY(onRegionChangeComplete, RCTDirectEventBlock)
102RCT_EXPORT_VIEW_PROPERTY(onPoiClick, RCTDirectEventBlock)
103RCT_EXPORT_VIEW_PROPERTY(onIndoorLevelActivated, RCTDirectEventBlock)
104RCT_EXPORT_VIEW_PROPERTY(onIndoorBuildingFocused, RCTDirectEventBlock)
105RCT_EXPORT_VIEW_PROPERTY(mapType, GMSMapViewType)
106RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
107RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
108RCT_EXPORT_VIEW_PROPERTY(kmlSrc, NSString)
109
110RCT_EXPORT_METHOD(getCamera:(nonnull NSNumber *)reactTag
111                  resolver: (RCTPromiseResolveBlock)resolve
112                  rejecter:(RCTPromiseRejectBlock)reject)
113{
114    [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
115        id view = viewRegistry[reactTag];
116        if (![view isKindOfClass:[AIRGoogleMap class]]) {
117            reject(@"Invalid argument", [NSString stringWithFormat:@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view], NULL);
118        } else {
119            AIRGoogleMap *mapView = (AIRGoogleMap *)view;
120            resolve(@{
121                      @"center": @{
122                              @"latitude": @(mapView.camera.target.latitude),
123                              @"longitude": @(mapView.camera.target.longitude),
124                              },
125                      @"pitch": @(mapView.camera.viewingAngle),
126                      @"heading": @(mapView.camera.bearing),
127                      @"zoom": @(mapView.camera.zoom),
128                    });
129        }
130    }];
131}
132
133RCT_EXPORT_METHOD(setCamera:(nonnull NSNumber *)reactTag
134                  camera:(id)json)
135{
136    [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
137        id view = viewRegistry[reactTag];
138        if (![view isKindOfClass:[AIRGoogleMap class]]) {
139            RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
140        } else {
141            AIRGoogleMap *mapView = (AIRGoogleMap *)view;
142            GMSCameraPosition *camera = [RCTConvert GMSCameraPositionWithDefaults:json existingCamera:[mapView camera]];
143            [mapView setCamera:camera];
144        }
145    }];
146}
147
148
149RCT_EXPORT_METHOD(animateCamera:(nonnull NSNumber *)reactTag
150                  withCamera:(id)json
151                  withDuration:(CGFloat)duration)
152{
153    [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
154        id view = viewRegistry[reactTag];
155        if (![view isKindOfClass:[AIRGoogleMap class]]) {
156            RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
157        } else {
158            [CATransaction begin];
159            [CATransaction setAnimationDuration:duration/1000];
160            AIRGoogleMap *mapView = (AIRGoogleMap *)view;
161            GMSCameraPosition *camera = [RCTConvert GMSCameraPositionWithDefaults:json existingCamera:[mapView camera]];
162            [mapView animateToCameraPosition:camera];
163            [CATransaction commit];
164        }
165    }];
166}
167
168RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag
169                  withRegion:(MKCoordinateRegion)region
170                  withDuration:(CGFloat)duration)
171{
172  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
173    id view = viewRegistry[reactTag];
174    if (![view isKindOfClass:[AIRGoogleMap class]]) {
175      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
176    } else {
177      // Core Animation must be used to control the animation's duration
178      // See http://stackoverflow.com/a/15663039/171744
179      [CATransaction begin];
180      [CATransaction setAnimationDuration:duration/1000];
181      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
182      GMSCameraPosition *camera = [AIRGoogleMap makeGMSCameraPositionFromMap:mapView andMKCoordinateRegion:region];
183      [mapView animateToCameraPosition:camera];
184      [CATransaction commit];
185    }
186  }];
187}
188
189RCT_EXPORT_METHOD(fitToElements:(nonnull NSNumber *)reactTag
190                  edgePadding:(nonnull NSDictionary *)edgePadding
191                  animated:(BOOL)animated)
192{
193  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
194    id view = viewRegistry[reactTag];
195    if (![view isKindOfClass:[AIRGoogleMap class]]) {
196      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
197    } else {
198      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
199
200      CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(mapView.markers.firstObject)).realMarker.position;
201      GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
202
203      for (AIRGoogleMapMarker *marker in mapView.markers)
204        bounds = [bounds includingCoordinate:marker.realMarker.position];
205
206        GMSCameraUpdate* cameraUpdate;
207
208        if ([edgePadding count] != 0) {
209            // Set Map viewport
210            CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
211            CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
212            CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
213            CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
214
215            cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
216        } else {
217            cameraUpdate = [GMSCameraUpdate fitBounds:bounds withPadding:55.0f];
218        }
219      if (animated) {
220        [mapView animateWithCameraUpdate: cameraUpdate];
221      } else {
222        [mapView moveCamera: cameraUpdate];
223      }
224    }
225  }];
226}
227
228RCT_EXPORT_METHOD(fitToSuppliedMarkers:(nonnull NSNumber *)reactTag
229                  markers:(nonnull NSArray *)markers
230                  edgePadding:(nonnull NSDictionary *)edgePadding
231                  animated:(BOOL)animated)
232{
233  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
234    id view = viewRegistry[reactTag];
235    if (![view isKindOfClass:[AIRGoogleMap class]]) {
236      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
237    } else {
238      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
239
240      NSPredicate *filterMarkers = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
241        AIRGoogleMapMarker *marker = (AIRGoogleMapMarker *)evaluatedObject;
242        return [marker isKindOfClass:[AIRGoogleMapMarker class]] && [markers containsObject:marker.identifier];
243      }];
244
245      NSArray *filteredMarkers = [mapView.markers filteredArrayUsingPredicate:filterMarkers];
246
247      CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(filteredMarkers.firstObject)).realMarker.position;
248      GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
249
250      for (AIRGoogleMapMarker *marker in filteredMarkers)
251        bounds = [bounds includingCoordinate:marker.realMarker.position];
252
253      // Set Map viewport
254      CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
255      CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
256      CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
257      CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
258
259      GMSCameraUpdate* cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
260      if (animated) {
261        [mapView animateWithCameraUpdate:cameraUpdate
262         ];
263      } else {
264        [mapView moveCamera: cameraUpdate];
265      }
266    }
267  }];
268}
269
270RCT_EXPORT_METHOD(fitToCoordinates:(nonnull NSNumber *)reactTag
271                  coordinates:(nonnull NSArray<AIRMapCoordinate *> *)coordinates
272                  edgePadding:(nonnull NSDictionary *)edgePadding
273                  animated:(BOOL)animated)
274{
275  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
276    id view = viewRegistry[reactTag];
277    if (![view isKindOfClass:[AIRGoogleMap class]]) {
278      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
279    } else {
280      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
281
282      CLLocationCoordinate2D myLocation = coordinates.firstObject.coordinate;
283      GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
284
285      for (AIRMapCoordinate *coordinate in coordinates)
286        bounds = [bounds includingCoordinate:coordinate.coordinate];
287
288      // Set Map viewport
289      CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
290      CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
291      CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
292      CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
293
294      GMSCameraUpdate *cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
295
296      if (animated) {
297        [mapView animateWithCameraUpdate: cameraUpdate];
298      } else {
299        [mapView moveCamera: cameraUpdate];
300      }
301    }
302  }];
303}
304
305RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)reactTag
306                  withWidth:(nonnull NSNumber *)width
307                  withHeight:(nonnull NSNumber *)height
308                  withRegion:(MKCoordinateRegion)region
309                  format:(nonnull NSString *)format
310                  quality:(nonnull NSNumber *)quality
311                  result:(nonnull NSString *)result
312                  withCallback:(RCTResponseSenderBlock)callback)
313{
314  NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
315  NSString *pathComponent = [NSString stringWithFormat:@"Documents/snapshot-%.20lf.%@", timeStamp, format];
316  NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent: pathComponent];
317
318  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
319    id view = viewRegistry[reactTag];
320    if (![view isKindOfClass:[AIRGoogleMap class]]) {
321        RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
322    } else {
323      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
324
325      // TODO: currently we are ignoring width, height, region
326
327      UIGraphicsBeginImageContextWithOptions(mapView.frame.size, YES, 0.0f);
328      [mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
329      UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
330
331      NSData *data;
332      if ([format isEqualToString:@"png"]) {
333          data = UIImagePNGRepresentation(image);
334
335      }
336      else if([format isEqualToString:@"jpg"]) {
337            data = UIImageJPEGRepresentation(image, quality.floatValue);
338      }
339
340      if ([result isEqualToString:@"file"]) {
341          [data writeToFile:filePath atomically:YES];
342            callback(@[[NSNull null], filePath]);
343        }
344        else if ([result isEqualToString:@"base64"]) {
345            callback(@[[NSNull null], [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]]);
346        }
347    }
348    UIGraphicsEndImageContext();
349  }];
350}
351
352RCT_EXPORT_METHOD(pointForCoordinate:(nonnull NSNumber *)reactTag
353                  coordinate:(NSDictionary *)coordinate
354                  resolver: (RCTPromiseResolveBlock)resolve
355                  rejecter:(RCTPromiseRejectBlock)reject)
356{
357  CLLocationCoordinate2D coord =
358  CLLocationCoordinate2DMake(
359                             [coordinate[@"latitude"] doubleValue],
360                             [coordinate[@"longitude"] doubleValue]
361                             );
362
363  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
364    id view = viewRegistry[reactTag];
365    if (![view isKindOfClass:[AIRGoogleMap class]]) {
366      RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
367    } else {
368      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
369
370      CGPoint touchPoint = [mapView.projection pointForCoordinate:coord];
371
372      resolve(@{
373                @"x": @(touchPoint.x),
374                @"y": @(touchPoint.y),
375                });
376    }
377  }];
378}
379
380RCT_EXPORT_METHOD(coordinateForPoint:(nonnull NSNumber *)reactTag
381                  point:(NSDictionary *)point
382                  resolver: (RCTPromiseResolveBlock)resolve
383                  rejecter:(RCTPromiseRejectBlock)reject)
384{
385  CGPoint pt = CGPointMake(
386                           [point[@"x"] doubleValue],
387                           [point[@"y"] doubleValue]
388                           );
389
390  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
391    id view = viewRegistry[reactTag];
392    if (![view isKindOfClass:[AIRGoogleMap class]]) {
393      RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
394    } else {
395      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
396
397      CLLocationCoordinate2D coordinate = [mapView.projection coordinateForPoint:pt];
398
399      resolve(@{
400                @"latitude": @(coordinate.latitude),
401                @"longitude": @(coordinate.longitude),
402                });
403    }
404  }];
405}
406
407RCT_EXPORT_METHOD(getMarkersFrames:(nonnull NSNumber *)reactTag
408                  onlyVisible:(BOOL)onlyVisible
409                  resolver: (RCTPromiseResolveBlock)resolve
410                  rejecter:(RCTPromiseRejectBlock)reject)
411{
412    [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
413        id view = viewRegistry[reactTag];
414        if (![view isKindOfClass:[AIRGoogleMap class]]) {
415            RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
416        } else {
417            AIRGoogleMap *mapView = (AIRGoogleMap *)view;
418            resolve([mapView getMarkersFramesWithOnlyVisible:onlyVisible]);
419        }
420    }];
421}
422
423RCT_EXPORT_METHOD(getMapBoundaries:(nonnull NSNumber *)reactTag
424                  resolver:(RCTPromiseResolveBlock)resolve
425                  rejecter:(RCTPromiseRejectBlock)reject)
426{
427  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
428    id view = viewRegistry[reactTag];
429    if (![view isKindOfClass:[AIRGoogleMap class]]) {
430      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
431    } else {
432        NSArray *boundingBox = [view getMapBoundaries];
433
434        resolve(@{
435          @"northEast" : @{
436            @"longitude" : boundingBox[0][0],
437            @"latitude" : boundingBox[0][1]
438          },
439          @"southWest" : @{
440            @"longitude" : boundingBox[1][0],
441            @"latitude" : boundingBox[1][1]
442          }
443        });
444    }
445  }];
446}
447
448RCT_EXPORT_METHOD(setMapBoundaries:(nonnull NSNumber *)reactTag
449                  northEast:(CLLocationCoordinate2D)northEast
450                  southWest:(CLLocationCoordinate2D)southWest)
451{
452  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
453    id view = viewRegistry[reactTag];
454    if (![view isKindOfClass:[AIRGoogleMap class]]) {
455      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
456    } else {
457      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
458
459      GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast coordinate:southWest];
460
461      mapView.cameraTargetBounds = bounds;
462    }
463  }];
464}
465
466RCT_EXPORT_METHOD(setIndoorActiveLevelIndex:(nonnull NSNumber *)reactTag
467                  levelIndex:(NSInteger) levelIndex)
468{
469  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
470    id view = viewRegistry[reactTag];
471    if (![view isKindOfClass:[AIRGoogleMap class]]) {
472      RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
473    } else {
474      AIRGoogleMap *mapView = (AIRGoogleMap *)view;
475      if (!mapView.indoorDisplay) {
476        return;
477      }
478      if ( levelIndex < [mapView.indoorDisplay.activeBuilding.levels count]) {
479        mapView.indoorDisplay.activeLevel = mapView.indoorDisplay.activeBuilding.levels[levelIndex];
480      }
481    }
482  }];
483 }
484
485+ (BOOL)requiresMainQueueSetup {
486  return YES;
487}
488
489- (NSDictionary *)constantsToExport {
490  return @{ @"legalNotice": [GMSServices openSourceLicenseInfo] };
491}
492
493- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture{
494    self.isGesture = gesture;
495}
496
497- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
498  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
499  [googleMapView didPrepareMap];
500}
501
502- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
503  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
504  [googleMapView mapViewDidFinishTileRendering];
505}
506
507- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
508  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
509  return [googleMapView didTapMarker:marker];
510}
511
512- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSPolygon *)polygon {
513  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
514  [googleMapView didTapPolygon:polygon];
515}
516
517- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
518  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
519  [googleMapView didTapAtCoordinate:coordinate];
520}
521
522- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
523  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
524  [googleMapView didLongPressAtCoordinate:coordinate];
525}
526
527- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
528  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
529  [googleMapView didChangeCameraPosition:position isGesture:self.isGesture];
530}
531
532- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
533  AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
534  [googleMapView idleAtCameraPosition:position isGesture:self.isGesture];
535}
536
537- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
538  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
539  return [aMarker.fakeMarker markerInfoWindow];}
540
541- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker {
542  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
543  return [aMarker.fakeMarker markerInfoContents];
544}
545
546- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
547  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
548  [aMarker.fakeMarker didTapInfoWindowOfMarker:aMarker];
549}
550
551- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
552  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
553  [aMarker.fakeMarker didBeginDraggingMarker:aMarker];
554}
555
556- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
557  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
558  [aMarker.fakeMarker didEndDraggingMarker:aMarker];
559}
560
561- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
562  AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
563  [aMarker.fakeMarker didDragMarker:aMarker];
564}
565
566- (void)mapView:(GMSMapView *)mapView
567    didTapPOIWithPlaceID:(NSString *)placeID
568                    name:(NSString *)name
569                location:(CLLocationCoordinate2D)location {
570    AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
571    [googleMapView didTapPOIWithPlaceID:placeID name:name location:location];
572}
573
574#pragma mark Gesture Recognizer Handlers
575
576- (void)handleMapDrag:(UIPanGestureRecognizer*)recognizer {
577  AIRGoogleMap *map = (AIRGoogleMap *)recognizer.view;
578  if (!map.onPanDrag) return;
579
580  CGPoint touchPoint = [recognizer locationInView:map];
581  CLLocationCoordinate2D coord = [map.projection coordinateForPoint:touchPoint];
582  map.onPanDrag(@{
583                  @"coordinate": @{
584                      @"latitude": @(coord.latitude),
585                      @"longitude": @(coord.longitude),
586                      },
587                  @"position": @{
588                      @"x": @(touchPoint.x),
589                      @"y": @(touchPoint.y),
590                      },
591                  });
592
593}
594
595@end
596
597#endif
598