- App.tsx: full navigation (Auth stack + Main tabs with 5 screens) - Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen - HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors - MapScreen: interactive map with layer toggles (6 layers) - MarketplaceScreen: categories (6), products (5), search - ChatScreen: AI chat with quick prompts (4), bot responses - ProfileScreen: user info, stats, menu (9 items), logout - AlertsScreen: alert list with severity, acknowledge - SensorsScreen: sensor list with type filters (6 types), search - ZonesScreen: zone cards with stats - SettingsScreen: language picker (FR/EN/ES/DE), privacy, about - Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n - Hooks: useSensors, useAlerts, useNotifications, useLocation - Components: Card, Button, LoadingSpinner, ErrorBoundary, Header - Services: iotService, notificationService (with axios API client) - Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN) - Theme: colors.ts with full design system (Blue Ocean palette) - Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
1235 lines
43 KiB
Plaintext
1235 lines
43 KiB
Plaintext
//
|
|
// AIRGoogleMap.m
|
|
// AirMaps
|
|
//
|
|
// Created by Gil Birman on 9/1/16.
|
|
//
|
|
|
|
#ifdef HAVE_GOOGLE_MAPS
|
|
|
|
#import "AIRGoogleMap.h"
|
|
#import "AIRGoogleMapMarker.h"
|
|
#import "AIRGoogleMapMarkerManager.h"
|
|
#import "AIRGoogleMapPolygon.h"
|
|
#import "AIRGoogleMapPolyline.h"
|
|
#import "AIRGoogleMapCircle.h"
|
|
#import "AIRGoogleMapHeatmap.h"
|
|
#import "AIRGoogleMapUrlTile.h"
|
|
#import "AIRGoogleMapWMSTile.h"
|
|
#import "AIRGoogleMapOverlay.h"
|
|
#import "AIRGoogleMapCoordinate.h"
|
|
#import <GoogleMaps/GoogleMaps.h>
|
|
#import <MapKit/MapKit.h>
|
|
#import <React/UIView+React.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <objc/runtime.h>
|
|
|
|
|
|
#ifdef HAVE_GOOGLE_MAPS_UTILS
|
|
#import "GMUKMLParser.h"
|
|
#import "GMUPlacemark.h"
|
|
#import "GMUPoint.h"
|
|
#import "GMUGeometryRenderer.h"
|
|
#define REQUIRES_GOOGLE_MAPS_UTILS(feature) do {} while (0)
|
|
#else
|
|
#define GMUKMLParser void
|
|
#define GMUPlacemark void
|
|
#define REQUIRES_GOOGLE_MAPS_UTILS(feature) do { \
|
|
[NSException raise:@"ReactNativeMapsDependencyMissing" \
|
|
format:@"Use of " feature "requires Google-Maps-iOS-Utils, you must install via CocoaPods to use this feature"]; \
|
|
} while (0)
|
|
#endif
|
|
|
|
|
|
id regionAsJSON(MKCoordinateRegion region) {
|
|
return @{
|
|
@"latitude": [NSNumber numberWithDouble:region.center.latitude],
|
|
@"longitude": [NSNumber numberWithDouble:region.center.longitude],
|
|
@"latitudeDelta": [NSNumber numberWithDouble:region.span.latitudeDelta],
|
|
@"longitudeDelta": [NSNumber numberWithDouble:region.span.longitudeDelta],
|
|
};
|
|
}
|
|
#if __has_include(<ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>)
|
|
#import <ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>
|
|
#else
|
|
#import "RNMapsAirModuleDelegate.h"
|
|
#endif
|
|
@interface AIRGoogleMap () <GMSIndoorDisplayDelegate, RNMapsAirModuleDelegate>
|
|
|
|
- (id)eventFromCoordinate:(CLLocationCoordinate2D)coordinate;
|
|
|
|
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSDictionary*> *origGestureRecognizersMeta;
|
|
|
|
@end
|
|
|
|
@implementation AIRGoogleMap
|
|
{
|
|
NSMutableArray<UIView *> *_reactSubviews;
|
|
MKCoordinateRegion _initialRegion;
|
|
MKCoordinateRegion _region;
|
|
BOOL _initialRegionSet;
|
|
BOOL _initialCameraSet;
|
|
BOOL _didLayoutSubviews;
|
|
BOOL _didPrepareMap;
|
|
BOOL _didCallOnMapReady;
|
|
BOOL _zoomTapEnabled;
|
|
BOOL _isAnimating;
|
|
NSString* _googleMapId;
|
|
}
|
|
|
|
- (instancetype)initWithMapId:(NSString *)mapId initialCamera:(GMSCameraPosition*) camera backgroundColor:(UIColor *) backgroundColor andZoomTapEnabled:(BOOL)zoomTapEnabled
|
|
{
|
|
GMSMapViewOptions* options = [[GMSMapViewOptions alloc] init];
|
|
|
|
if (mapId){
|
|
GMSMapID *mapID = [GMSMapID mapIDWithIdentifier:mapId];
|
|
[options setMapID:mapID];
|
|
}
|
|
if (backgroundColor){
|
|
[options setBackgroundColor:backgroundColor];
|
|
}
|
|
if (camera){
|
|
[options setCamera:camera];
|
|
}
|
|
self = [super initWithOptions:options];
|
|
|
|
if (self) {
|
|
// Set paddingAdjustmentBehavior immediately to prevent Google Maps SDK default behavior
|
|
// from automatically adjusting padding based on safe area insets during initialization
|
|
self.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever;
|
|
|
|
_reactSubviews = [NSMutableArray new];
|
|
_markers = [NSMutableArray array];
|
|
_polygons = [NSMutableArray array];
|
|
_polylines = [NSMutableArray array];
|
|
_circles = [NSMutableArray array];
|
|
_heatmaps = [NSMutableArray array];
|
|
_tiles = [NSMutableArray array];
|
|
_overlays = [NSMutableArray array];
|
|
_initialCamera = nil;
|
|
_initialRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0.0, 0.0), MKCoordinateSpanMake(0.0, 0.0));
|
|
_region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0.0, 0.0), MKCoordinateSpanMake(0.0, 0.0));
|
|
_initialRegionSet = false;
|
|
_initialCameraSet = false;
|
|
_didLayoutSubviews = false;
|
|
_didPrepareMap = false;
|
|
_didCallOnMapReady = false;
|
|
_zoomTapEnabled = zoomTapEnabled;
|
|
|
|
// Listen to the myLocation property of GMSMapView.
|
|
[self addObserver:self
|
|
forKeyPath:@"myLocation"
|
|
options:NSKeyValueObservingOptionNew
|
|
context:NULL];
|
|
|
|
self.origGestureRecognizersMeta = [[NSMutableDictionary alloc] init];
|
|
|
|
self.indoorDisplay.delegate = self;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (instancetype) init {
|
|
return [self initWithMapId:nil initialCamera:nil backgroundColor:nil andZoomTapEnabled:YES];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self removeObserver:self
|
|
forKeyPath:@"myLocation"
|
|
context:NULL];
|
|
}
|
|
|
|
- (id)eventFromCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
|
|
CGPoint touchPoint = [self.projection pointForCoordinate:coordinate];
|
|
|
|
return @{
|
|
@"coordinate": @{
|
|
@"latitude": @(coordinate.latitude),
|
|
@"longitude": @(coordinate.longitude),
|
|
},
|
|
@"position": @{
|
|
@"x": @(touchPoint.x),
|
|
@"y": @(touchPoint.y),
|
|
},
|
|
};
|
|
}
|
|
|
|
- (BOOL) isReady {
|
|
return _didPrepareMap;
|
|
}
|
|
|
|
-(void) fitToCoordinates:(NSArray<AIRGoogleMapCoordinate *> *) coordinates withEdgePadding:(NSDictionary*) edgePadding animated:(BOOL)animated
|
|
{
|
|
CLLocationCoordinate2D myLocation = coordinates.firstObject.coordinate;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRGoogleMapCoordinate *coordinate in coordinates)
|
|
bounds = [bounds includingCoordinate:coordinate.coordinate];
|
|
|
|
// Set Map viewport
|
|
CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
|
|
CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
|
|
CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
|
|
CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
|
|
|
|
GMSCameraUpdate *cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
|
|
|
|
if (animated) {
|
|
[self animateWithCameraUpdate: cameraUpdate];
|
|
} else {
|
|
[self moveCamera: cameraUpdate];
|
|
}
|
|
}
|
|
|
|
-(void) fitToElementsWithEdgePadding:(nonnull NSDictionary *)edgePadding
|
|
animated:(BOOL)animated
|
|
{
|
|
CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(self.markers.firstObject)).realMarker.position;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRGoogleMapMarker *marker in self.markers)
|
|
bounds = [bounds includingCoordinate:marker.realMarker.position];
|
|
|
|
GMSCameraUpdate* cameraUpdate;
|
|
|
|
if ([edgePadding count] != 0) {
|
|
// Set Map viewport
|
|
CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
|
|
CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
|
|
CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
|
|
CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
|
|
|
|
cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
|
|
} else {
|
|
cameraUpdate = [GMSCameraUpdate fitBounds:bounds withPadding:55.0f];
|
|
}
|
|
if (animated) {
|
|
[self animateWithCameraUpdate: cameraUpdate];
|
|
} else {
|
|
[self moveCamera: cameraUpdate];
|
|
}
|
|
}
|
|
|
|
- (void) fitToSuppliedMarkers:(NSArray*) markers withEdgePadding:(NSDictionary*) edgePadding animated:(BOOL)animated
|
|
{
|
|
NSPredicate *filterMarkers = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
|
|
AIRGoogleMapMarker *marker = (AIRGoogleMapMarker *)evaluatedObject;
|
|
return [marker isKindOfClass:[AIRGoogleMapMarker class]] && [markers containsObject:marker.identifier];
|
|
}];
|
|
|
|
NSArray *filteredMarkers = [self.markers filteredArrayUsingPredicate:filterMarkers];
|
|
|
|
CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(filteredMarkers.firstObject)).realMarker.position;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRGoogleMapMarker *marker in filteredMarkers)
|
|
bounds = [bounds includingCoordinate:marker.realMarker.position];
|
|
|
|
// Set Map viewport
|
|
CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
|
|
CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
|
|
CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
|
|
CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
|
|
|
|
GMSCameraUpdate* cameraUpdate = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
|
|
if (animated) {
|
|
[self animateWithCameraUpdate:cameraUpdate
|
|
];
|
|
} else {
|
|
[self moveCamera: cameraUpdate];
|
|
}
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
|
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex {
|
|
// Our desired API is to pass up markers/overlays as children to the mapview component.
|
|
// This is where we intercept them and do the appropriate underlying mapview action.
|
|
if ([subview isKindOfClass:[AIRGoogleMapMarker class]]) {
|
|
AIRGoogleMapMarker *marker = (AIRGoogleMapMarker*)subview;
|
|
[marker didInsertInMap:self];
|
|
[self.markers addObject:marker];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapPolygon class]]) {
|
|
AIRGoogleMapPolygon *polygon = (AIRGoogleMapPolygon*)subview;
|
|
[polygon didInsertInMap:self];
|
|
[self.polygons addObject:polygon];
|
|
|
|
} else if ([NSStringFromClass([subview class]) isEqualToString:@"RNMapsGooglePolygonView"]){
|
|
// RNMapsGooglePolygonView *polygon = (RNMapsGooglePolygonView*)subview;
|
|
// [polygon didInsertInMap:self];
|
|
[self.polygons addObject:subview];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapPolyline class]]) {
|
|
AIRGoogleMapPolyline *polyline = (AIRGoogleMapPolyline*)subview;
|
|
polyline.polyline.map = self;
|
|
[self.polylines addObject:polyline];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapCircle class]]) {
|
|
AIRGoogleMapCircle *circle = (AIRGoogleMapCircle*)subview;
|
|
circle.circle.map = self;
|
|
[self.circles addObject:circle];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapUrlTile class]]) {
|
|
AIRGoogleMapUrlTile *tile = (AIRGoogleMapUrlTile*)subview;
|
|
tile.tileLayer.map = self;
|
|
[self.tiles addObject:tile];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapWMSTile class]]) {
|
|
AIRGoogleMapWMSTile *tile = (AIRGoogleMapWMSTile*)subview;
|
|
tile.tileLayer.map = self;
|
|
[self.tiles addObject:tile];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapOverlay class]]) {
|
|
AIRGoogleMapOverlay *overlay = (AIRGoogleMapOverlay*)subview;
|
|
overlay.overlay.map = self;
|
|
[self.overlays addObject:overlay];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapHeatmap class]]){
|
|
AIRGoogleMapHeatmap *heatmap = (AIRGoogleMapHeatmap*)subview;
|
|
heatmap.heatmap.map = self;
|
|
[self.heatmaps addObject:heatmap];
|
|
} else {
|
|
NSArray<id<RCTComponent>> *childSubviews = [subview reactSubviews];
|
|
for (int i = 0; i < childSubviews.count; i++) {
|
|
[self insertReactSubview:(UIView *)childSubviews[i] atIndex:atIndex];
|
|
}
|
|
}
|
|
[_reactSubviews insertObject:(UIView *)subview atIndex:(NSUInteger) atIndex];
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
|
- (void)removeReactSubview:(id<RCTComponent>)subview {
|
|
// similarly, when the children are being removed we have to do the appropriate
|
|
// underlying mapview action here.
|
|
if ([subview isKindOfClass:[AIRGoogleMapMarker class]]) {
|
|
AIRGoogleMapMarker *marker = (AIRGoogleMapMarker*)subview;
|
|
marker.realMarker.map = nil;
|
|
[self.markers removeObject:marker];
|
|
} else if ([NSStringFromClass([subview class]) isEqualToString:@"RNMapsGooglePolygonView"]) {
|
|
[self.polygons removeObject:subview];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapPolygon class]]) {
|
|
AIRGoogleMapPolygon *polygon = (AIRGoogleMapPolygon*)subview;
|
|
polygon.polygon.map = nil;
|
|
[self.polygons removeObject:polygon];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapPolyline class]]) {
|
|
AIRGoogleMapPolyline *polyline = (AIRGoogleMapPolyline*)subview;
|
|
polyline.polyline.map = nil;
|
|
[self.polylines removeObject:polyline];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapCircle class]]) {
|
|
AIRGoogleMapCircle *circle = (AIRGoogleMapCircle*)subview;
|
|
circle.circle.map = nil;
|
|
[self.circles removeObject:circle];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapUrlTile class]]) {
|
|
AIRGoogleMapUrlTile *tile = (AIRGoogleMapUrlTile*)subview;
|
|
tile.tileLayer.map = nil;
|
|
[self.tiles removeObject:tile];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapWMSTile class]]) {
|
|
AIRGoogleMapWMSTile *tile = (AIRGoogleMapWMSTile*)subview;
|
|
tile.tileLayer.map = nil;
|
|
[self.tiles removeObject:tile];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapOverlay class]]) {
|
|
AIRGoogleMapOverlay *overlay = (AIRGoogleMapOverlay*)subview;
|
|
overlay.overlay.map = nil;
|
|
[self.overlays removeObject:overlay];
|
|
} else if ([subview isKindOfClass:[AIRGoogleMapHeatmap class]]){
|
|
AIRGoogleMapHeatmap *heatmap = (AIRGoogleMapHeatmap*)subview;
|
|
heatmap.heatmap.map = nil;
|
|
[self.heatmaps removeObject:heatmap];
|
|
} else {
|
|
NSArray<id<RCTComponent>> *childSubviews = [subview reactSubviews];
|
|
for (int i = 0; i < childSubviews.count; i++) {
|
|
[self removeReactSubview:(UIView *)childSubviews[i]];
|
|
}
|
|
}
|
|
[_reactSubviews removeObject:(UIView *)subview];
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
|
- (NSArray<id<RCTComponent>> *)reactSubviews {
|
|
return _reactSubviews;
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
- (NSDictionary *)getMapBoundaries
|
|
{
|
|
GMSVisibleRegion visibleRegion = self.projection.visibleRegion;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:visibleRegion];
|
|
|
|
CLLocationCoordinate2D northEast = bounds.northEast;
|
|
CLLocationCoordinate2D southWest = bounds.southWest;
|
|
|
|
return @{
|
|
@"northEast": @{
|
|
@"latitude": [NSNumber numberWithDouble:northEast.latitude],
|
|
@"longitude": [NSNumber numberWithDouble:northEast.longitude],
|
|
},
|
|
@"southWest": @{
|
|
@"latitude": [NSNumber numberWithDouble:southWest.latitude],
|
|
@"longitude": [NSNumber numberWithDouble:southWest.longitude],
|
|
},
|
|
};
|
|
}
|
|
|
|
- (NSDictionary *) getCameraDic {
|
|
GMSCameraPosition *camera = [self camera];
|
|
return @{
|
|
@"center": @{
|
|
@"latitude": @(camera.target.latitude),
|
|
@"longitude": @(camera.target.longitude),
|
|
},
|
|
@"pitch": @(camera.viewingAngle),
|
|
@"heading": @(camera.bearing),
|
|
@"zoom": @(camera.zoom),
|
|
};
|
|
}
|
|
|
|
- (void)layoutSubviews {
|
|
[super layoutSubviews];
|
|
if(_didLayoutSubviews) return;
|
|
_didLayoutSubviews = true;
|
|
|
|
if (_initialCamera != nil) {
|
|
self.camera = _initialCamera;
|
|
_initialCameraSet = true;
|
|
}
|
|
else if (_initialRegion.span.latitudeDelta != 0.0 &&
|
|
_initialRegion.span.longitudeDelta != 0.0) {
|
|
self.camera = [AIRGoogleMap makeGMSCameraPositionFromMap:self andMKCoordinateRegion:_initialRegion];
|
|
_initialRegionSet = true;
|
|
} else if (_region.span.latitudeDelta != 0.0 &&
|
|
_region.span.longitudeDelta != 0.0) {
|
|
self.camera = [AIRGoogleMap makeGMSCameraPositionFromMap:self andMKCoordinateRegion:_region];
|
|
}
|
|
}
|
|
|
|
- (void)setInitialRegion:(MKCoordinateRegion)initialRegion {
|
|
_initialRegion = initialRegion;
|
|
if(!_initialRegionSet && _didLayoutSubviews){
|
|
self.camera = [AIRGoogleMap makeGMSCameraPositionFromMap:self andMKCoordinateRegion:initialRegion];
|
|
_initialRegionSet = true;
|
|
}
|
|
}
|
|
|
|
- (void)setInitialCamera:(GMSCameraPosition*)initialCamera {
|
|
_initialCamera = initialCamera;
|
|
if(!_initialCameraSet && _didLayoutSubviews){
|
|
self.camera = initialCamera;
|
|
_initialCameraSet = true;
|
|
}
|
|
}
|
|
|
|
- (void)setRegion:(MKCoordinateRegion)region {
|
|
if (_isAnimating || !CLLocationCoordinate2DIsValid(region.center)) return;
|
|
_region = region;
|
|
if(_didLayoutSubviews) {
|
|
self.camera = [AIRGoogleMap makeGMSCameraPositionFromMap:self andMKCoordinateRegion:region];
|
|
}
|
|
}
|
|
|
|
- (void) setGoogleMapId:(NSString *) googleMapId {
|
|
_googleMapId = googleMapId;
|
|
}
|
|
|
|
- (GMSCameraPosition *)cameraProp {
|
|
if(_didLayoutSubviews) {
|
|
return self.camera;
|
|
} else {
|
|
return _initialCamera;
|
|
}
|
|
}
|
|
|
|
- (void)setCameraProp:(GMSCameraPosition*)camera {
|
|
if (_isAnimating || !CLLocationCoordinate2DIsValid([camera target])) return;
|
|
_initialCamera = camera;
|
|
if(_didLayoutSubviews) {
|
|
self.camera = camera;
|
|
}
|
|
}
|
|
|
|
- (void)setOnMapReady:(RCTBubblingEventBlock)onMapReady {
|
|
_onMapReady = onMapReady;
|
|
if(!_didCallOnMapReady && _didPrepareMap) {
|
|
self.onMapReady(@{});
|
|
_didCallOnMapReady = true;
|
|
}
|
|
}
|
|
|
|
- (void)didPrepareMap {
|
|
if (!_didPrepareMap){
|
|
[self overrideGestureRecognizersForView];
|
|
}
|
|
|
|
if (!_didCallOnMapReady && self.onMapReady) {
|
|
self.onMapReady(@{});
|
|
_didCallOnMapReady = true;
|
|
}
|
|
_didPrepareMap = true;
|
|
}
|
|
|
|
- (void)mapViewDidFinishTileRendering {
|
|
if (self.onMapLoaded) self.onMapLoaded(@{});
|
|
}
|
|
|
|
- (BOOL)didTapMarker:(GMSMarker *)marker {
|
|
AIRGMSMarker *airMarker = (AIRGMSMarker *)marker;
|
|
|
|
id event = @{@"action": @"marker-press",
|
|
@"id": airMarker.identifier ?: @"unknown",
|
|
@"coordinate": @{
|
|
@"latitude": @(airMarker.position.latitude),
|
|
@"longitude": @(airMarker.position.longitude)
|
|
}
|
|
};
|
|
|
|
if (airMarker.onPress) airMarker.onPress(event);
|
|
if (self.onMarkerPress) self.onMarkerPress(event);
|
|
|
|
// TODO: not sure why this is necessary
|
|
[self setSelectedMarker:marker];
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)didTapPolyline:(GMSOverlay *)polyline {
|
|
AIRGMSPolyline *airPolyline = (AIRGMSPolyline *)polyline;
|
|
|
|
id event = @{@"action": @"polyline-press",
|
|
@"id": airPolyline.identifier ?: @"unknown",
|
|
};
|
|
|
|
if (airPolyline.onPress) airPolyline.onPress(event);
|
|
}
|
|
|
|
- (void)didTapPolygon:(GMSOverlay *)polygon {
|
|
AIRGMSPolygon *airPolygon = (AIRGMSPolygon *)polygon;
|
|
|
|
id event = @{@"action": @"polygon-press",
|
|
@"id": airPolygon.identifier ?: @"unknown",
|
|
};
|
|
|
|
if (airPolygon.onPress) airPolygon.onPress(event);
|
|
}
|
|
|
|
- (void)didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
if (!self.onPress) return;
|
|
self.onPress([self eventFromCoordinate:coordinate]);
|
|
}
|
|
|
|
- (void)didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
if (!self.onLongPress) return;
|
|
self.onLongPress([self eventFromCoordinate:coordinate]);
|
|
}
|
|
|
|
- (void)willMove:(BOOL)gesture {
|
|
_isAnimating = YES;
|
|
GMSCameraPosition *position = self.camera;
|
|
id event = @{@"region": regionAsJSON([AIRGoogleMap makeGMSCameraPositionFromMap:self andGMSCameraPosition:position]),
|
|
@"isGesture": [NSNumber numberWithBool:gesture],
|
|
};
|
|
if (self.onRegionChangeStart) self.onRegionChangeStart(event);
|
|
}
|
|
|
|
- (void)didChangeCameraPosition:(GMSCameraPosition *)position isGesture:(BOOL)isGesture{
|
|
id event = @{@"region": regionAsJSON([AIRGoogleMap makeGMSCameraPositionFromMap:self andGMSCameraPosition:position]),
|
|
@"isGesture": [NSNumber numberWithBool:isGesture],
|
|
};
|
|
|
|
if (self.onRegionChange) self.onRegionChange(event);
|
|
}
|
|
|
|
- (void)didTapPOIWithPlaceID:(NSString *)placeID
|
|
name:(NSString *)name
|
|
location:(CLLocationCoordinate2D)location {
|
|
id event = @{@"placeId": placeID,
|
|
@"name": name,
|
|
@"coordinate": @{
|
|
@"latitude": @(location.latitude),
|
|
@"longitude": @(location.longitude)
|
|
}
|
|
};
|
|
|
|
if (self.onPoiClick) self.onPoiClick(event);
|
|
}
|
|
|
|
- (void)idleAtCameraPosition:(GMSCameraPosition *)position isGesture:(BOOL)isGesture{
|
|
id event = @{@"region": regionAsJSON([AIRGoogleMap makeGMSCameraPositionFromMap:self andGMSCameraPosition:position]),
|
|
@"isGesture": [NSNumber numberWithBool:isGesture],
|
|
};
|
|
if (self.onRegionChangeComplete) self.onRegionChangeComplete(event); // complete
|
|
_isAnimating = NO;
|
|
}
|
|
|
|
- (void)setMapPadding:(UIEdgeInsets)mapPadding {
|
|
self.padding = mapPadding;
|
|
}
|
|
|
|
- (UIEdgeInsets)mapPadding {
|
|
return self.padding;
|
|
}
|
|
|
|
- (void)setPaddingAdjustmentBehaviorString:(NSString *)str
|
|
{
|
|
if ([str isEqualToString:@"never"])
|
|
{
|
|
self.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever;
|
|
}
|
|
else if ([str isEqualToString:@"automatic"])
|
|
{
|
|
self.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAutomatic;
|
|
}
|
|
else //if ([str isEqualToString:@"always"]) <-- default
|
|
{
|
|
self.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAlways;
|
|
}
|
|
}
|
|
|
|
- (NSString *)paddingAdjustmentBehaviorString
|
|
{
|
|
switch (self.paddingAdjustmentBehavior)
|
|
{
|
|
case kGMSMapViewPaddingAdjustmentBehaviorNever:
|
|
return @"never";
|
|
case kGMSMapViewPaddingAdjustmentBehaviorAutomatic:
|
|
return @"automatic";
|
|
case kGMSMapViewPaddingAdjustmentBehaviorAlways:
|
|
return @"always";
|
|
|
|
default:
|
|
return @"unknown";
|
|
}
|
|
}
|
|
|
|
- (void)setScrollEnabled:(BOOL)scrollEnabled {
|
|
self.settings.scrollGestures = scrollEnabled;
|
|
}
|
|
|
|
- (BOOL)scrollEnabled {
|
|
return self.settings.scrollGestures;
|
|
}
|
|
|
|
- (void)setZoomEnabled:(BOOL)zoomEnabled {
|
|
self.settings.zoomGestures = zoomEnabled;
|
|
}
|
|
|
|
- (BOOL)zoomEnabled {
|
|
return self.settings.zoomGestures;
|
|
}
|
|
|
|
- (void)setScrollDuringRotateOrZoomEnabled:(BOOL)enableScrollGesturesDuringRotateOrZoom {
|
|
self.settings.allowScrollGesturesDuringRotateOrZoom = enableScrollGesturesDuringRotateOrZoom;
|
|
}
|
|
|
|
- (BOOL)scrollDuringRotateOrZoomEnabled {
|
|
return self.settings.allowScrollGesturesDuringRotateOrZoom;
|
|
}
|
|
|
|
- (void)setZoomTapEnabled:(BOOL)zoomTapEnabled {
|
|
_zoomTapEnabled = zoomTapEnabled;
|
|
}
|
|
|
|
- (BOOL)zoomTapEnabled {
|
|
return _zoomTapEnabled;
|
|
}
|
|
|
|
- (void)setRotateEnabled:(BOOL)rotateEnabled {
|
|
self.settings.rotateGestures = rotateEnabled;
|
|
}
|
|
|
|
- (BOOL)rotateEnabled {
|
|
return self.settings.rotateGestures;
|
|
}
|
|
|
|
- (void)setPitchEnabled:(BOOL)pitchEnabled {
|
|
self.settings.tiltGestures = pitchEnabled;
|
|
}
|
|
|
|
- (BOOL)pitchEnabled {
|
|
return self.settings.tiltGestures;
|
|
}
|
|
|
|
- (void)setShowsTraffic:(BOOL)showsTraffic {
|
|
self.trafficEnabled = showsTraffic;
|
|
}
|
|
|
|
- (BOOL)showsTraffic {
|
|
return self.trafficEnabled;
|
|
}
|
|
|
|
- (void)setShowsBuildings:(BOOL)showsBuildings {
|
|
self.buildingsEnabled = showsBuildings;
|
|
}
|
|
|
|
- (BOOL)showsBuildings {
|
|
return self.buildingsEnabled;
|
|
}
|
|
|
|
- (void)setShowsCompass:(BOOL)showsCompass {
|
|
self.settings.compassButton = showsCompass;
|
|
}
|
|
|
|
- (void)setCustomMapStyleString:(NSString *)customMapStyleString {
|
|
NSError *error;
|
|
|
|
GMSMapStyle *style = [GMSMapStyle styleWithJSONString:customMapStyleString error:&error];
|
|
|
|
if (!style) {
|
|
NSLog(@"The style definition could not be loaded: %@", error);
|
|
}
|
|
[self setMapStyle:style];
|
|
}
|
|
|
|
- (BOOL)showsCompass {
|
|
return self.settings.compassButton;
|
|
}
|
|
|
|
- (void)setShowsUserLocation:(BOOL)showsUserLocation {
|
|
self.myLocationEnabled = showsUserLocation;
|
|
}
|
|
|
|
- (BOOL)showsUserLocation {
|
|
return self.myLocationEnabled;
|
|
}
|
|
|
|
- (void)setShowsMyLocationButton:(BOOL)showsMyLocationButton {
|
|
self.settings.myLocationButton = showsMyLocationButton;
|
|
}
|
|
|
|
- (BOOL)showsMyLocationButton {
|
|
return self.settings.myLocationButton;
|
|
}
|
|
|
|
- (void)setMinZoom:(CGFloat)minZoomLevel {
|
|
[self setMinZoom:minZoomLevel maxZoom:self.maxZoom ];
|
|
}
|
|
|
|
- (void)setMaxZoom:(CGFloat)maxZoomLevel {
|
|
[self setMinZoom:self.minZoom maxZoom:maxZoomLevel ];
|
|
}
|
|
|
|
- (void)setShowsIndoors:(BOOL)showsIndoors {
|
|
self.indoorEnabled = showsIndoors;
|
|
}
|
|
|
|
- (BOOL)showsIndoors {
|
|
return self.indoorEnabled;
|
|
}
|
|
|
|
- (void)setShowsIndoorLevelPicker:(BOOL)showsIndoorLevelPicker {
|
|
self.settings.indoorPicker = showsIndoorLevelPicker;
|
|
}
|
|
|
|
- (BOOL)showsIndoorLevelPicker {
|
|
return self.settings.indoorPicker;
|
|
}
|
|
|
|
-(void)setSelectedMarker:(AIRGMSMarker *)selectedMarker {
|
|
if (selectedMarker == self.selectedMarker) {
|
|
return;
|
|
}
|
|
AIRGMSMarker *airMarker = (AIRGMSMarker *) self.selectedMarker;
|
|
AIRGoogleMapMarker *fakeAirMarker = (AIRGoogleMapMarker *) airMarker.fakeMarker;
|
|
AIRGoogleMapMarker *fakeSelectedMarker = (AIRGoogleMapMarker *) selectedMarker.fakeMarker;
|
|
|
|
if (airMarker && airMarker.onDeselect) {
|
|
airMarker.onDeselect([fakeAirMarker makeEventData:@"marker-deselect"]);
|
|
}
|
|
|
|
if (airMarker && self.onMarkerDeselect) {
|
|
self.onMarkerDeselect([fakeAirMarker makeEventData:@"marker-deselect"]);
|
|
}
|
|
|
|
if (selectedMarker && selectedMarker.onSelect) {
|
|
selectedMarker.onSelect([fakeSelectedMarker makeEventData:@"marker-select"]);
|
|
}
|
|
|
|
if (selectedMarker && self.onMarkerSelect) {
|
|
self.onMarkerSelect([fakeSelectedMarker makeEventData:@"marker-select"]);
|
|
}
|
|
|
|
[super setSelectedMarker:selectedMarker];
|
|
}
|
|
|
|
+ (MKCoordinateRegion) makeGMSCameraPositionFromMap:(GMSMapView *)map andGMSCameraPosition:(GMSCameraPosition *)position {
|
|
// solution from here: http://stackoverflow.com/a/16587735/1102215
|
|
GMSVisibleRegion visibleRegion = map.projection.visibleRegion;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion: visibleRegion];
|
|
CLLocationCoordinate2D center;
|
|
CLLocationDegrees longitudeDelta;
|
|
CLLocationDegrees latitudeDelta = bounds.northEast.latitude - bounds.southWest.latitude;
|
|
|
|
if(bounds.northEast.longitude >= bounds.southWest.longitude) {
|
|
//Standard case
|
|
center = CLLocationCoordinate2DMake((bounds.southWest.latitude + bounds.northEast.latitude) / 2,
|
|
(bounds.southWest.longitude + bounds.northEast.longitude) / 2);
|
|
longitudeDelta = bounds.northEast.longitude - bounds.southWest.longitude;
|
|
} else {
|
|
//Region spans the international dateline
|
|
center = CLLocationCoordinate2DMake((bounds.southWest.latitude + bounds.northEast.latitude) / 2,
|
|
(bounds.southWest.longitude + bounds.northEast.longitude + 360) / 2);
|
|
longitudeDelta = bounds.northEast.longitude + 360 - bounds.southWest.longitude;
|
|
}
|
|
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
|
|
return MKCoordinateRegionMake(center, span);
|
|
}
|
|
|
|
+ (GMSCameraPosition*) makeGMSCameraPositionFromMap:(GMSMapView *)map andMKCoordinateRegion:(MKCoordinateRegion)region {
|
|
float latitudeDelta = region.span.latitudeDelta * 0.5;
|
|
float longitudeDelta = region.span.longitudeDelta * 0.5;
|
|
|
|
CLLocationCoordinate2D a = CLLocationCoordinate2DMake(region.center.latitude + latitudeDelta,
|
|
region.center.longitude + longitudeDelta);
|
|
CLLocationCoordinate2D b = CLLocationCoordinate2DMake(region.center.latitude - latitudeDelta,
|
|
region.center.longitude - longitudeDelta);
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:a coordinate:b];
|
|
return [map cameraForBounds:bounds insets:UIEdgeInsetsZero];
|
|
}
|
|
|
|
#pragma mark - RNMapsAirModuleDelegate
|
|
|
|
- (NSDictionary *) getCoordinatesForPoint:(CGPoint)point
|
|
{
|
|
CLLocationCoordinate2D coordinate = [self.projection coordinateForPoint:point];
|
|
return @{
|
|
@"latitude": @(coordinate.latitude),
|
|
@"longitude": @(coordinate.longitude),
|
|
};
|
|
}
|
|
|
|
- (NSDictionary *) getPointForCoordinates:(CLLocationCoordinate2D)location
|
|
{
|
|
CGPoint touchPoint = [self.projection pointForCoordinate:location];
|
|
return @{
|
|
@"x": @(touchPoint.x),
|
|
@"y": @(touchPoint.y),
|
|
};
|
|
}
|
|
|
|
-(void) takeSnapshotWithConfig:(NSDictionary *)config success:(RCTPromiseResolveBlock)success error:(RCTPromiseRejectBlock)error {
|
|
/* unused
|
|
NSNumber *width = [config objectForKey:@"width"];
|
|
NSNumber *height = [config objectForKey:@"height"];
|
|
*/
|
|
NSNumber *quality = [config objectForKey:@"quality"];
|
|
|
|
NSString *format = [config objectForKey:@"format"];
|
|
NSString *result = [config objectForKey:@"result"];
|
|
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
|
|
NSString *pathComponent = [NSString stringWithFormat:@"Documents/snapshot-%.20lf.%@", timeStamp, format];
|
|
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent: pathComponent];
|
|
|
|
// TODO: currently we are ignoring width, height, region
|
|
|
|
UIGraphicsBeginImageContextWithOptions(self.frame.size, YES, 0.0f);
|
|
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
|
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
|
|
NSData *data;
|
|
if ([format isEqualToString:@"png"]) {
|
|
data = UIImagePNGRepresentation(image);
|
|
|
|
} else if([format isEqualToString:@"jpg"]) {
|
|
data = UIImageJPEGRepresentation(image, quality.floatValue);
|
|
}
|
|
|
|
if ([result isEqualToString:@"file"]) {
|
|
[data writeToFile:filePath atomically:YES];
|
|
success(filePath);
|
|
} else if ([result isEqualToString:@"base64"]) {
|
|
success([data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]);
|
|
}
|
|
|
|
UIGraphicsEndImageContext();
|
|
}
|
|
|
|
#pragma mark - Utils
|
|
|
|
- (CGRect) frameForMarker:(AIRGoogleMapMarker*) mrkView {
|
|
CGPoint mrkAnchor = mrkView.realMarker.groundAnchor;
|
|
CGPoint mrkPoint = [self.projection pointForCoordinate:mrkView.coordinate];
|
|
CGSize mrkSize = mrkView.realMarker.iconView ? mrkView.realMarker.iconView.bounds.size : CGSizeMake(20, 30);
|
|
CGRect mrkFrame = CGRectMake(mrkPoint.x, mrkPoint.y, mrkSize.width, mrkSize.height);
|
|
mrkFrame.origin.y -= mrkAnchor.y * mrkSize.height;
|
|
mrkFrame.origin.x -= mrkAnchor.x * mrkSize.width;
|
|
return mrkFrame;
|
|
}
|
|
|
|
- (NSDictionary*) getMarkersFramesWithOnlyVisible:(BOOL)onlyVisible {
|
|
NSMutableDictionary* markersFrames = [NSMutableDictionary new];
|
|
for (AIRGoogleMapMarker* mrkView in self.markers) {
|
|
CGRect frame = [self frameForMarker:mrkView];
|
|
CGPoint point = [self.projection pointForCoordinate:mrkView.coordinate];
|
|
NSDictionary* frameDict = @{
|
|
@"x": @(frame.origin.x),
|
|
@"y": @(frame.origin.y),
|
|
@"width": @(frame.size.width),
|
|
@"height": @(frame.size.height)
|
|
};
|
|
NSDictionary* pointDict = @{
|
|
@"x": @(point.x),
|
|
@"y": @(point.y)
|
|
};
|
|
NSString* k = mrkView.identifier;
|
|
BOOL isVisible = CGRectIntersectsRect(self.bounds, frame);
|
|
if (k != nil && (!onlyVisible || isVisible)) {
|
|
[markersFrames setObject:@{ @"frame": frameDict, @"point": pointDict } forKey:k];
|
|
}
|
|
}
|
|
return markersFrames;
|
|
}
|
|
|
|
- (AIRGoogleMapMarker*) markerAtPoint:(CGPoint)point {
|
|
AIRGoogleMapMarker* mrk = nil;
|
|
for (AIRGoogleMapMarker* mrkView in self.markers) {
|
|
CGRect frame = [self frameForMarker:mrkView];
|
|
if (CGRectContainsPoint(frame, point)) {
|
|
mrk = mrkView;
|
|
break;
|
|
}
|
|
}
|
|
return mrk;
|
|
}
|
|
|
|
-(SEL)getActionForTarget:(NSObject*)target {
|
|
SEL action = nil;
|
|
uint32_t ivarCount;
|
|
Ivar *ivars = class_copyIvarList([target class], &ivarCount);
|
|
if (ivars) {
|
|
for (uint32_t i = 0 ; i < ivarCount ; i++) {
|
|
Ivar ivar = ivars[i];
|
|
const char* type = ivar_getTypeEncoding(ivar);
|
|
const char* ivarName = ivar_getName(ivar);
|
|
NSString* name = [NSString stringWithCString: ivarName encoding: NSASCIIStringEncoding];
|
|
if (type[0] == ':' && [name isEqualToString:@"_action"]) {
|
|
SEL sel = ((SEL (*)(id, Ivar))object_getIvar)(target, ivar);
|
|
action = sel;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(ivars);
|
|
return action;
|
|
}
|
|
|
|
#pragma mark - Overrides for Callout behavior
|
|
|
|
-(void)overrideGestureRecognizersForView {
|
|
NSArray* grs = self.gestureRecognizers;
|
|
for (UIGestureRecognizer* gestureRecognizer in grs) {
|
|
NSNumber* grHash = [NSNumber numberWithUnsignedInteger:gestureRecognizer.hash];
|
|
if([self.origGestureRecognizersMeta objectForKey:grHash] != nil)
|
|
continue; //already patched
|
|
|
|
//get original handlers
|
|
NSArray* origTargets = [gestureRecognizer valueForKey:@"targets"];
|
|
NSMutableArray* origTargetsActions = [[NSMutableArray alloc] init];
|
|
BOOL isZoomTapGesture = NO;
|
|
for (NSObject* trg in origTargets) {
|
|
NSObject* target = [trg valueForKey:@"target"];
|
|
SEL action = [self getActionForTarget:trg];
|
|
isZoomTapGesture = [NSStringFromSelector(action) isEqualToString:@"handleZoomTapGesture:"];
|
|
[origTargetsActions addObject:@{
|
|
@"target": [NSValue valueWithNonretainedObject:target],
|
|
@"action": NSStringFromSelector(action)
|
|
}];
|
|
}
|
|
if (isZoomTapGesture && self.zoomTapEnabled == NO) {
|
|
[self removeGestureRecognizer:gestureRecognizer];
|
|
continue;
|
|
}
|
|
|
|
//replace with extendedMapGestureHandler
|
|
for (NSDictionary* origTargetAction in origTargetsActions) {
|
|
NSValue* targetValue = [origTargetAction objectForKey:@"target"];
|
|
NSObject* target = [targetValue nonretainedObjectValue];
|
|
NSString* actionString = [origTargetAction objectForKey:@"action"];
|
|
SEL action = NSSelectorFromString(actionString);
|
|
[gestureRecognizer removeTarget:target action:action];
|
|
}
|
|
[gestureRecognizer addTarget:self action:@selector(extendedMapGestureHandler:)];
|
|
|
|
[self.origGestureRecognizersMeta setObject:@{@"targets": origTargetsActions}
|
|
forKey:grHash];
|
|
}
|
|
}
|
|
|
|
- (id)extendedMapGestureHandler:(UIGestureRecognizer*)gestureRecognizer {
|
|
NSNumber* grHash = [NSNumber numberWithUnsignedInteger:gestureRecognizer.hash];
|
|
UIWindow* win = [[[UIApplication sharedApplication] windows] firstObject];
|
|
NSObject* bubbleProvider = [self valueForKey:@"bubbleProvider"]; //GMSbubbleEntityProvider
|
|
CGRect bubbleAbsoluteFrame = [bubbleProvider accessibilityFrame];
|
|
CGRect bubbleFrame = [win convertRect:bubbleAbsoluteFrame toView:self];
|
|
UIView* bubbleView = [bubbleProvider valueForKey:@"view"];
|
|
|
|
BOOL performOriginalActions = YES;
|
|
BOOL isTap = [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] || [gestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]];
|
|
if (isTap) {
|
|
BOOL isTapInsideBubble = NO;
|
|
CGPoint tapPoint = CGPointZero;
|
|
CGPoint tapPointInBubble = CGPointZero;
|
|
|
|
NSArray* touches = [gestureRecognizer valueForKey:@"touches"];
|
|
UITouch* oneTouch = [touches firstObject];
|
|
NSArray* delayedTouches = [gestureRecognizer valueForKey:@"delayedTouches"];
|
|
NSObject* delayedTouch = [delayedTouches firstObject]; //UIGestureDeleayedTouch
|
|
UITouch* tapTouch = [delayedTouch valueForKey:@"stateWhenDelayed"];
|
|
|
|
if (!tapTouch) {
|
|
tapTouch = oneTouch;
|
|
};
|
|
|
|
tapPoint = [tapTouch locationInView:self];
|
|
isTapInsideBubble = tapTouch != nil && CGRectContainsPoint(bubbleFrame, tapPoint);
|
|
if (isTapInsideBubble) {
|
|
tapPointInBubble = CGPointMake(tapPoint.x - bubbleFrame.origin.x, tapPoint.y - bubbleFrame.origin.y);
|
|
}
|
|
if (isTapInsideBubble) {
|
|
//find bubble's marker
|
|
AIRGoogleMapMarker* markerView = nil;
|
|
AIRGMSMarker* marker = nil;
|
|
for (AIRGoogleMapMarker* mrk in self.markers) {
|
|
if ([mrk.calloutView isEqual:bubbleView]) {
|
|
markerView = mrk;
|
|
marker = markerView.realMarker;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//find real tap target subview
|
|
UIView* realSubview = [(RCTView*)bubbleView hitTest:tapPointInBubble withEvent:nil];
|
|
AIRGoogleMapCalloutSubview* realPressableSubview = nil;
|
|
if (realSubview) {
|
|
UIView* tmp = realSubview;
|
|
while (tmp && tmp != win && tmp != bubbleView) {
|
|
if ([tmp respondsToSelector:@selector(onPress)]) {
|
|
realPressableSubview = (AIRGoogleMapCalloutSubview*) tmp;
|
|
break;
|
|
}
|
|
tmp = tmp.superview;
|
|
}
|
|
}
|
|
|
|
if (markerView) {
|
|
BOOL isInsideCallout = [markerView.calloutView isPointInside:tapPointInBubble];
|
|
if (isInsideCallout) {
|
|
[markerView didTapInfoWindowOfMarker:marker subview:realPressableSubview point:tapPointInBubble frame:bubbleFrame];
|
|
} else {
|
|
AIRGoogleMapMarker* markerAtTapPoint = [self markerAtPoint:tapPoint];
|
|
if (markerAtTapPoint != nil) {
|
|
[self didTapMarker:markerAtTapPoint.realMarker];
|
|
} else {
|
|
CLLocationCoordinate2D coord = [self.projection coordinateForPoint:tapPoint];
|
|
[markerView hideCalloutView];
|
|
[self didTapAtCoordinate:coord];
|
|
}
|
|
}
|
|
|
|
performOriginalActions = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (performOriginalActions) {
|
|
NSDictionary* origMeta = [self.origGestureRecognizersMeta objectForKey:grHash];
|
|
NSDictionary* origTargets = [origMeta objectForKey:@"targets"];
|
|
for (NSDictionary* origTarget in origTargets) {
|
|
NSValue* targetValue = [origTarget objectForKey:@"target"];
|
|
NSObject* target = [targetValue nonretainedObjectValue];
|
|
NSString* actionString = [origTarget objectForKey:@"action"];
|
|
SEL action = NSSelectorFromString(actionString);
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
|
[target performSelector:action withObject:gestureRecognizer];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
|
|
#pragma mark - KVO updates
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary *)change
|
|
context:(void *)context {
|
|
if ([keyPath isEqualToString:@"myLocation"]){
|
|
CLLocation *location = [object myLocation];
|
|
|
|
id event = @{@"coordinate": @{
|
|
@"latitude": @(location.coordinate.latitude),
|
|
@"longitude": @(location.coordinate.longitude),
|
|
@"altitude": @(location.altitude),
|
|
@"timestamp": @(location.timestamp.timeIntervalSince1970 * 1000),
|
|
@"accuracy": @(location.horizontalAccuracy),
|
|
@"altitudeAccuracy": @(location.verticalAccuracy),
|
|
@"speed": @(location.speed),
|
|
@"heading": @(location.course),
|
|
}
|
|
};
|
|
|
|
if (self.onUserLocationChange) self.onUserLocationChange(event);
|
|
} else {
|
|
// This message is not for me; pass it on to super.
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
}
|
|
}
|
|
|
|
+ (NSString *)GetIconUrl:(GMUPlacemark *) marker parser:(GMUKMLParser *) parser {
|
|
#if HAVE_GOOGLE_MAPS_UTILS
|
|
if (marker.style.styleID != nil) {
|
|
for (GMUStyle *style in parser.styles) {
|
|
if (style.styleID == marker.style.styleID) {
|
|
return style.iconUrl;
|
|
}
|
|
}
|
|
}
|
|
|
|
return marker.style.iconUrl;
|
|
#else
|
|
REQUIRES_GOOGLE_MAPS_UTILS("GetIconUrl:parser:"); return @"";
|
|
#endif
|
|
}
|
|
|
|
- (NSString *)KmlSrc {
|
|
return _kmlSrc;
|
|
}
|
|
|
|
- (void) setKMLData:(NSData *) urlData {
|
|
GMUKMLParser *parser = [[GMUKMLParser alloc] initWithData:urlData];
|
|
[parser parse];
|
|
|
|
NSUInteger index = 0;
|
|
NSMutableArray *markers = [[NSMutableArray alloc]init];
|
|
|
|
for (GMUPlacemark *place in parser.placemarks) {
|
|
|
|
CLLocationCoordinate2D location =((GMUPoint *) place.geometry).coordinate;
|
|
|
|
AIRGoogleMapMarker *marker = (AIRGoogleMapMarker *)[[AIRGoogleMapMarkerManager alloc] view];
|
|
if (!marker.bridge) {
|
|
marker.bridge = _bridge;
|
|
}
|
|
marker.identifier = place.title;
|
|
marker.coordinate = location;
|
|
marker.title = place.title;
|
|
marker.subtitle = place.snippet;
|
|
marker.pinColor = place.style.fillColor;
|
|
marker.imageSrc = [AIRGoogleMap GetIconUrl:place parser:parser];
|
|
marker.layer.backgroundColor = [UIColor clearColor].CGColor;
|
|
marker.layer.position = CGPointZero;
|
|
|
|
[self insertReactSubview:(UIView *) marker atIndex:index];
|
|
|
|
[markers addObject:@{@"id": marker.identifier,
|
|
@"title": marker.title,
|
|
@"description": marker.subtitle,
|
|
@"coordinate": @{
|
|
@"latitude": @(location.latitude),
|
|
@"longitude": @(location.longitude)
|
|
}
|
|
}];
|
|
|
|
index++;
|
|
}
|
|
|
|
id event = @{@"markers": markers};
|
|
if (self.onKmlReady) self.onKmlReady(event);
|
|
#else
|
|
REQUIRES_GOOGLE_MAPS_UTILS();
|
|
#endif
|
|
}
|
|
|
|
- (void)setKmlSrc:(NSString *)kmlUrl {
|
|
#if HAVE_GOOGLE_MAPS_UTILS
|
|
|
|
_kmlSrc = kmlUrl;
|
|
|
|
NSURL *url = [NSURL URLWithString:kmlUrl];
|
|
NSData *urlData = nil;
|
|
|
|
if ([url isFileURL]) {
|
|
[self setKMLData:[NSData dataWithContentsOfURL:url]];
|
|
} else {
|
|
__weak AIRGoogleMap *weakSelf = self;
|
|
|
|
NSURLSession *session = [NSURLSession sharedSession];
|
|
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
|
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Error fetching data: %@", error.localizedDescription);
|
|
return;
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
AIRGoogleMap *strongSelf = weakSelf;
|
|
[strongSelf setKMLData:data];
|
|
});
|
|
|
|
}
|
|
}];
|
|
[dataTask resume];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- (void) didChangeActiveBuilding: (nullable GMSIndoorBuilding *) building {
|
|
if (!building) {
|
|
if (!self.onIndoorBuildingFocused) {
|
|
return;
|
|
}
|
|
self.onIndoorBuildingFocused(@{
|
|
@"activeLevelIndex": @0,
|
|
@"underground": @false,
|
|
@"levels": [[NSMutableArray alloc]init]
|
|
});
|
|
}
|
|
NSInteger i = 0;
|
|
NSMutableArray *arrayLevels = [[NSMutableArray alloc]init];
|
|
for (GMSIndoorLevel *level in building.levels) {
|
|
[arrayLevels addObject: @{
|
|
@"index": @(i),
|
|
@"name" : level.name,
|
|
@"shortName" : level.shortName,
|
|
}];
|
|
i++;
|
|
}
|
|
if (!self.onIndoorBuildingFocused) {
|
|
return;
|
|
}
|
|
self.onIndoorBuildingFocused(@{
|
|
@"activeLevelIndex": @(building.defaultLevelIndex),
|
|
@"underground": @(building.underground),
|
|
@"levels": arrayLevels
|
|
});
|
|
}
|
|
|
|
- (void) didChangeActiveLevel: (nullable GMSIndoorLevel *) level {
|
|
if (!self.onIndoorLevelActivated || !self.indoorDisplay || !level) {
|
|
return;
|
|
}
|
|
NSInteger i = 0;
|
|
for (GMSIndoorLevel *buildingLevel in self.indoorDisplay.activeBuilding.levels) {
|
|
if (buildingLevel.name == level.name && buildingLevel.shortName == level.shortName) {
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
self.onIndoorLevelActivated(@{
|
|
@"activeLevelIndex": @(i),
|
|
@"name": level.name,
|
|
@"shortName": level.shortName
|
|
});
|
|
}
|
|
// do nothing, passed as options on initialization
|
|
- (void)setLoadingBackgroundColor:(UIColor *)loadingBackgroundColor {
|
|
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
#endif
|