;
@@ -133,30 +132,48 @@ declare namespace MapboxGL {
}
namespace geoUtils {
- function makePoint(coordinates: Position, properties?: P, options?: PositionsOptions): Feature;
+ function makePoint(coordinates: Position, properties?: P, options?: PositionsOptions): Feature;
function makeLineString(coordinates: Position[], properties?: P, options?: PositionsOptions): Feature;
function makeLatLngBounds(northEastCoordinates: Position[], southWestCoordinates: Position[]): FeatureCollection;
function makeFeature(geometry: G, properties?: P): Feature;
- function makeFeatureCollection(features: Array>, options?: PositionsOptions): FeatureCollection;
+ function makeFeatureCollection(features: Array>, options?: PositionsOptions): FeatureCollection;
function addToFeatureCollection(newFeatureCollection: Array>, newFeature: Feature): FeatureCollection;
function calculateDistance(origin: Coord, dest: Coord, options?: UnitsOptions): number;
- function pointAlongLine(newLineString: Feature | LineString, distAlong: number, options?: UnitsOptions): Feature;
+ function pointAlongLine(newLineString: Feature | LineString, distAlong: number, options?: UnitsOptions): Feature;
function getOrCalculateVisibleRegion(coord: { lon: number; lat: number }, zoomLevel: number, width: number, height: number, nativeRegion: { properties: { visibleBounds: number[] }; visibleBounds: number[] }): void;
}
namespace Animated {
// sources
- class ShapeSource extends Component {}
- class ImageSource extends Component {}
+ class ShapeSource extends Component { }
+ class ImageSource extends Component { }
// layers
- class FillLayer extends Component {}
- class FillExtrusionLayer extends Component {}
- class LineLayer extends Component {}
- class CircleLayer extends Component {}
- class SymbolLayer extends Component {}
- class RasterLayer extends Component {}
- class BackgroundLayer extends Component {}
+ class FillLayer extends Component { }
+ class FillExtrusionLayer extends Component { }
+ class LineLayer extends Component { }
+ class CircleLayer extends Component { }
+ class SymbolLayer extends Component { }
+ class RasterLayer extends Component { }
+ class BackgroundLayer extends Component { }
+ }
+
+ /**
+ * Classes
+ */
+
+ class AnimatedPoint {
+ constructor(point?: GeoJSON.Point);
+ longitude: ReactNative.Animated.Value;
+ latitude: ReactNative.Animated.Value;
+ setValue: (point: GeoJSON.Point) => void;
+ setOffset: (point: GeoJSON.Point) => void;
+ flattenOffset: () => void;
+ stopAnimation: (cb?: () => GeoJSON.Point) => void;
+ addListener: (cb?: () => GeoJSON.Point) => void;
+ removeListener: (id: string) => void;
+ spring: (config: Record) => ReactNative.Animated.CompositeAnimation;
+ timing: (config: Record) => ReactNative.Animated.CompositeAnimation;
}
/**
@@ -197,7 +214,7 @@ declare namespace MapboxGL {
setCamera(config: CameraSettings): void;
}
- class UserLocation extends Component {}
+ class UserLocation extends Component { }
interface Location {
coords: Coordinates;
@@ -205,29 +222,62 @@ declare namespace MapboxGL {
}
interface Coordinates {
+
+ /**
+ * The heading (measured in degrees) relative to true north.
+ * Heading is used to describe the direction the device is pointing to (the value of the compass).
+ * Note that on Android this is incorrectly reporting the course value as mentioned in issue https://github.com/react-native-mapbox-gl/maps/issues/1213
+ * and will be corrected in a future update.
+ */
heading?: number;
+
+ /**
+ * The direction in which the device is traveling, measured in degrees and relative to due north.
+ * The course refers to the direction the device is actually moving (not the same as heading).
+ */
+ course?: number;
+
+ /**
+ * The instantaneous speed of the device, measured in meters per second.
+ */
speed?: number;
+
+ /**
+ * The latitude in degrees.
+ */
latitude: number;
+
+ /**
+ * The longitude in degrees.
+ */
longitude: number;
+
+ /**
+ * The radius of uncertainty for the location, measured in meters.
+ */
accuracy?: number;
+
+ /**
+ * The altitude, measured in meters.
+ */
altitude?: number;
}
- class Light extends Component {}
+ class Light extends Component { }
class StyleSheet extends Component {
- static create | NamedStyles>(styles: T): void;
+ static create | NamedStyles>(styles: T): T;
camera(
- stops: {[key: number]: string},
+ stops: { [key: number]: string },
interpolationMode?: InterpolationMode,
): void;
source(
- stops: {[key: number]: string},
+ stops: { [key: number]: string },
attributeName: string,
interpolationMode?: InterpolationMode,
): void;
composite(
- stops: {[key: number]: string},
+ stops: { [key: number]: string },
attributeName: string,
interpolationMode?: InterpolationMode,
): void;
@@ -238,30 +288,46 @@ declare namespace MapboxGL {
class PointAnnotation extends Component {
refresh(): void;
}
- class MarkerView extends Component {}
- class Callout extends Component {}
- interface Style extends React.FC {}
+ class MarkerView extends Component { }
+ class Callout extends Component { }
+ interface Style extends React.FC { }
/**
* Sources
*/
- class VectorSource extends Component {}
- class ShapeSource extends Component {}
- class RasterSource extends Component {}
+ class VectorSource extends Component { }
+ class ShapeSource extends Component {
+ features(filter?: Expression): Promise>
+
+ getClusterExpansionZoom(feature: Feature | number): Promise
+ /**
+ * Returns all the leaves of a cluster with pagination support.
+ * @param cluster feature cluster
+ * @param limit the number of leaves to return
+ * @param offset the amount of points to skip (for pagination)
+ */
+ getClusterLeaves: (feature: Feature | number, limit: number, offset: number ) => Promise>
+ /**
+ * Returns the children of a cluster (on the next zoom level).
+ * @param cluster feature cluster
+ */
+ getClusterChildren: (feature: Feature | number) => Promise>
+ }
+ class RasterSource extends Component { }
/**
* Layers
*/
- class BackgroundLayer extends Component {}
- class CircleLayer extends Component {}
- class FillExtrusionLayer extends Component {}
- class FillLayer extends Component {}
- class LineLayer extends Component {}
- class RasterLayer extends Component {}
- class SymbolLayer extends Component {}
- class HeatmapLayer extends Component {}
- class Images extends Component {}
- class ImageSource extends Component {}
+ class BackgroundLayer extends Component { }
+ class CircleLayer extends Component { }
+ class FillExtrusionLayer extends Component { }
+ class FillLayer extends Component { }
+ class LineLayer extends Component { }
+ class RasterLayer extends Component { }
+ class SymbolLayer extends Component { }
+ class HeatmapLayer extends Component { }
+ class Images extends Component { }
+ class ImageSource extends Component { }
class LocationManager extends Component {
start(displacement?: number): void;
@@ -278,6 +344,7 @@ declare namespace MapboxGL {
errorListener?: (pack: OfflinePack, err: OfflineProgressError) => void
): Promise;
deletePack(name: string): Promise;
+ invalidatePack(name: string): Promise;
getPacks(): Promise>;
getPack(name: string): Promise;
invalidateAmbientCache(): Promise;
@@ -360,17 +427,19 @@ declare namespace MapboxGL {
TrafficDay = 'mapbox://styles/mapbox/navigation-preview-day-v4',
TrafficNight = 'mapbox://styles/mapbox/navigation-preview-night-v4',
}
-
- enum StyleSource {
- DefaultSourceID = 0,
- }
}
export type AttributionPosition =
- | {top: number; left: number}
- | {top: number; right: number}
- | {bottom: number; left: number}
- | {bottom: number; right: number};
+ | { top: number; left: number }
+ | { top: number; right: number }
+ | { bottom: number; left: number }
+ | { bottom: number; right: number };
+
+export type LogoPosition =
+ | { top: number; left: number }
+ | { top: number; right: number }
+ | { bottom: number; left: number }
+ | { bottom: number; right: number };
export interface RegionPayload {
zoomLevel: number;
@@ -388,6 +457,8 @@ export interface MapViewProps extends ViewProps {
contentInset?: Array;
style?: StyleProp;
styleURL?: string;
+ styleJSON?: string;
+ preferredFramesPerSecond?: number;
localizeLabels?: boolean;
zoomEnabled?: boolean;
scrollEnabled?: boolean;
@@ -396,12 +467,14 @@ export interface MapViewProps extends ViewProps {
attributionEnabled?: boolean;
attributionPosition?: AttributionPosition;
logoEnabled?: boolean;
+ logoPosition?: LogoPosition;
compassEnabled?: boolean;
compassViewPosition?: number;
compassViewMargins?: Point;
surfaceView?: boolean;
regionWillChangeDebounceTime?: number;
regionDidChangeDebounceTime?: number;
+ tintColor?: string;
onPress?: (feature: GeoJSON.Feature) => void;
onLongPress?: (feature: GeoJSON.Feature) => void;
@@ -429,12 +502,13 @@ export interface MapViewProps extends ViewProps {
}
export interface CameraProps extends CameraSettings, ViewProps {
+ allowUpdates?: boolean;
animationDuration?: number;
- animationMode?: 'flyTo' | 'easeTo' | 'moveTo';
+ animationMode?: 'flyTo' | 'easeTo' | 'linearTo' | 'moveTo';
defaultSettings?: CameraSettings;
minZoomLevel?: number;
maxZoomLevel?: number;
- maxBounds?: {ne: [number, number]; sw: [number, number]};
+ maxBounds?: { ne: [number, number]; sw: [number, number] };
followUserLocation?: boolean;
followUserMode?: 'normal' | 'compass' | 'course';
followZoomLevel?: number;
@@ -453,21 +527,25 @@ export interface CameraProps extends CameraSettings, ViewProps {
) => void;
}
+export interface CameraPadding {
+ paddingLeft?: number;
+ paddingRight?: number;
+ paddingTop?: number;
+ paddingBottom?: number;
+}
+
export interface CameraSettings {
centerCoordinate?: GeoJSON.Position;
heading?: number;
pitch?: number;
- bounds?: {
+ padding?: CameraPadding;
+ bounds?: CameraPadding & {
ne: GeoJSON.Position;
sw: GeoJSON.Position;
- paddingLeft?: number;
- paddingRight?: number;
- paddingTop?: number;
- paddingBottom?: number;
};
zoomLevel?: number;
animationDuration?: number;
- animationMode?: 'flyTo' | 'easeTo' | 'moveTo';
+ animationMode?: 'flyTo' | 'easeTo' | 'linearTo' | 'moveTo';
stops?: CameraSettings[];
}
@@ -612,12 +690,13 @@ export interface RasterLayerStyle {
rasterFadeDuration?: number | Expression;
}
-export type TextVariableAnchorValues = "center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
+export type TextVariableAnchorValues = "center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
export interface SymbolLayerStyle {
symbolPlacement?: 'point' | 'line' | Expression;
symbolSpacing?: number | Expression;
symbolAvoidEdges?: boolean | Expression;
+ symbolSortKey?: number | Expression;
symbolZOrder?: 'auto' | 'viewport-y' | 'source' | Expression;
iconAllowOverlap?: boolean | Expression;
iconIgnorePlacement?: boolean | Expression;
@@ -732,7 +811,7 @@ export interface CalloutProps extends Omit {
containerStyle?: StyleProp>;
contentStyle?: StyleProp>;
tipStyle?: StyleProp>;
- textStyle?: StyleProp>;
+ textStyle?: StyleProp>;
}
export interface TileSourceProps extends ViewProps {
@@ -761,7 +840,8 @@ export interface ShapeSourceProps extends ViewProps {
maxZoomLevel?: number;
buffer?: number;
tolerance?: number;
- images?: {assets?: string[]} & {[key: string]: ImageSourcePropType};
+ lineMetrics?: boolean;
+ images?: { assets?: string[] } & { [key: string]: ImageSourcePropType };
onPress?: (event: OnPressEvent) => void;
hitbox?: {
width: number;
@@ -775,7 +855,7 @@ export interface RasterSourceProps extends TileSourceProps {
export interface LayerBaseProps extends Omit {
id: string;
- sourceID?: MapboxGL.StyleSource;
+ sourceID?: string;
sourceLayerID?: string;
aboveLayerID?: string;
belowLayerID?: string;
@@ -793,7 +873,7 @@ export interface CircleLayerProps extends LayerBaseProps {
style?: StyleProp;
}
-export interface FillExtrusionLayerProps extends Omit {
+export interface FillExtrusionLayerProps extends Omit {
id: string;
style?: StyleProp;
}
@@ -819,7 +899,9 @@ export interface HeatmapLayerProps extends LayerBaseProps {
}
export interface ImagesProps extends ViewProps {
- images?: {assets?: string[]} & {[key: string]: ImageSourcePropType};
+ images?: { assets?: string[] } & { [key: string]: ImageSourcePropType };
+ nativeAssetImages?: string[]
+ onImageMissing?: (imageKey: string) => void
}
export interface ImageSourceProps extends ViewProps {
@@ -853,4 +935,20 @@ export interface SnapshotOptions {
writeToDisk?: boolean;
}
+// Logger class
+type LogLevel = "error" | "warning" | "info" | "debug" | "verbose";
+
+interface LogObject {
+ level: LogLevel;
+ message: string;
+ tag: string;
+}
+
+type LogCallback = (object: LogObject) => void;
+
+export class Logger {
+ public static setLogCallback: (cb: LogCallback) => boolean;
+ public static setLogLevel: (level: LogLevel) => void;
+}
+
export default MapboxGL;
diff --git a/ios/RCTMGL/CameraMode.h b/ios/RCTMGL/CameraMode.h
index 46c034af2..b46750698 100644
--- a/ios/RCTMGL/CameraMode.h
+++ b/ios/RCTMGL/CameraMode.h
@@ -12,6 +12,7 @@
extern int const RCT_MAPBOX_CAMERA_MODE_FLIGHT;
extern int const RCT_MAPBOX_CAMERA_MODE_EASE;
+extern int const RCT_MAPBOX_CAMERA_MODE_LINEAR;
extern int const RCT_MAPBOX_CAMERA_MODE_NONE;
@end
diff --git a/ios/RCTMGL/CameraMode.m b/ios/RCTMGL/CameraMode.m
index 8bd67224d..b27df6caf 100644
--- a/ios/RCTMGL/CameraMode.m
+++ b/ios/RCTMGL/CameraMode.m
@@ -12,6 +12,7 @@ @implementation CameraMode
int const RCT_MAPBOX_CAMERA_MODE_FLIGHT = 1;
int const RCT_MAPBOX_CAMERA_MODE_EASE = 2;
-int const RCT_MAPBOX_CAMERA_MODE_NONE = 3;
+int const RCT_MAPBOX_CAMERA_MODE_LINEAR = 3;
+int const RCT_MAPBOX_CAMERA_MODE_NONE = 4;
@end
diff --git a/ios/RCTMGL/CameraStop.h b/ios/RCTMGL/CameraStop.h
index 18386180e..398643542 100644
--- a/ios/RCTMGL/CameraStop.h
+++ b/ios/RCTMGL/CameraStop.h
@@ -19,7 +19,7 @@
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, assign) MGLCoordinateBounds bounds;
-@property (nonatomic, assign) UIEdgeInsets boundsPadding;
+@property (nonatomic, assign) UIEdgeInsets padding;
+ (CameraStop*)fromDictionary:(NSDictionary*)args;
diff --git a/ios/RCTMGL/CameraStop.m b/ios/RCTMGL/CameraStop.m
index 1d40af949..0aaf23c8d 100644
--- a/ios/RCTMGL/CameraStop.m
+++ b/ios/RCTMGL/CameraStop.m
@@ -21,6 +21,8 @@ - (void)setMode:(NSNumber *)mode
_mode = [NSNumber numberWithInt:modeInt];
} else if (modeInt == RCT_MAPBOX_CAMERA_MODE_NONE) {
_mode = [NSNumber numberWithInt:modeInt];
+ } else if (modeInt == RCT_MAPBOX_CAMERA_MODE_LINEAR) {
+ _mode = [NSNumber numberWithInt:modeInt];
} else {
_mode = [NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_EASE];
}
@@ -46,7 +48,7 @@ + (CameraStop*)fromDictionary:(NSDictionary *)args
if (args[@"heading"]) {
stop.heading = args[@"heading"];
}
-
+
if (args[@"centerCoordinate"]) {
stop.coordinate = [RCTMGLUtils fromFeature:args[@"centerCoordinate"]];
}
@@ -61,14 +63,14 @@ + (CameraStop*)fromDictionary:(NSDictionary *)args
if (args[@"bounds"]) {
stop.bounds = [RCTMGLUtils fromFeatureCollection:args[@"bounds"]];
-
- CGFloat paddingTop = args[@"boundsPaddingTop"] ? [args[@"boundsPaddingTop"] floatValue] : 0.0;
- CGFloat paddingRight = args[@"boundsPaddingRight"] ? [args[@"boundsPaddingRight"] floatValue] : 0.0;
- CGFloat paddingBottom = args[@"boundsPaddingBottom"] ? [args[@"boundsPaddingBottom"] floatValue] : 0.0;
- CGFloat paddingLeft = args[@"boundsPaddingLeft"] ? [args[@"boundsPaddingLeft"] floatValue] : 0.0;
- stop.boundsPadding = UIEdgeInsetsMake(paddingTop, paddingLeft, paddingBottom, paddingRight);
}
+ CGFloat paddingTop = args[@"paddingTop"] ? [args[@"paddingTop"] floatValue] : 0.0;
+ CGFloat paddingRight = args[@"paddingRight"] ? [args[@"paddingRight"] floatValue] : 0.0;
+ CGFloat paddingBottom = args[@"paddingBottom"] ? [args[@"paddingBottom"] floatValue] : 0.0;
+ CGFloat paddingLeft = args[@"paddingLeft"] ? [args[@"paddingLeft"] floatValue] : 0.0;
+ stop.padding = UIEdgeInsetsMake(paddingTop, paddingLeft, paddingBottom, paddingRight);
+
NSTimeInterval duration = 2.0;
if (args[@"duration"]) {
duration = [RCTMGLUtils fromMS:args[@"duration"]];
diff --git a/ios/RCTMGL/CameraUpdateItem.m b/ios/RCTMGL/CameraUpdateItem.m
index 515ee0a7b..64018a59b 100644
--- a/ios/RCTMGL/CameraUpdateItem.m
+++ b/ios/RCTMGL/CameraUpdateItem.m
@@ -32,11 +32,11 @@ - (void)execute:(RCTMGLMapView *)mapView withCompletionHandler:(void (^)(void))c
if (_cameraStop.mode == [NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_FLIGHT]) {
[self _flyToCamera:mapView withCompletionHandler:completionHandler];
} else if (_cameraStop.mode == [NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_EASE]) {
- [self _moveCamera:mapView animated:YES withCompletionHandler:completionHandler];
- } else if ([self _areBoundsValid:_cameraStop.bounds]) {
- [self _fitBoundsCamera:mapView withCompletionHandler:completionHandler];
+ [self _moveCamera:mapView animated:YES ease:YES withCompletionHandler:completionHandler];
+ } else if (_cameraStop.mode == [NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_LINEAR]) {
+ [self _moveCamera:mapView animated:YES ease:NO withCompletionHandler:completionHandler];
} else {
- [self _moveCamera:mapView animated:NO withCompletionHandler:completionHandler];
+ [self _moveCamera:mapView animated:NO ease:NO withCompletionHandler:completionHandler];
}
}
@@ -45,25 +45,82 @@ - (void)_flyToCamera:(RCTMGLMapView*)mapView withCompletionHandler:(void (^)(voi
RCTMGLCameraWithPadding *nextCamera = [self _makeCamera:mapView];
if ([mapView respondsToSelector:@selector(_flyToCamera:edgePadding:withDuration:peakAltitude:completionHandler:)]) {
- [mapView _flyToCamera:nextCamera.camera edgePadding:nextCamera.boundsPadding withDuration:_cameraStop.duration peakAltitude:-1 completionHandler:completionHandler];
+ [mapView
+ _flyToCamera:nextCamera.camera
+ edgePadding:nextCamera.boundsPadding
+ withDuration:_cameraStop.duration
+ peakAltitude:-1
+ completionHandler:completionHandler];
} else {
- [mapView flyToCamera:nextCamera.camera withDuration:_cameraStop.duration completionHandler:completionHandler];
+ [mapView
+ flyToCamera:nextCamera.camera
+ withDuration:_cameraStop.duration
+ completionHandler:completionHandler];
}
}
-- (void)_moveCamera:(RCTMGLMapView*)mapView animated:(BOOL)animated withCompletionHandler:(void (^)(void))completionHandler
+- (void)_moveCamera:(RCTMGLMapView*)mapView animated:(BOOL)animated ease:(BOOL)ease withCompletionHandler:(void (^)(void))completionHandler
{
- if ([self _hasCenterCoordAndZoom]) {
- [self _centerCoordWithZoomCamera:mapView animated:animated withCompletionHandler:completionHandler];
- } else {
RCTMGLCameraWithPadding *nextCamera = [self _makeCamera:mapView];
+ NSString *easeFunctionName = ease ? kCAMediaTimingFunctionEaseInEaseOut : kCAMediaTimingFunctionLinear;
[mapView setCamera:nextCamera.camera
withDuration:animated ? _cameraStop.duration : 0
- animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
+ animationTimingFunction:[CAMediaTimingFunction functionWithName:easeFunctionName]
edgePadding:nextCamera.boundsPadding
completionHandler:completionHandler];
+}
+
+- (RCTMGLCameraWithPadding*)_makeCamera:(RCTMGLMapView*)mapView
+{
+ MGLMapCamera *nextCamera = [mapView.camera copy];
+
+ UIEdgeInsets padding = [self _clippedPadding:_cameraStop.padding forView:mapView];
+ if (padding.top <= 0 && padding.bottom <= 0) {
+ // If all padding properties are 0 in the update, and the bounds and centerCoordinate do not
+ // change, the padding doesn't change either. This seems to be a bug in the iOS SDK.
+ padding.top = 1.0;
+ padding.bottom = 1.0;
}
+
+ bool hasSetAltitude = false;
+
+ if ([self _isCoordValid:_cameraStop.coordinate]) {
+ MGLCoordinateBounds boundsFromCoord = { .sw = _cameraStop.coordinate, .ne = _cameraStop.coordinate };
+ MGLMapCamera *boundsCamera = [mapView
+ camera:nextCamera
+ fittingCoordinateBounds:boundsFromCoord
+ edgePadding: padding];
+ nextCamera.centerCoordinate = boundsCamera.centerCoordinate;
+ } else if ([self _areBoundsValid:_cameraStop.bounds]) {
+ MGLMapCamera *boundsCamera = [mapView
+ camera:nextCamera
+ fittingCoordinateBounds:_cameraStop.bounds
+ edgePadding: padding];
+ nextCamera.centerCoordinate = boundsCamera.centerCoordinate;
+ nextCamera.altitude = boundsCamera.altitude;
+ hasSetAltitude = true;
+ }
+
+ if (_cameraStop.pitch != nil) {
+ nextCamera.pitch = [_cameraStop.pitch floatValue];
+ }
+
+ if (_cameraStop.heading != nil) {
+ nextCamera.heading = [_cameraStop.heading floatValue];
+ }
+
+ if (_cameraStop.zoom != nil && hasSetAltitude == false) {
+ nextCamera.altitude = [mapView
+ altitudeFromZoom:[_cameraStop.zoom doubleValue]
+ atLatitude:nextCamera.centerCoordinate.latitude
+ atPitch:nextCamera.pitch];
+ }
+
+ RCTMGLCameraWithPadding* cameraWithPadding = [[RCTMGLCameraWithPadding alloc] init];
+ cameraWithPadding.camera = nextCamera;
+ cameraWithPadding.boundsPadding = padding;
+ return cameraWithPadding;
}
- (UIEdgeInsets)_clippedPadding:(UIEdgeInsets)padding forView:(RCTMGLMapView*)mapView
@@ -84,67 +141,6 @@ - (UIEdgeInsets)_clippedPadding:(UIEdgeInsets)padding forView:(RCTMGLMapView*)ma
return result;
}
-- (void)_fitBoundsCamera:(RCTMGLMapView*)mapView withCompletionHandler:(void (^)(void))completionHandler
-{
- MGLCoordinateBounds bounds = _cameraStop.bounds;
- CLLocationCoordinate2D coordinates[] = {
- { bounds.ne.latitude, bounds.sw.longitude },
- bounds.sw,
- { bounds.sw.latitude, bounds.ne.longitude },
- bounds.ne
- };
-
- [mapView setVisibleCoordinates:coordinates
- count:4
- edgePadding:[self _clippedPadding:_cameraStop.boundsPadding forView:mapView]
- direction:mapView.direction
- duration:_cameraStop.duration
- animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
- completionHandler:completionHandler];
-}
-
-- (void)_centerCoordWithZoomCamera:(RCTMGLMapView*)mapView animated:(BOOL)animated withCompletionHandler:(void (^)(void))completionHandler
-{
- MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:_cameraStop.coordinate
- fromDistance:[mapView altitudeFromZoom:[_cameraStop.zoom doubleValue] atLatitude:_cameraStop.coordinate.latitude]
- pitch:[_cameraStop.pitch floatValue]
- heading:[_cameraStop.heading floatValue]];
- [mapView setCamera:camera
- withDuration:animated ? _cameraStop.duration : 0
- animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
- completionHandler:completionHandler];
-}
-
-- (RCTMGLCameraWithPadding*)_makeCamera:(RCTMGLMapView*)mapView
-{
- MGLMapCamera *nextCamera = [mapView.camera copy];
-
- if (_cameraStop.pitch != nil) {
- nextCamera.pitch = [_cameraStop.pitch floatValue];
- }
-
- if (_cameraStop.heading != nil) {
- nextCamera.heading = [_cameraStop.heading floatValue];
- }
-
- if ([self _isCoordValid:_cameraStop.coordinate]) {
- nextCamera.centerCoordinate = _cameraStop.coordinate;
- } else if ([self _areBoundsValid:_cameraStop.bounds]) {
- MGLMapCamera *boundsCamera = [mapView camera:nextCamera fittingCoordinateBounds:_cameraStop.bounds edgePadding: [self _clippedPadding:_cameraStop.boundsPadding forView:mapView]];
- nextCamera.centerCoordinate = boundsCamera.centerCoordinate;
- nextCamera.altitude = boundsCamera.altitude;
- }
-
- if (_cameraStop.zoom != nil) {
- nextCamera.altitude = [mapView altitudeFromZoom:[_cameraStop.zoom doubleValue] atLatitude:nextCamera.centerCoordinate.latitude atPitch:nextCamera.pitch];
- }
-
- RCTMGLCameraWithPadding* cameraWithPadding = [[RCTMGLCameraWithPadding alloc] init];
- cameraWithPadding.camera = nextCamera;
- cameraWithPadding.boundsPadding = [self _clippedPadding:_cameraStop.boundsPadding forView:mapView];
- return cameraWithPadding;
-}
-
- (BOOL)_areBoundsValid:(MGLCoordinateBounds)bounds {
BOOL isValid = CLLocationCoordinate2DIsValid(bounds.ne) && CLLocationCoordinate2DIsValid(bounds.sw);
diff --git a/ios/RCTMGL/MGLModule.m b/ios/RCTMGL/MGLModule.m
index d9a795000..4526c82fe 100644
--- a/ios/RCTMGL/MGLModule.m
+++ b/ios/RCTMGL/MGLModule.m
@@ -27,12 +27,20 @@ + (BOOL)requiresMainQueueSetup
{
// style urls
NSMutableDictionary *styleURLS = [[NSMutableDictionary alloc] init];
+
+#ifdef RNMGL_USE_MAPLIBRE
+ for (MGLDefaultStyle* style in [MGLStyle predefinedStyles]) {
+ [styleURLS setObject:[style.url absoluteString] forKey:style.name];
+ }
+ [styleURLS setObject:[[MGLStyle defaultStyleURL] absoluteString] forKey:@"Default"];
+#else
[styleURLS setObject:[MGLStyle.streetsStyleURL absoluteString] forKey:@"Street"];
[styleURLS setObject:[MGLStyle.darkStyleURL absoluteString] forKey:@"Dark"];
[styleURLS setObject:[MGLStyle.lightStyleURL absoluteString] forKey:@"Light"];
[styleURLS setObject:[MGLStyle.outdoorsStyleURL absoluteString] forKey:@"Outdoors"];
[styleURLS setObject:[MGLStyle.satelliteStyleURL absoluteString] forKey:@"Satellite"];
[styleURLS setObject:[MGLStyle.satelliteStreetsStyleURL absoluteString] forKey:@"SatelliteStreet"];
+#endif
// event types
NSMutableDictionary *eventTypes = [[NSMutableDictionary alloc] init];
@@ -73,6 +81,7 @@ + (BOOL)requiresMainQueueSetup
NSMutableDictionary *cameraModes = [[NSMutableDictionary alloc] init];
[cameraModes setObject:[NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_FLIGHT] forKey:@"Flight"];
[cameraModes setObject:[NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_EASE] forKey:@"Ease"];
+ [cameraModes setObject:[NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_LINEAR] forKey:@"Linear"];
[cameraModes setObject:[NSNumber numberWithInt:RCT_MAPBOX_CAMERA_MODE_NONE] forKey:@"None"];
// style sources
@@ -239,7 +248,13 @@ + (BOOL)requiresMainQueueSetup
RCT_EXPORT_METHOD(setAccessToken:(NSString *)accessToken)
{
+#ifdef RNMGL_USE_MAPLIBRE
+ if (accessToken.length > 0) {
+ [MGLSettings setApiKey:accessToken];
+ }
+#else
[MGLAccountManager setAccessToken:accessToken];
+#endif
}
RCT_EXPORT_METHOD(addCustomHeader:(NSString *)headerName forHeaderValue:(NSString *) headerValue)
@@ -254,7 +269,11 @@ + (BOOL)requiresMainQueueSetup
RCT_EXPORT_METHOD(getAccessToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
+#ifdef RNMGL_USE_MAPLIBRE
+ NSString* accessToken = MGLSettings.apiKey;
+#else
NSString *accessToken = MGLAccountManager.accessToken;
+#endif
if (accessToken != nil) {
resolve(accessToken);
diff --git a/ios/RCTMGL/RCTMGLCallout.m b/ios/RCTMGL/RCTMGLCallout.m
index c6c5e137a..5e995c633 100644
--- a/ios/RCTMGL/RCTMGLCallout.m
+++ b/ios/RCTMGL/RCTMGLCallout.m
@@ -7,7 +7,7 @@
//
#import "RCTMGLCallout.h"
-#import "UIView+React.h"
+#import
@implementation RCTMGLCallout
{
diff --git a/ios/RCTMGL/RCTMGLImages.m b/ios/RCTMGL/RCTMGLImages.m
index d0d42023d..2738d0ec7 100644
--- a/ios/RCTMGL/RCTMGLImages.m
+++ b/ios/RCTMGL/RCTMGLImages.m
@@ -1,5 +1,5 @@
#import "RCTMGLImages.h"
-#import "UIView+React.h"
+#import
#import "RCTMGLMapView.h"
#import "RCTMGLUtils.h"
#import "RCTMGLEvent.h"
diff --git a/ios/RCTMGL/RCTMGLLocation.m b/ios/RCTMGL/RCTMGLLocation.m
index bb0ec9406..bce7a2070 100644
--- a/ios/RCTMGL/RCTMGLLocation.m
+++ b/ios/RCTMGL/RCTMGLLocation.m
@@ -20,6 +20,7 @@ @implementation RCTMGLLocation
coords[@"altitude"] = @(_location.altitude);
coords[@"accuracy"] = @(_location.horizontalAccuracy);
coords[@"heading"] = @(_heading.trueHeading);
+ coords[@"course"] = @(_location.course);
coords[@"speed"] = @(_location.speed);
json[@"coords"] = coords;
diff --git a/ios/RCTMGL/RCTMGLLogging.m b/ios/RCTMGL/RCTMGLLogging.m
index 0a06b0052..8516b3308 100644
--- a/ios/RCTMGL/RCTMGLLogging.m
+++ b/ios/RCTMGL/RCTMGLLogging.m
@@ -2,6 +2,10 @@
@import Mapbox;
+@interface RCTMGLLogging()
+@property (nonatomic) BOOL hasListeners;
+@end
+
@implementation RCTMGLLogging
+ (id)allocWithZone:(NSZone *)zone {
@@ -37,8 +41,22 @@ + (BOOL)requiresMainQueueSetup
return @[@"LogEvent"];
}
+- (void)startObserving
+{
+ [super startObserving];
+ self.hasListeners = true;
+}
+
+- (void)stopObserving
+{
+ [super stopObserving];
+ self.hasListeners = false;
+}
+
- (void)sendLogWithLevel:(MGLLoggingLevel)loggingLevel filePath:(NSString*)filePath line:(NSUInteger)line message:(NSString*)message
{
+ if (!self.hasListeners) return;
+
NSString* level = @"n/a";
switch (loggingLevel) {
case MGLLoggingLevelInfo:
diff --git a/ios/RCTMGL/RCTMGLMapView.m b/ios/RCTMGL/RCTMGLMapView.m
index bbaab06d2..b439cc78b 100644
--- a/ios/RCTMGL/RCTMGLMapView.m
+++ b/ios/RCTMGL/RCTMGLMapView.m
@@ -11,7 +11,7 @@
#import "RCTMGLUtils.h"
#import "RNMBImageUtils.h"
#import "RCTMGLImages.h"
-#import "UIView+React.h"
+#import
#import "RCTMGLNativeUserLocation.h"
#import "RCTMGLLogging.h"
@@ -301,6 +301,31 @@ - (void)setReactLogoEnabled:(BOOL)reactLogoEnabled
self.logoView.hidden = !_reactLogoEnabled;
}
+- (void)setReactLogoPosition:(NSDictionary *)logoPosition
+{
+ NSNumber *left = [logoPosition valueForKey:@"left"];
+ NSNumber *right = [logoPosition valueForKey:@"right"];
+ NSNumber *top = [logoPosition valueForKey:@"top"];
+ NSNumber *bottom = [logoPosition valueForKey:@"bottom"];
+ if (left != nil && top != nil) {
+ [self setLogoViewPosition:MGLOrnamentPositionTopLeft];
+ [self setLogoViewMargins:CGPointMake([left floatValue], [top floatValue])];
+ } else if (right != nil && top != nil) {
+ [self setLogoViewPosition:MGLOrnamentPositionTopRight];
+ [self setLogoViewMargins:CGPointMake([right floatValue], [top floatValue])];
+ } else if (bottom != nil && right != nil) {
+ [self setLogoViewPosition:MGLOrnamentPositionBottomRight];
+ [self setLogoViewMargins:CGPointMake([right floatValue], [bottom floatValue])];
+ } else if (bottom != nil && left != nil) {
+ [self setLogoViewPosition:MGLOrnamentPositionBottomLeft];
+ [self setLogoViewMargins:CGPointMake([left floatValue], [bottom floatValue])];
+ } else {
+ [self setLogoViewPosition:MGLOrnamentPositionBottomRight];
+ [self setLogoViewMargins:CGPointMake(8, 8)];
+ }
+
+}
+
- (void)setReactCompassEnabled:(BOOL)reactCompassEnabled
{
_reactCompassEnabled = reactCompassEnabled;
@@ -485,7 +510,13 @@ - (RCTMGLSource *)getTouchableSourceWithHighestZIndex:(NSArray *
- (NSURL*)_getStyleURLFromKey:(NSString *)styleURL
{
- return [NSURL URLWithString:styleURL];
+ NSURL *url = [NSURL URLWithString:styleURL];
+ if (url) {
+ return url;
+ } else if (RCTJSONParse(styleURL, nil)) {
+ return [RCTMGLUtils styleURLFromStyleJSON:styleURL];
+ }
+ return url;
}
- (void)_removeAllSourcesFromMap
diff --git a/ios/RCTMGL/RCTMGLMapViewManager.m b/ios/RCTMGL/RCTMGLMapViewManager.m
index c456ff581..d7d298583 100644
--- a/ios/RCTMGL/RCTMGLMapViewManager.m
+++ b/ios/RCTMGL/RCTMGLMapViewManager.m
@@ -80,6 +80,7 @@ - (UIView *)view
RCT_REMAP_VIEW_PROPERTY(attributionEnabled, reactAttributionEnabled, BOOL)
RCT_REMAP_VIEW_PROPERTY(attributionPosition, reactAttributionPosition, NSDictionary)
RCT_REMAP_VIEW_PROPERTY(logoEnabled, reactLogoEnabled, BOOL)
+RCT_REMAP_VIEW_PROPERTY(logoPosition, reactLogoPosition, NSDictionary)
RCT_REMAP_VIEW_PROPERTY(compassEnabled, reactCompassEnabled, BOOL)
RCT_REMAP_VIEW_PROPERTY(zoomEnabled, reactZoomEnabled, BOOL)
@@ -490,7 +491,8 @@ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView
- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated
{
- if ((reason & MGLCameraChangeReasonTransitionCancelled) == MGLCameraChangeReasonTransitionCancelled) return;
+ if (((reason & MGLCameraChangeReasonTransitionCancelled) == MGLCameraChangeReasonTransitionCancelled)
+ && ((reason & MGLCameraChangeReasonGesturePan) != MGLCameraChangeReasonGesturePan)) return;
((RCTMGLMapView *) mapView).isUserInteraction = (BOOL)(reason & ~MGLCameraChangeReasonProgrammatic);
diff --git a/ios/RCTMGL/RCTMGLPointAnnotation.m b/ios/RCTMGL/RCTMGLPointAnnotation.m
index e68501460..57621c3b1 100644
--- a/ios/RCTMGL/RCTMGLPointAnnotation.m
+++ b/ios/RCTMGL/RCTMGLPointAnnotation.m
@@ -9,7 +9,7 @@
#import "RCTMGLPointAnnotation.h"
#import "RCTMGLMapTouchEvent.h"
#import "RCTMGLUtils.h"
-#import "UIView+React.h"
+#import
const float CENTER_X_OFFSET_BASE = -0.5f;
const float CENTER_Y_OFFSET_BASE = -0.5f;
@@ -162,20 +162,19 @@ - (void)_setCenterOffset:(CGRect)frame
float x = [_anchor[@"x"] floatValue];
float y = [_anchor[@"y"] floatValue];
- // (fullWidthOffset - centerWidthOffset) / 2
- float dx = -(x * frame.size.width - (frame.size.width / 2)) / 2;
- float dy = -(y * frame.size.height - (frame.size.height / 2)) / 2;
+ float dx = -(x * frame.size.width - (frame.size.width / 2));
+ float dy = -(y * frame.size.height - (frame.size.height / 2));
// special cases 0 and 1
if (x == 0) {
dx = frame.size.width / 2;
} else if (x == 1) {
- dy = -frame.size.height / 2;
+ dx = -frame.size.width / 2;
}
if (y == 0) {
- dy = frame.size.width / 2;
+ dy = frame.size.height / 2;
} else if (y == 1) {
dy = -frame.size.height / 2;
}
diff --git a/ios/RCTMGL/RCTMGLShapeSource.h b/ios/RCTMGL/RCTMGLShapeSource.h
index 816bdecc9..ffa933e06 100644
--- a/ios/RCTMGL/RCTMGLShapeSource.h
+++ b/ios/RCTMGL/RCTMGLShapeSource.h
@@ -25,8 +25,28 @@
@property (nonatomic, strong) NSNumber *maxZoomLevel;
@property (nonatomic, strong) NSNumber *buffer;
@property (nonatomic, strong) NSNumber *tolerance;
+@property (nonatomic, strong) NSNumber *lineMetrics;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, assign) BOOL hasPressListener;
+- (nonnull NSArray> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate;
+- (nonnull NSArray> *)getClusterLeaves:(nonnull NSString *)featureJSON
+ number:(NSUInteger)number
+ offset:(NSUInteger)offset;
+- (nonnull NSArray> *)getClusterChildren:(nonnull NSString *)featureJSON;
+
+- (double)getClusterExpansionZoom:(nonnull NSString *)featureJSON;
+
+// Deprecated. Will be removed in 9+ ver.
+- (nonnull NSArray> *)getClusterLeavesById:(nonnull NSNumber *)clusterId
+ number:(NSUInteger)number
+ offset:(NSUInteger)offset;
+
+// Deprecated. Will be removed in 9+ ver.
+- (nonnull NSArray> *)getClusterChildrenById:(nonnull NSNumber *)clusterId;
+
+// Deprecated. Will be removed in 9+ ver.
+- (double)getClusterExpansionZoomById:(nonnull NSNumber *)clusterId;
+
@end
diff --git a/ios/RCTMGL/RCTMGLShapeSource.m b/ios/RCTMGL/RCTMGLShapeSource.m
index fe55f6f49..d6b4a9888 100644
--- a/ios/RCTMGL/RCTMGLShapeSource.m
+++ b/ios/RCTMGL/RCTMGLShapeSource.m
@@ -26,7 +26,7 @@ - (void)setUrl: (NSString*) url
- (void)setShape:(NSString *)shape
{
_shape = shape;
-
+
if (self.source != nil) {
MGLShapeSource *source = (MGLShapeSource *)self.source;
[source setShape: shape == nil ? nil : [RCTMGLUtils shapeFromGeoJSON:_shape]];
@@ -46,19 +46,19 @@ - (void)removeFromMap
if (self.map.style == nil) {
return;
}
-
+
[super removeFromMap];
}
- (nullable MGLSource*)makeSource
{
NSDictionary *options = [self _getOptions];
-
+
if (_shape != nil) {
MGLShape *shape = [RCTMGLUtils shapeFromGeoJSON:_shape];
return [[MGLShapeSource alloc] initWithIdentifier:self.id shape:shape options:options];
}
-
+
if (_url != nil) {
NSURL *url = [[NSURL alloc] initWithString:_url];
return [[MGLShapeSource alloc] initWithIdentifier:self.id URL:url options:options];
@@ -69,32 +69,112 @@ - (nullable MGLSource*)makeSource
- (NSDictionary*)_getOptions
{
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
-
+
if (_cluster != nil) {
options[MGLShapeSourceOptionClustered] = [NSNumber numberWithBool:[_cluster intValue] == 1];
}
-
+
if (_clusterRadius != nil) {
options[MGLShapeSourceOptionClusterRadius] = _clusterRadius;
}
-
+
if (_clusterMaxZoomLevel != nil) {
options[MGLShapeSourceOptionMaximumZoomLevelForClustering] = _clusterMaxZoomLevel;
}
-
+
if (_maxZoomLevel != nil) {
options[MGLShapeSourceOptionMaximumZoomLevel] = _maxZoomLevel;
}
-
+
if (_buffer != nil) {
options[MGLShapeSourceOptionBuffer] = _buffer;
}
-
+
if (_tolerance != nil) {
options[MGLShapeSourceOptionSimplificationTolerance] = _tolerance;
}
-
+
+ if (_lineMetrics != nil) {
+ options[MGLShapeSourceOptionLineDistanceMetrics] = _lineMetrics;
+ }
+
return options;
}
+- (nonnull NSArray> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ return [shapeSource featuresMatchingPredicate:predicate];
+}
+
+- (double)getClusterExpansionZoom:(nonnull NSString *)featureJSON
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ MGLPointFeature *feature = (MGLPointFeature*)[RCTMGLUtils shapeFromGeoJSON:featureJSON];
+
+ return [shapeSource zoomLevelForExpandingCluster:(MGLPointFeatureCluster *)feature];
+}
+
+- (nonnull NSArray> *)getClusterLeaves:(nonnull NSString *)featureJSON
+ number:(NSUInteger)number
+ offset:(NSUInteger)offset
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ MGLPointFeature *feature = (MGLPointFeature*)[RCTMGLUtils shapeFromGeoJSON:featureJSON];
+
+ MGLPointFeatureCluster * cluster = (MGLPointFeatureCluster *)feature;
+ return [shapeSource leavesOfCluster:cluster offset:offset limit:number];
+}
+
+- (nonnull NSArray> *)getClusterChildren:(nonnull NSString *)featureJSON
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ MGLPointFeature *feature = (MGLPointFeature*)[RCTMGLUtils shapeFromGeoJSON:featureJSON];
+
+ MGLPointFeatureCluster * cluster = (MGLPointFeatureCluster *)feature;
+ return [shapeSource childrenOfCluster:cluster];
+}
+
+
+// Deprecated. Will be removed in 9+ ver.
+- (double)getClusterExpansionZoomById:(nonnull NSNumber *)clusterId
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+ NSArray> *features = [shapeSource featuresMatchingPredicate: [NSPredicate predicateWithFormat:@"%K = %i", @"cluster_id", clusterId.intValue]];
+ if (features.count == 0) {
+ return -1;
+ }
+ return [shapeSource zoomLevelForExpandingCluster:(MGLPointFeatureCluster *)features[0]];
+}
+
+// Deprecated. Will be removed in 9+ ver.
+- (nonnull NSArray> *)getClusterLeavesById:(nonnull NSNumber *)clusterId
+ number:(NSUInteger)number
+ offset:(NSUInteger)offset
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ NSPredicate* predicate = [NSPredicate predicateWithFormat:@"cluster_id == %@", clusterId];
+ NSArray> *features = [shapeSource featuresMatchingPredicate:predicate];
+
+ MGLPointFeatureCluster * cluster = (MGLPointFeatureCluster *)features[0];
+ return [shapeSource leavesOfCluster:cluster offset:offset limit:number];
+}
+
+// Deprecated. Will be removed in 9+ ver.
+- (nonnull NSArray> *)getClusterChildrenById:(nonnull NSNumber *)clusterId
+{
+ MGLShapeSource *shapeSource = (MGLShapeSource *)self.source;
+
+ NSPredicate* predicate = [NSPredicate predicateWithFormat:@"cluster_id == %@", clusterId];
+ NSArray> *features = [shapeSource featuresMatchingPredicate:predicate];
+
+ MGLPointFeatureCluster * cluster = (MGLPointFeatureCluster *)features[0];
+ return [shapeSource childrenOfCluster:cluster];
+}
+
@end
diff --git a/ios/RCTMGL/RCTMGLShapeSourceManager.h b/ios/RCTMGL/RCTMGLShapeSourceManager.h
index 571393a44..4fde63538 100644
--- a/ios/RCTMGL/RCTMGLShapeSourceManager.h
+++ b/ios/RCTMGL/RCTMGLShapeSourceManager.h
@@ -7,7 +7,8 @@
//
#import "ViewManager.h"
+#import
-@interface RCTMGLShapeSourceManager : ViewManager
+@interface RCTMGLShapeSourceManager : ViewManager
@end
diff --git a/ios/RCTMGL/RCTMGLShapeSourceManager.m b/ios/RCTMGL/RCTMGLShapeSourceManager.m
index 55b40cee2..a99fa8204 100644
--- a/ios/RCTMGL/RCTMGLShapeSourceManager.m
+++ b/ios/RCTMGL/RCTMGLShapeSourceManager.m
@@ -5,13 +5,16 @@
// Created by Nick Italiano on 9/19/17.
// Copyright Β© 2017 Mapbox Inc. All rights reserved.
//
+#import
#import "RCTMGLShapeSourceManager.h"
#import "RCTMGLShapeSource.h"
+#import "FilterParser.h"
+
@implementation RCTMGLShapeSourceManager
-RCT_EXPORT_MODULE()
+RCT_EXPORT_MODULE(RCTMGLShapeSource)
RCT_EXPORT_VIEW_PROPERTY(id, NSString)
RCT_EXPORT_VIEW_PROPERTY(url, NSString)
@@ -23,6 +26,7 @@ @implementation RCTMGLShapeSourceManager
RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(buffer, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(tolerance, NSNumber)
+RCT_EXPORT_VIEW_PROPERTY(lineMetrics, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(images, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(nativeImages, NSArray)
RCT_EXPORT_VIEW_PROPERTY(hasPressListener, BOOL)
@@ -36,4 +40,166 @@ - (UIView*)view
return source;
}
+RCT_EXPORT_METHOD(features:(nonnull NSNumber*)reactTag
+ withFilter:(NSArray *)filter
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = viewRegistry[reactTag];
+
+ if (![shapeSource isKindOfClass:[RCTMGLShapeSource class]]) {
+ RCTLogError(@"Invalid react tag, could not find RCTMGLMapView");
+ return;
+ }
+
+ NSPredicate* predicate = [FilterParser parse:filter];
+ NSArray> *shapes = [shapeSource featuresMatchingPredicate: predicate];
+
+ NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:shapes.count];
+ for (int i = 0; i < shapes.count; i++) {
+ [features addObject:shapes[i].geoJSONDictionary];
+ }
+
+ resolve(@{
+ @"data": @{ @"type": @"FeatureCollection", @"features": features }
+ });
+ }];
+}
+
+RCT_EXPORT_METHOD(getClusterExpansionZoom:(nonnull NSNumber*)reactTag
+ featureJSON:(nonnull NSString*)featureJSON
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ if (![shapeSource isKindOfClass:[RCTMGLShapeSource class]]) {
+ RCTLogError(@"Invalid react tag, could not find RCTMGLMapView");
+ return;
+ }
+
+ double zoom = [shapeSource getClusterExpansionZoom: featureJSON];
+ if (zoom == -1) {
+ reject(@"zoom_error", [NSString stringWithFormat:@"Could not get zoom for cluster %@", featureJSON], nil);
+ return;
+ }
+ resolve(@{@"data":@(zoom)});
+ }];
+}
+
+
+RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber*)reactTag
+ featureJSON:(nonnull NSString*)featureJSON
+ number:(NSUInteger) number
+ offset:(NSUInteger) offset
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ NSArray> *shapes = [shapeSource getClusterLeaves:featureJSON number:number offset:offset];
+
+ NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:shapes.count];
+ for (int i = 0; i < shapes.count; i++) {
+ [features addObject:shapes[i].geoJSONDictionary];
+ }
+
+ resolve(@{
+ @"data": @{ @"type": @"FeatureCollection", @"features": features }
+ });
+ }];
+}
+
+RCT_EXPORT_METHOD(getClusterChildren:(nonnull NSNumber*)reactTag
+ featureJSON:(nonnull NSString*)featureJSON
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ NSArray> *shapes = [shapeSource getClusterChildren: featureJSON];
+
+ NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:shapes.count];
+ for (int i = 0; i < shapes.count; i++) {
+ [features addObject:shapes[i].geoJSONDictionary];
+ }
+
+ resolve(@{
+ @"data": @{ @"type": @"FeatureCollection", @"features": features }
+ });
+ }];
+}
+
+// Deprecated. Will be removed in 9+ ver.
+RCT_EXPORT_METHOD(getClusterExpansionZoomById:(nonnull NSNumber*)reactTag
+ clusterId:(nonnull NSNumber*)clusterId
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ if (![shapeSource isKindOfClass:[RCTMGLShapeSource class]]) {
+ RCTLogError(@"Invalid react tag, could not find RCTMGLMapView");
+ return;
+ }
+
+ double zoom = [shapeSource getClusterExpansionZoomById:clusterId];
+ if (zoom == -1) {
+ reject(@"zoom_error", [NSString stringWithFormat:@"Could not get zoom for cluster id %@", clusterId], nil);
+ return;
+ }
+ resolve(@{@"data":@(zoom)});
+ }];
+}
+
+// Deprecated. Will be removed in 9+ ver.
+RCT_EXPORT_METHOD(getClusterLeavesById:(nonnull NSNumber*)reactTag
+ clusterId:(nonnull NSNumber *)clusterId
+ number:(NSUInteger) number
+ offset:(NSUInteger) offset
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ NSArray> *shapes = [shapeSource getClusterLeavesById:clusterId number:number offset:offset];
+
+ NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:shapes.count];
+ for (int i = 0; i < shapes.count; i++) {
+ [features addObject:shapes[i].geoJSONDictionary];
+ }
+
+ resolve(@{
+ @"data": @{ @"type": @"FeatureCollection", @"features": features }
+ });
+ }];
+}
+// Deprecated. Will be removed in 9+ ver.
+RCT_EXPORT_METHOD(getClusterChildrenById:(nonnull NSNumber*)reactTag
+ clusterId:(nonnull NSNumber *)clusterId
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *manager, NSDictionary *viewRegistry) {
+ RCTMGLShapeSource* shapeSource = (RCTMGLShapeSource *)viewRegistry[reactTag];
+
+ NSArray> *shapes = [shapeSource getClusterChildrenById: clusterId];
+
+ NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:shapes.count];
+ for (int i = 0; i < shapes.count; i++) {
+ [features addObject:shapes[i].geoJSONDictionary];
+ }
+
+ resolve(@{
+ @"data": @{ @"type": @"FeatureCollection", @"features": features }
+ });
+ }];
+}
+
@end
diff --git a/ios/RCTMGL/RCTMGLSource.m b/ios/RCTMGL/RCTMGLSource.m
index 988660290..fa2e7ee96 100644
--- a/ios/RCTMGL/RCTMGLSource.m
+++ b/ios/RCTMGL/RCTMGLSource.m
@@ -7,7 +7,7 @@
//
#import "RCTMGLSource.h"
-#import "UIView+React.h"
+#import
#import "RCTMGLMapView.h"
#import
diff --git a/ios/RCTMGL/RCTMGLSymbolLayer.m b/ios/RCTMGL/RCTMGLSymbolLayer.m
index 5c7df3096..475cb45c4 100644
--- a/ios/RCTMGL/RCTMGLSymbolLayer.m
+++ b/ios/RCTMGL/RCTMGLSymbolLayer.m
@@ -8,7 +8,7 @@
#import "RCTMGLSymbolLayer.h"
#import "RCTMGLStyle.h"
-#import "UIView+React.h"
+#import
#import
@implementation RCTMGLSymbolLayer
diff --git a/ios/RCTMGL/RCTMGLUtils.h b/ios/RCTMGL/RCTMGLUtils.h
index b2d843fe2..d32381eab 100644
--- a/ios/RCTMGL/RCTMGLUtils.h
+++ b/ios/RCTMGL/RCTMGLUtils.h
@@ -26,5 +26,6 @@
+ (void)fetchImages:(RCTBridge *)bridge style:(MGLStyle *)style objects:(NSDictionary*)objects forceUpdate:(BOOL)forceUpdate callback:(void (^)(void))callback;
+ (CGVector)toCGVector:(NSArray*)arr;
+ (UIEdgeInsets)toUIEdgeInsets:(NSArray *)arr;
++ (NSURL*)styleURLFromStyleJSON:(NSString *)styleJSON;
@end
diff --git a/ios/RCTMGL/RCTMGLUtils.m b/ios/RCTMGL/RCTMGLUtils.m
index ad74c23aa..1dca4bed4 100644
--- a/ios/RCTMGL/RCTMGLUtils.m
+++ b/ios/RCTMGL/RCTMGLUtils.m
@@ -141,4 +141,78 @@ + (void)fetchImages:(RCTBridge *)bridge style:(MGLStyle *)style objects:(NSDicti
}
}
++ (NSString*)getStyleJsonTempDirectory
+{
+ static NSString *styleJsonTempDirectory;
+ if (!styleJsonTempDirectory) {
+ styleJsonTempDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"RCTMGLStyleJSON"];
+ }
+ return styleJsonTempDirectory;
+}
+
+/**
+ * Clears cached style-json entries from previous app runs. Can be safely called multiple times as it will
+ * only perform the action once per app run.
+ *
+ * @see styleURLFromStyleJSON:
+ */
++ (void)cleanCustomStyleJSONCacheIfNeeded
+{
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *styleJsonTempDirectory = [RCTMGLUtils getStyleJsonTempDirectory];
+
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ if ([fileManager fileExistsAtPath:styleJsonTempDirectory]) {
+ [fileManager removeItemAtPath:styleJsonTempDirectory error:NULL];
+ }
+ });
+}
+
+/**
+ * Provides a way to convert raw style-json into a file so it can be directly referenced / used as styleURL.
+ * It's a crude / alternative approach to support Android's API: Style.Builder.fromJson().
+ */
++ (NSURL*)styleURLFromStyleJSON:(NSString *)styleJSON
+{
+ [RCTMGLUtils cleanCustomStyleJSONCacheIfNeeded];
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *styleJsonTempDirectory = [RCTMGLUtils getStyleJsonTempDirectory];
+
+ // attempt to create the temporary directory
+ if (![fileManager fileExistsAtPath:styleJsonTempDirectory]) {
+ NSError *error;
+ [fileManager createDirectoryAtPath:styleJsonTempDirectory
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error];
+ if (error) {
+ RCTLogError(@"Failed to create temporary directory '%@' for storing style-json. Error: %@", styleJsonTempDirectory, error);
+ return nil;
+ }
+ }
+
+ // Determine filename based on the md5 hash of the style-json.
+ // This way, the written file can also act as a cached entry in case
+ // the same style-json is used again.
+ NSString *hashedFilename = [RCTMD5Hash(styleJSON) stringByAppendingPathExtension:@"json"];
+
+ // Construct temporary file path (tempdir + md5 hash for filename)
+ NSString *styleJsonTempPath = [styleJsonTempDirectory stringByAppendingPathComponent:hashedFilename];
+ NSURL* styleJsonTempURL = [NSURL fileURLWithPath:styleJsonTempPath isDirectory:false];
+
+ // Write style-json to temporary file in case it doesn't already exist
+ if (![fileManager fileExistsAtPath:styleJsonTempPath isDirectory:false]) {
+ NSError *error;
+ [styleJSON writeToURL:styleJsonTempURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
+ if (error) {
+ RCTLogError(@"Failed to write style-json to temporary file '%@'. Error: %@", styleJsonTempURL, error);
+ return nil;
+ }
+ }
+
+ return styleJsonTempURL;
+}
+
@end
diff --git a/ios/install.md b/ios/install.md
index 2910a012f..7eed9cfbb 100644
--- a/ios/install.md
+++ b/ios/install.md
@@ -2,33 +2,108 @@
## React-Native > `0.60.0`
-If you are using autolinking feature introduced in React-Native `0.60.0`, you just need `npm install @react-native-mapbox-gl/maps`, followed by `pod install` from the `ios` directory.
+The following assumes, that you're using autolinking and installed
-## Using CocoaPods
+`@react-native-mapbox-gl/maps` via `npm` or `yarn`.
-To install with CocoaPods, add the following to your `Podfile`:
+
+
+The following is required for every following setup
+
+Add the following to your `ios/Podfile`:
```ruby
- # Mapbox
- pod 'react-native-mapbox-gl', :path => '../node_modules/@react-native-mapbox-gl/maps'
+ pre_install do |installer|
+ $RNMBGL.pre_install(installer)
+ ... other pre install hooks
+ end
+```
+```ruby
+ post_install do |installer|
+ $RNMBGL.post_install(installer)
+ ... other post install hooks
+ end
```
-Then run `pod install` and rebuild your project.
+Running `pod install` will add Mapbox iOS SDK `5.8.0`
+
+```sh
+# Go to the ios folder
+cd ios
+
+# Run Pod Install
+pod install
+```
-## use_frameworks!
+You are good to go!
-Mapbox normally [requires](https://github.com/mapbox/mapbox-gl-native-ios/issues/154) `use_frameworks!` in cocoapods. By default we implement a [workaround](https://github.com/react-native-mapbox-gl/maps/pull/714). In case you need `use_frameworks!` for some reason, you can use the mapbox pod without the workaround with the `DynamicLibrary` subspec:
+Read on if you want to edit your Mapbox version or flavor.
+
+
+## Mapbox Maps SDK
+
+It is possible to set a custom version of the Mapbox SDK:
+
+### New version - since `8.1rc5`
+
+Add the following to you `ios/Podfile`:
```ruby
- # Mapbox
- pod 'react-native-mapbox-gl/DynamicLibrary', :path => '../node_modules/@react-native-mapbox-gl/maps'
+$ReactNativeMapboxGLIOSVersion = '~> 6.1'
+```
+
+Check the current version of the SDK [here](https://docs.mapbox.com/ios/maps/overview/).
+
+### Mapbox Maps SDK > `v6.0.0`
+
+If you are using version `v6.0.0` of the SDK or later, you will need to authorize your download of the Maps SDK with a secret access token with the `DOWNLOADS:READ` scope. This [guide](https://docs.mapbox.com/ios/maps/guides/install/#configure-credentials) explains how to configure the secret token under section `Configure your secret token`.
+
+
- ...
+## Maplibre
- use_frameworks!
+[MapLibre](https://github.com/maplibre/maplibre-gl-native) is an OSS fork of MapboxGL
+Current default MapLibre version is `5.12.0`
+
+If you want to use that, simply add this to your `ios/Podfile`
+
+```ruby
+$RNMBGL_Use_SPM = true
+$RNMGL_USE_MAPLIBRE = true
+```
+
+If you want to adjust/ edit your MapLibre version you can also pass a hash
+
+Example overwrite within your `ios/Podfile`:
+
+```ruby
+$RNMBGL_Use_SPM = {
+ url: "https://github.com/maplibre/maplibre-gl-native-distribution",
+ requirement: {
+ kind: "upToNextMajorVersion",
+ minimumVersion: "5.12.0"
+ },
+ product_name: "Mapbox"
+}
+$RNMGL_USE_MAPLIBRE = true
```
+
+
+## React-Native < `0.60.0`
+
+### Using CocoaPods without autolink
+
+To install with CocoaPods, add the following to your `Podfile`:
+
+```ruby
+ # Mapbox
+ pod 'react-native-mapbox-gl', :path => '../node_modules/@react-native-mapbox-gl/maps'
+
+```
+
+Then run `pod install` and rebuild your project.
diff --git a/javascript/components/BackgroundLayer.js b/javascript/components/BackgroundLayer.js
index 85ad49539..e1cf1cf9a 100644
--- a/javascript/components/BackgroundLayer.js
+++ b/javascript/components/BackgroundLayer.js
@@ -21,7 +21,9 @@ class BackgroundLayer extends AbstractLayer {
id: PropTypes.string.isRequired,
/**
- * The source from which to obtain the data to style. If the source has not yet been added to the current style, the behavior is undefined.
+ * The source from which to obtain the data to style.
+ * If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
diff --git a/javascript/components/Camera.js b/javascript/components/Camera.js
index 91b69d29b..4dd55ea03 100644
--- a/javascript/components/Camera.js
+++ b/javascript/components/Camera.js
@@ -15,6 +15,31 @@ const SettingsPropTypes = {
*/
centerCoordinate: PropTypes.arrayOf(PropTypes.number),
+ /**
+ * Padding around edges of map in points
+ */
+ padding: PropTypes.shape({
+ /**
+ * Left padding in points
+ */
+ paddingLeft: PropTypes.number,
+
+ /**
+ * Right padding in points
+ */
+ paddingRight: PropTypes.number,
+
+ /**
+ * Top padding in points
+ */
+ paddingTop: PropTypes.number,
+
+ /**
+ * Bottom padding in points
+ */
+ paddingBottom: PropTypes.number,
+ }),
+
/**
* Heading on map
*/
@@ -27,6 +52,7 @@ const SettingsPropTypes = {
/**
* Represents a rectangle in geographical coordinates marking the visible area of the map.
+ * The `bounds.padding*` properties are deprecated; use root `padding` property instead.
*/
bounds: PropTypes.shape({
/**
@@ -40,31 +66,31 @@ const SettingsPropTypes = {
sw: PropTypes.arrayOf(PropTypes.number).isRequired,
/**
- * Left camera padding for bounds
+ * Left padding in points (deprecated; use root `padding` property instead)
*/
paddingLeft: PropTypes.number,
/**
- * Right camera padding for bounds
+ * Right padding in points (deprecated; use root `padding` property instead)
*/
paddingRight: PropTypes.number,
/**
- * Top camera padding for bounds
+ * Top padding in points (deprecated; use root `padding` property instead)
*/
paddingTop: PropTypes.number,
/**
- * Bottom camera padding for bounds
+ * Bottom padding in points (deprecated; use root `padding` property instead)
*/
paddingBottom: PropTypes.number,
-
- /**
- * Callback that is triggered on user tracking mode changes
- */
- onUserTrackingModeChange: PropTypes.func,
}),
+ /**
+ * Callback that is triggered on user tracking mode changes
+ */
+ onUserTrackingModeChange: PropTypes.func,
+
/**
* Zoom level of the map
*/
@@ -75,15 +101,20 @@ class Camera extends React.Component {
static propTypes = {
...viewPropTypes,
+ /**
+ * If false, the camera will not send any props to the native module. Intended to be used to prevent unnecessary tile fetching and improve performance when the map is not visible. Defaults to true.
+ */
+ allowUpdates: PropTypes.bool,
+
/**
* The duration a camera update takes (in ms)
*/
animationDuration: PropTypes.number,
/**
- * The animationstyle when the camara updates. One of; `flyTo`, `easeTo`, `moveTo`
+ * The animationstyle when the camara updates. One of: `flyTo`, `easeTo`, `linearTo`, `moveTo`
*/
- animationMode: PropTypes.oneOf(['flyTo', 'easeTo', 'moveTo']),
+ animationMode: PropTypes.oneOf(['flyTo', 'easeTo', 'linearTo', 'moveTo']),
/**
* Default view settings applied on camera
@@ -153,6 +184,7 @@ class Camera extends React.Component {
};
static defaultProps = {
+ allowUpdates: true,
animationMode: 'easeTo',
animationDuration: 2000,
};
@@ -161,6 +193,7 @@ class Camera extends React.Component {
Flight: 'flyTo',
Move: 'moveTo',
Ease: 'easeTo',
+ Linear: 'linearTo',
};
UNSAFE_componentWillReceiveProps(nextProps) {
@@ -172,47 +205,90 @@ class Camera extends React.Component {
}
_handleCameraChange(currentCamera, nextCamera) {
- const hasCameraChanged = this._hasCameraChanged(currentCamera, nextCamera);
+ const c = currentCamera;
+ const n = nextCamera;
+
+ if (!n.allowUpdates) {
+ return;
+ }
+
+ const hasCameraChanged = this._hasCameraChanged(c, n);
if (!hasCameraChanged) {
return;
}
- if (currentCamera.followUserLocation && !nextCamera.followUserLocation) {
+ if (c.followUserLocation && !n.followUserLocation) {
this.refs.camera.setNativeProps({followUserLocation: false});
return;
}
- if (!currentCamera.followUserLocation && nextCamera.followUserLocation) {
+ if (!c.followUserLocation && n.followUserLocation) {
this.refs.camera.setNativeProps({followUserLocation: true});
}
- if (nextCamera.followUserLocation) {
+ if (n.followUserLocation) {
this.refs.camera.setNativeProps({
- followUserMode: nextCamera.followUserMode,
- followPitch: nextCamera.followPitch || nextCamera.pitch,
- followHeading: nextCamera.followHeading || nextCamera.heading,
- followZoomLevel: nextCamera.followZoomLevel || nextCamera.zoomLevel,
+ followUserMode: n.followUserMode,
+ followPitch: n.followPitch || n.pitch,
+ followHeading: n.followHeading || n.heading,
+ followZoomLevel: n.followZoomLevel || n.zoomLevel,
});
return;
}
+ if (n.maxBounds) {
+ this.refs.camera.setNativeProps({
+ maxBounds: this._getMaxBounds(),
+ });
+ }
+ if (n.minZoomLevel) {
+ this.refs.camera.setNativeProps({
+ minZoomLevel: this.props.minZoomLevel,
+ });
+ }
+ if (n.maxZoomLevel) {
+ this.refs.camera.setNativeProps({
+ maxZoomLevel: this.props.maxZoomLevel,
+ });
+ }
+
const cameraConfig = {
- animationMode: nextCamera.animationMode,
- animationDuration: nextCamera.animationDuration,
- zoomLevel: nextCamera.zoomLevel,
- pitch: nextCamera.pitch,
- heading: nextCamera.heading,
+ bounds: undefined,
+ centerCoordinate: undefined,
+ padding: n.padding,
+ zoomLevel: n.zoomLevel,
+ pitch: n.pitch,
+ heading: n.heading,
+ animationMode: n.animationMode,
+ animationDuration: n.animationDuration,
};
- if (
- nextCamera.bounds &&
- this._hasBoundsChanged(currentCamera, nextCamera)
- ) {
- cameraConfig.bounds = nextCamera.bounds;
- } else {
- cameraConfig.centerCoordinate = nextCamera.centerCoordinate;
+ const boundsChanged = this._hasBoundsChanged(c.bounds, n.bounds);
+ const centerCoordinateChanged = this._hasCenterCoordinateChanged(
+ c.centerCoordinate,
+ n.centerCoordinate,
+ );
+ const paddingChanged = this._hasPaddingChanged(c.padding, n.padding);
+ const zoomChanged = this._hasNumberChanged(c.zoomLevel, n.zoomLevel);
+ const pitchChanged = this._hasNumberChanged(c.pitch, n.pitch);
+ const headingChanged = this._hasNumberChanged(c.heading, n.heading);
+
+ let shouldUpdate = false;
+
+ if (n.bounds && boundsChanged) {
+ cameraConfig.bounds = n.bounds;
+ shouldUpdate = true;
+ } else if (n.centerCoordinate && centerCoordinateChanged) {
+ cameraConfig.centerCoordinate = n.centerCoordinate;
+ shouldUpdate = true;
}
- this._setCamera(cameraConfig);
+ if (paddingChanged || zoomChanged || pitchChanged || headingChanged) {
+ shouldUpdate = true;
+ }
+
+ if (shouldUpdate) {
+ this._setCamera(cameraConfig);
+ }
}
_hasCameraChanged(currentCamera, nextCamera) {
@@ -221,8 +297,12 @@ class Camera extends React.Component {
const hasDefaultPropsChanged =
c.heading !== n.heading ||
- this._hasCenterCoordinateChanged(c, n) ||
- this._hasBoundsChanged(c, n) ||
+ this._hasCenterCoordinateChanged(
+ c.centerCoordinate,
+ n.centerCoordinate,
+ ) ||
+ this._hasBoundsChanged(c.bounds, n.bounds) ||
+ this._hasPaddingChanged(c.padding, n.padding) ||
c.pitch !== n.pitch ||
c.zoomLevel !== n.zoomLevel ||
c.triggerKey !== n.triggerKey;
@@ -238,36 +318,34 @@ class Camera extends React.Component {
c.animationMode !== n.animationMode ||
c.animationDuration !== n.animationDuration;
+ const hasNavigationConstraintsPropsChanged =
+ this._hasBoundsChanged(c.maxBounds, n.maxBounds) ||
+ c.minZoomLevel !== n.minZoomLevel ||
+ c.maxZoomLevel !== n.maxZoomLevel;
+
return (
hasDefaultPropsChanged ||
hasFollowPropsChanged ||
- hasAnimationPropsChanged
+ hasAnimationPropsChanged ||
+ hasNavigationConstraintsPropsChanged
);
}
- _hasCenterCoordinateChanged(currentCamera, nextCamera) {
- const cC = currentCamera.centerCoordinate;
- const nC = nextCamera.centerCoordinate;
+ _hasCenterCoordinateChanged(cC, nC) {
+ if (!cC && !nC) {
+ return false;
+ }
if (existenceChange(cC, nC)) {
return true;
}
- if (!cC && !nC) {
- return false;
- }
-
- const isLngDiff =
- currentCamera.centerCoordinate[0] !== nextCamera.centerCoordinate[0];
- const isLatDiff =
- currentCamera.centerCoordinate[1] !== nextCamera.centerCoordinate[1];
+ const isLngDiff = cC[0] !== nC[0];
+ const isLatDiff = cC[1] !== nC[1];
return isLngDiff || isLatDiff;
}
- _hasBoundsChanged(currentCamera, nextCamera) {
- const cB = currentCamera.bounds;
- const nB = nextCamera.bounds;
-
+ _hasBoundsChanged(cB, nB) {
if (!cB && !nB) {
return false;
}
@@ -288,6 +366,35 @@ class Camera extends React.Component {
);
}
+ _hasPaddingChanged(cP, nP) {
+ if (!cP && !nP) {
+ return false;
+ }
+
+ if (existenceChange(cP, nP)) {
+ return true;
+ }
+
+ return (
+ cP.paddingTop !== nP.paddingTop ||
+ cP.paddingLeft !== nP.paddingLeft ||
+ cP.paddingRight !== nP.paddingRight ||
+ cP.paddingBottom !== nP.paddingBottom
+ );
+ }
+
+ _hasNumberChanged(prev, next) {
+ if (existenceChange(prev, next)) {
+ return true;
+ }
+
+ if (!prev && !next) {
+ return false;
+ }
+
+ return prev !== next;
+ }
+
/**
* Map camera transitions to fit provided bounds
*
@@ -339,8 +446,8 @@ class Camera extends React.Component {
bounds: {
ne: northEastCoordinates,
sw: southWestCoordinates,
- ...pad,
},
+ padding: pad,
animationDuration,
animationMode: Camera.Mode.Ease,
});
@@ -377,7 +484,7 @@ class Camera extends React.Component {
* @return {void}
*/
moveTo(coordinates, animationDuration = 0) {
- return this._setCamera({
+ return this.setCamera({
centerCoordinate: coordinates,
animationDuration,
});
@@ -395,7 +502,7 @@ class Camera extends React.Component {
* @return {void}
*/
zoomTo(zoomLevel, animationDuration = 2000) {
- return this._setCamera({
+ return this.setCamera({
zoomLevel,
animationDuration,
animationMode: Camera.Mode.Flight,
@@ -479,21 +586,19 @@ class Camera extends React.Component {
}
if (config.bounds && config.bounds.ne && config.bounds.sw) {
- const {
- ne,
- sw,
- paddingLeft,
- paddingRight,
- paddingTop,
- paddingBottom,
- } = config.bounds;
+ const {ne, sw} = config.bounds;
stopConfig.bounds = toJSONString(geoUtils.makeLatLngBounds(ne, sw));
- stopConfig.boundsPaddingTop = paddingTop || 0;
- stopConfig.boundsPaddingRight = paddingRight || 0;
- stopConfig.boundsPaddingBottom = paddingBottom || 0;
- stopConfig.boundsPaddingLeft = paddingLeft || 0;
}
+ stopConfig.paddingTop =
+ config.padding?.paddingTop || config.bounds?.paddingTop || 0;
+ stopConfig.paddingRight =
+ config.padding?.paddingRight || config.bounds?.paddingRight || 0;
+ stopConfig.paddingBottom =
+ config.padding?.paddingBottom || config.bounds?.paddingBottom || 0;
+ stopConfig.paddingLeft =
+ config.padding?.paddingLeft || config.bounds?.paddingLeft || 0;
+
return stopConfig;
}
@@ -503,6 +608,8 @@ class Camera extends React.Component {
return MapboxGL.CameraModes.Flight;
case Camera.Mode.Move:
return MapboxGL.CameraModes.None;
+ case Camera.Mode.Linear:
+ return MapboxGL.CameraModes.Linear;
default:
return MapboxGL.CameraModes.Ease;
}
diff --git a/javascript/components/CircleLayer.js b/javascript/components/CircleLayer.js
index 70a807237..1b414f32e 100644
--- a/javascript/components/CircleLayer.js
+++ b/javascript/components/CircleLayer.js
@@ -26,6 +26,7 @@ class CircleLayer extends AbstractLayer {
/**
* The source from which to obtain the data to style.
* If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
diff --git a/javascript/components/FillExtrusionLayer.js b/javascript/components/FillExtrusionLayer.js
index e1532db71..8f581f50e 100644
--- a/javascript/components/FillExtrusionLayer.js
+++ b/javascript/components/FillExtrusionLayer.js
@@ -24,7 +24,9 @@ class FillExtrusionLayer extends AbstractLayer {
id: PropTypes.string.isRequired,
/**
- * The source from which to obtain the data to style. If the source has not yet been added to the current style, the behavior is undefined.
+ * The source from which to obtain the data to style.
+ * If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
diff --git a/javascript/components/FillLayer.js b/javascript/components/FillLayer.js
index dfc425232..8750b5e2a 100644
--- a/javascript/components/FillLayer.js
+++ b/javascript/components/FillLayer.js
@@ -24,7 +24,9 @@ class FillLayer extends AbstractLayer {
id: PropTypes.string.isRequired,
/**
- * The source from which to obtain the data to style. If the source has not yet been added to the current style, the behavior is undefined.
+ * The source from which to obtain the data to style.
+ * If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
diff --git a/javascript/components/HeadingIndicator.js b/javascript/components/HeadingIndicator.js
index 9de1d1752..91e6100f0 100644
--- a/javascript/components/HeadingIndicator.js
+++ b/javascript/components/HeadingIndicator.js
@@ -9,9 +9,10 @@ const style = {
iconImage: headingIcon,
iconAllowOverlap: true,
iconPitchAlignment: 'map',
+ iconRotationAlignment: 'map',
};
-const HeadingIndicator = (heading) => (
+const HeadingIndicator = heading => (
this._setNativeRef(nativeRef),
+ ref: nativeRef => this._setNativeRef(nativeRef),
onPress: this._onPress,
onLongPress: this._onLongPress,
onMapChange: this._onChange,
@@ -767,8 +794,7 @@ class MapView extends NativeBridgeComponent(React.Component) {
+ testID={mapView ? null : this.props.testID}>
{mapView}
);
diff --git a/javascript/components/NativeBridgeComponent.js b/javascript/components/NativeBridgeComponent.js
index 9e4c4824f..e3327daa1 100644
--- a/javascript/components/NativeBridgeComponent.js
+++ b/javascript/components/NativeBridgeComponent.js
@@ -2,7 +2,7 @@ import {runNativeCommand, isAndroid} from '../utils';
let callbackIncrement = 0;
-const NativeBridgeComponent = (B) =>
+const NativeBridgeComponent = B =>
class extends B {
constructor(props, nativeModuleName) {
super(props);
@@ -57,7 +57,7 @@ const NativeBridgeComponent = (B) =>
_runNativeCommand(methodName, nativeRef, args = []) {
if (!nativeRef) {
- return new Promise((resolve) => {
+ return new Promise(resolve => {
this._preRefMapMethodQueue.push({
method: {name: methodName, args},
resolver: resolve,
diff --git a/javascript/components/PointAnnotation.js b/javascript/components/PointAnnotation.js
index fd2ef698a..a89ef7d24 100644
--- a/javascript/components/PointAnnotation.js
+++ b/javascript/components/PointAnnotation.js
@@ -176,7 +176,7 @@ class PointAnnotation extends NativeBridgeComponent(React.PureComponent) {
render() {
const props = {
...this.props,
- ref: (nativeRef) => this._setNativeRef(nativeRef),
+ ref: nativeRef => this._setNativeRef(nativeRef),
id: this.props.id,
title: this.props.title,
snippet: this.props.snippet,
diff --git a/javascript/components/RasterLayer.js b/javascript/components/RasterLayer.js
index 2d0040200..471908e57 100644
--- a/javascript/components/RasterLayer.js
+++ b/javascript/components/RasterLayer.js
@@ -21,7 +21,9 @@ class RasterLayer extends AbstractLayer {
id: PropTypes.string.isRequired,
/**
- * The source from which to obtain the data to style. If the source has not yet been added to the current style, the behavior is undefined.
+ * The source from which to obtain the data to style.
+ * If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
diff --git a/javascript/components/RasterSource.js b/javascript/components/RasterSource.js
index bc4e741b1..918645638 100644
--- a/javascript/components/RasterSource.js
+++ b/javascript/components/RasterSource.js
@@ -10,7 +10,7 @@ const MapboxGL = NativeModules.MGLModule;
export const NATIVE_MODULE_NAME = 'RCTMGLRasterSource';
-const isTileTemplateUrl = (url) =>
+const isTileTemplateUrl = url =>
url &&
(url.includes('{z}') || url.includes('{bbox-') || url.includes('{quadkey}'));
diff --git a/javascript/components/ShapeSource.js b/javascript/components/ShapeSource.js
index de3c775e3..78d3d9ee5 100644
--- a/javascript/components/ShapeSource.js
+++ b/javascript/components/ShapeSource.js
@@ -85,6 +85,13 @@ class ShapeSource extends NativeBridgeComponent(AbstractSource) {
*/
tolerance: PropTypes.number,
+ /**
+ * Whether to calculate line distance metrics.
+ * This is required for line layers that specify lineGradient values.
+ * The default value is false.
+ */
+ lineMetrics: PropTypes.bool,
+
/**
* Source press listener, gets called when a user presses one of the children layers only
* if that layer has a higher z-index than another source layers
@@ -147,6 +154,118 @@ class ShapeSource extends NativeBridgeComponent(AbstractSource) {
return res.data;
}
+ /**
+ * Returns the zoom needed to expand the cluster.
+ *
+ * @example
+ * const zoom = await shapeSource.getClusterExpansionZoom(clusterId);
+ *
+ * @param {Feature} feature - The feature cluster to expand.
+ * @return {number}
+ */
+ async getClusterExpansionZoom(feature) {
+ if (typeof feature === 'number') {
+ console.warn(
+ 'Using cluster_id is deprecated and will be removed from the future releases. Please use cluster as an argument instead.',
+ );
+ const res = await this._runNativeCommand(
+ 'getClusterExpansionZoomById',
+ this._nativeRef,
+ [feature],
+ );
+ return res.data;
+ }
+
+ const res = await this._runNativeCommand(
+ 'getClusterExpansionZoom',
+ this._nativeRef,
+ [JSON.stringify(feature)],
+ );
+ return res.data;
+ }
+
+ /**
+ * Returns the FeatureCollection from the cluster.
+ *
+ * @example
+ * const collection = await shapeSource.getClusterLeaves(clusterId, limit, offset);
+ *
+ * @param {Feature} feature - The feature cluster to expand.
+ * @param {number} limit - The number of points to return.
+ * @param {number} offset - The amount of points to skip (for pagination).
+ * @return {FeatureCollection}
+ */
+ async getClusterLeaves(feature, limit, offset) {
+ if (typeof feature === 'number') {
+ console.warn(
+ 'Using cluster_id is deprecated and will be removed from the future releases. Please use cluster as an argument instead.',
+ );
+ const res = await this._runNativeCommand(
+ 'getClusterLeavesById',
+ this._nativeRef,
+ [feature, limit, offset],
+ );
+
+ if (isAndroid()) {
+ return JSON.parse(res.data);
+ }
+
+ return res.data;
+ }
+
+ const res = await this._runNativeCommand(
+ 'getClusterLeaves',
+ this._nativeRef,
+ [JSON.stringify(feature), limit, offset],
+ );
+
+ if (isAndroid()) {
+ return JSON.parse(res.data);
+ }
+
+ return res.data;
+ }
+
+ /**
+ * Returns the FeatureCollection from the cluster (on the next zoom level).
+ *
+ * @example
+ * const collection = await shapeSource.getClusterChildren(clusterId);
+ *
+ * @param {Feature} feature - The feature cluster to expand.
+ * @return {FeatureCollection}
+ */
+ async getClusterChildren(feature) {
+ if (typeof feature === 'number') {
+ console.warn(
+ 'Using cluster_id is deprecated and will be removed from the future releases. Please use cluster as an argument instead.',
+ );
+ const res = await this._runNativeCommand(
+ 'getClusterChildrenById',
+ this._nativeRef,
+ [feature],
+ );
+
+ if (isAndroid()) {
+ return JSON.parse(res.data);
+ }
+
+ return res.data;
+ }
+
+ const res = await this._runNativeCommand(
+ 'getClusterChildren',
+ this._nativeRef,
+ [JSON.stringify(feature)],
+ );
+
+ if (isAndroid()) {
+ return JSON.parse(res.data);
+ }
+
+ return res.data;
+ }
+
setNativeProps(props) {
const shallowProps = Object.assign({}, props);
@@ -179,13 +298,13 @@ class ShapeSource extends NativeBridgeComponent(AbstractSource) {
newEvent = copyPropertiesAsDeprecated(
event,
newEvent,
- (key) => {
+ key => {
console.warn(
`event.${key} is deprecated on ShapeSource#onPress, please use event.features`,
);
},
{
- nativeEvent: (origNativeEvent) => ({
+ nativeEvent: origNativeEvent => ({
...origNativeEvent,
payload: features[0],
}),
@@ -208,8 +327,9 @@ class ShapeSource extends NativeBridgeComponent(AbstractSource) {
maxZoomLevel: this.props.maxZoomLevel,
buffer: this.props.buffer,
tolerance: this.props.tolerance,
+ lineMetrics: this.props.lineMetrics,
onPress: undefined,
- ref: (nativeRef) => this._setNativeRef(nativeRef),
+ ref: nativeRef => this._setNativeRef(nativeRef),
onAndroidCallback: isAndroid() ? this._onAndroidCallback : undefined,
};
diff --git a/javascript/components/Style.js b/javascript/components/Style.js
index dff57eb17..0fed7eaef 100644
--- a/javascript/components/Style.js
+++ b/javascript/components/Style.js
@@ -15,7 +15,7 @@ import ImageSource from './ImageSource';
import ShapeSource from './ShapeSource';
function toCamelCase(s) {
- return s.replace(/([-_][a-z])/gi, ($1) => {
+ return s.replace(/([-_][a-z])/gi, $1 => {
return $1.toUpperCase().replace('-', '').replace('_', '');
});
}
@@ -27,7 +27,7 @@ function toCamelCaseKeys(oldObj) {
return {};
}
const newObj = {};
- Object.keys(oldObj).forEach((key) => {
+ Object.keys(oldObj).forEach(key => {
const value = oldObj[key];
if (key.includes('-')) {
newObj[toCamelCase(key)] = value;
@@ -169,6 +169,9 @@ function getShapeSource(id, source) {
if (source.tolerance !== undefined) {
sourceProps.tolerance = source.tolerance;
}
+ if (source.lineMetrics !== undefined) {
+ sourceProps.lineMetrics = source.lineMetrics;
+ }
return ;
}
@@ -194,13 +197,13 @@ function asSourceComponent(id, source) {
* Only [`sources`](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources) & [`layers`](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/) are supported.
* Other fields such as `sprites`, `glyphs` etc. will be ignored. Not all layer / source attributes from the style spec are supported, in general the supported attributes will mentioned under https://github.com/react-native-mapbox-gl/maps/tree/master/docs.
*/
-const Style = (props) => {
+const Style = props => {
const [fetchedJson, setFetchedJson] = useState({});
const json = typeof props.json === 'object' ? props.json : fetchedJson;
// Fetch style when props.json is a URL
useEffect(() => {
- const abortController = new window.AbortController();
+ const abortController = new AbortController();
const fetchStyleJson = async () => {
try {
const response = await fetch(props.json, {
@@ -228,7 +231,7 @@ const Style = (props) => {
if (!json.layers) {
return [];
}
- return json.layers.map(asLayerComponent).filter((x) => !!x);
+ return json.layers.map(asLayerComponent).filter(x => !!x);
}, [json.layers]);
// Extract source components from json
@@ -237,8 +240,8 @@ const Style = (props) => {
return [];
}
return Object.keys(json.sources)
- .map((id) => asSourceComponent(id, json.sources[id]))
- .filter((x) => !!x);
+ .map(id => asSourceComponent(id, json.sources[id]))
+ .filter(x => !!x);
}, [json.sources]);
return (
diff --git a/javascript/components/SymbolLayer.js b/javascript/components/SymbolLayer.js
index 8f1a64231..d1a036ad0 100644
--- a/javascript/components/SymbolLayer.js
+++ b/javascript/components/SymbolLayer.js
@@ -24,7 +24,9 @@ class SymbolLayer extends AbstractLayer {
id: PropTypes.string.isRequired,
/**
- * The source from which to obtain the data to style. If the source has not yet been added to the current style, the behavior is undefined.
+ * The source from which to obtain the data to style.
+ * If the source has not yet been added to the current style, the behavior is undefined.
+ * Inferred from parent source only if the layer is a direct child to it.
*/
sourceID: PropTypes.string,
@@ -83,7 +85,7 @@ class SymbolLayer extends AbstractLayer {
return isSnapshot;
}
- React.Children.forEach(this.props.children, (child) => {
+ React.Children.forEach(this.props.children, child => {
if (child.type === View) {
isSnapshot = true;
}
diff --git a/javascript/components/UserLocation.js b/javascript/components/UserLocation.js
index ba50c6a2f..fb6cc26ac 100644
--- a/javascript/components/UserLocation.js
+++ b/javascript/components/UserLocation.js
@@ -198,14 +198,15 @@ class UserLocation extends React.Component {
* @return {boolean}
*/
needsLocationManagerRunning() {
- if (this.props.renderMode === UserLocation.RenderMode.Native) {
- return false;
- }
- return !!this.props.onUpdate || this.props.visible;
+ return (
+ !!this.props.onUpdate ||
+ (this.props.renderMode === UserLocation.RenderMode.Normal &&
+ this.props.visible)
+ );
}
_onLocationUpdate(location) {
- if (!this._isMounted) {
+ if (!this._isMounted || !location) {
return;
}
let coordinates = null;
@@ -239,13 +240,8 @@ class UserLocation extends React.Component {
render() {
const {heading, coordinates} = this.state;
- const {
- children,
- visible,
- showsUserHeadingIndicator,
- onPress,
- animated,
- } = this.props;
+ const {children, visible, showsUserHeadingIndicator, onPress, animated} =
+ this.props;
if (!visible) {
return null;
@@ -267,8 +263,7 @@ class UserLocation extends React.Component {
coordinates={coordinates}
style={{
iconRotate: heading,
- }}
- >
+ }}>
{children || normalIcon(showsUserHeadingIndicator, heading)}
);
diff --git a/javascript/components/VectorSource.js b/javascript/components/VectorSource.js
index e009d1da1..291b38540 100644
--- a/javascript/components/VectorSource.js
+++ b/javascript/components/VectorSource.js
@@ -145,13 +145,13 @@ class VectorSource extends NativeBridgeComponent(AbstractSource) {
newEvent = copyPropertiesAsDeprecated(
event,
newEvent,
- (key) => {
+ key => {
console.warn(
`event.${key} is deprecated on VectorSource#onPress, please use event.features`,
);
},
{
- nativeEvent: (origNativeEvent) => ({
+ nativeEvent: origNativeEvent => ({
...origNativeEvent,
payload: features[0],
}),
@@ -173,7 +173,7 @@ class VectorSource extends NativeBridgeComponent(AbstractSource) {
hasPressListener: isFunction(this.props.onPress),
onMapboxVectorSourcePress: this.onPress.bind(this),
onPress: undefined,
- ref: (nativeRef) => this._setNativeRef(nativeRef),
+ ref: nativeRef => this._setNativeRef(nativeRef),
onAndroidCallback: isAndroid() ? this._onAndroidCallback : undefined,
};
return (
diff --git a/javascript/components/annotations/Annotation.js b/javascript/components/annotations/Annotation.js
index e35df7703..d8c6a470c 100644
--- a/javascript/components/annotations/Annotation.js
+++ b/javascript/components/annotations/Annotation.js
@@ -106,8 +106,7 @@ class Annotation extends React.Component {
id={this.props.id}
ref="source"
onPress={this.onPress}
- shape={this.state.shape}
- >
+ shape={this.state.shape}>
{this.symbolStyle && (
l !== listener);
+ this._listeners = this._listeners.filter(l => l !== listener);
if (this._listeners.length === 0) {
this.stop();
}
@@ -66,7 +68,7 @@ class LocationManager {
if (!this._isListening) {
MapboxGLLocationManager.start(displacement);
- LocationModuleEventEmitter.addListener(
+ this.subscription = LocationModuleEventEmitter.addListener(
MapboxGL.LocationCallbackName.Update,
this.onUpdate,
);
@@ -79,10 +81,7 @@ class LocationManager {
MapboxGLLocationManager.stop();
if (this._isListening) {
- LocationModuleEventEmitter.removeListener(
- MapboxGL.LocationCallbackName.Update,
- this.onUpdate,
- );
+ this.subscription.remove();
}
this._isListening = false;
@@ -95,7 +94,7 @@ class LocationManager {
onUpdate(location) {
this._lastKnownLocation = location;
- this._listeners.forEach((l) => l(location));
+ this._listeners.forEach(l => l(location));
}
}
diff --git a/javascript/modules/offline/offlineManager.js b/javascript/modules/offline/offlineManager.js
index ebd193f49..f19daab63 100644
--- a/javascript/modules/offline/offlineManager.js
+++ b/javascript/modules/offline/offlineManager.js
@@ -26,6 +26,9 @@ class OfflineManager {
this._onProgress = this._onProgress.bind(this);
this._onError = this._onError.bind(this);
+
+ this.subscriptionProgress = null;
+ this.subscriptionError = null;
}
/**
@@ -183,7 +186,7 @@ class OfflineManager {
async getPacks() {
await this._initialize();
return Object.keys(this._offlinePacks).map(
- (name) => this._offlinePacks[name],
+ name => this._offlinePacks[name],
);
}
@@ -217,7 +220,7 @@ class OfflineManager {
/**
* Sets the maximum number of Mapbox-hosted tiles that may be downloaded and stored on the current device.
- * The Mapbox Terms of Service prohibits changing or bypassing this limit without permission from Mapbox.
+ * The Mapbox Terms of Service prohibit changing or bypassing this limit without permission from Mapbox.
*
* @example
* MapboxGL.offlineManager.setTileCountLimit(1000);
@@ -230,11 +233,11 @@ class OfflineManager {
}
/**
- * Sets the value at which download status events will be sent over the React Native bridge.
- * These events happening very very fast default is 500ms.
+ * Sets the period at which download status events will be sent over the React Native bridge.
+ * The default is 500ms.
*
* @example
- * MapboxGL.setProgressEventThrottle(500);
+ * MapboxGL.offlineManager.setProgressEventThrottle(500);
*
* @param {Number} throttleValue event throttle value in ms.
* @return {void}
@@ -261,7 +264,7 @@ class OfflineManager {
const totalProgressListeners = Object.keys(this._progressListeners).length;
if (isFunction(progressListener)) {
if (totalProgressListeners === 0) {
- OfflineModuleEventEmitter.addListener(
+ this.subscriptionProgress = OfflineModuleEventEmitter.addListener(
MapboxGL.OfflineCallbackName.Progress,
this._onProgress,
);
@@ -272,7 +275,7 @@ class OfflineManager {
const totalErrorListeners = Object.keys(this._errorListeners).length;
if (isFunction(errorListener)) {
if (totalErrorListeners === 0) {
- OfflineModuleEventEmitter.addListener(
+ this.subscriptionError = OfflineModuleEventEmitter.addListener(
MapboxGL.OfflineCallbackName.Error,
this._onError,
);
@@ -287,7 +290,7 @@ class OfflineManager {
// manually set a listener, since listeners are only set on create flow
await MapboxGLOfflineManager.setPackObserver(packName);
} catch (e) {
- console.log('Unable to set pack observer', e); // eslint-disable-line
+ console.log('Unable to set pack observer', e);
}
}
}
@@ -306,18 +309,18 @@ class OfflineManager {
delete this._progressListeners[packName];
delete this._errorListeners[packName];
- if (Object.keys(this._progressListeners).length === 0) {
- OfflineModuleEventEmitter.removeListener(
- MapboxGL.OfflineCallbackName.Progress,
- this._onProgress,
- );
+ if (
+ Object.keys(this._progressListeners).length === 0 &&
+ this.subscriptionProgress
+ ) {
+ this.subscriptionProgress.remove();
}
- if (Object.keys(this._errorListeners).length === 0) {
- OfflineModuleEventEmitter.removeListener(
- MapboxGL.OfflineCallbackName.Error,
- this._onError,
- );
+ if (
+ Object.keys(this._errorListeners).length === 0 &&
+ this.subscriptionError
+ ) {
+ this.subscriptionError.remove();
}
}
diff --git a/javascript/utils/Logger.js b/javascript/utils/Logger.js
index 9f7fda4e0..5101fc663 100644
--- a/javascript/utils/Logger.js
+++ b/javascript/utils/Logger.js
@@ -71,7 +71,7 @@ class Logger {
}
subscribe() {
- this.subscription = this.loggerEmitter.addListener('LogEvent', (log) => {
+ this.subscription = this.loggerEmitter.addListener('LogEvent', log => {
this.onLog(log);
});
}
diff --git a/javascript/utils/animated/AnimatedCoordinatesArray.js b/javascript/utils/animated/AnimatedCoordinatesArray.js
index fcca640f9..75ee2b8f5 100644
--- a/javascript/utils/animated/AnimatedCoordinatesArray.js
+++ b/javascript/utils/animated/AnimatedCoordinatesArray.js
@@ -31,7 +31,7 @@ class AnimatedCoordinatesArray extends AnimatedWithChildren {
* @returns {object} - the state object
*/
onInitialState(coordinatesArray) {
- return {coords: coordinatesArray.map((coord) => [coord[0], coord[1]])};
+ return {coords: coordinatesArray.map(coord => [coord[0], coord[1]])};
}
/**
@@ -72,7 +72,7 @@ class AnimatedCoordinatesArray extends AnimatedWithChildren {
coords.length > 0 ? coords[coords.length - 1] : targetCoords[0];
const adding = targetCoords
.slice(commonLen, targetCoords.length)
- .map((newCoord) => [
+ .map(newCoord => [
addingOrig[0] * origF + newCoord[0] * newF,
addingOrig[1] * origF + newCoord[1] * newF,
]);
@@ -87,7 +87,7 @@ class AnimatedCoordinatesArray extends AnimatedWithChildren {
: coords[0];
const dissapearing = coords
.slice(commonLen, coords.length)
- .map((origCoord) => [
+ .map(origCoord => [
origCoord[0] * origF + dissapearingNew[0] * newF,
origCoord[1] * origF + dissapearingNew[1] * newF,
]);
@@ -105,7 +105,7 @@ class AnimatedCoordinatesArray extends AnimatedWithChildren {
* @returns {object} The state
*/
onStart(state, toValue) {
- const targetCoords = toValue.map((coord) => [coord[0], coord[1]]);
+ const targetCoords = toValue.map(coord => [coord[0], coord[1]]);
return {
...state,
targetCoords,
@@ -115,7 +115,7 @@ class AnimatedCoordinatesArray extends AnimatedWithChildren {
animate(progressValue, progressAnimation, config) {
const {toValue} = config;
- const onAnimationStart = (animation) => {
+ const onAnimationStart = animation => {
if (this.animation) {
// there was a started but not finsihed animation
const actProgress = this.progressValue.__getValue();
diff --git a/javascript/utils/animated/AnimatedRouteCoordinatesArray.js b/javascript/utils/animated/AnimatedRouteCoordinatesArray.js
index 7c8be5e31..bfadd1983 100644
--- a/javascript/utils/animated/AnimatedRouteCoordinatesArray.js
+++ b/javascript/utils/animated/AnimatedRouteCoordinatesArray.js
@@ -1,17 +1,9 @@
-import {
- lineString,
- point,
- convertDistance as convertDistanceFn,
- convertLength as convertLengthFn,
-} from '@turf/helpers';
+import {lineString, point, convertLength} from '@turf/helpers';
import distance from '@turf/distance';
import nearestPointOnLine from '@turf/nearest-point-on-line';
import length from '@turf/length';
import AnimatedCoordinatesArray from './AnimatedCoordinatesArray';
-
-const convertLength = convertLengthFn || convertDistanceFn;
-
export default class AnimatedRouteCoordinatesArray extends AnimatedCoordinatesArray {
/**
* Calculate initial state
@@ -21,7 +13,7 @@ export default class AnimatedRouteCoordinatesArray extends AnimatedCoordinatesAr
*/
onInitialState(coordinatesArray) {
return {
- fullRoute: coordinatesArray.map((coord) => [coord[0], coord[1]]),
+ fullRoute: coordinatesArray.map(coord => [coord[0], coord[1]]),
end: {from: 0},
};
}
diff --git a/javascript/utils/animated/AnimatedShape.js b/javascript/utils/animated/AnimatedShape.js
index 4af8a1beb..57513427a 100644
--- a/javascript/utils/animated/AnimatedShape.js
+++ b/javascript/utils/animated/AnimatedShape.js
@@ -1,7 +1,5 @@
import {Animated} from 'react-native';
-/* eslint-disable guard-for-in */
-
// see
// https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/nodes/AnimatedWithChildren.js
const AnimatedWithChildren = Object.getPrototypeOf(Animated.ValueXY);
@@ -29,7 +27,7 @@ class AnimatedShape extends AnimatedWithChildren {
_walkShapeAndGetValues(value) {
if (Array.isArray(value)) {
- return value.map((i) => this._walkShapeAndGetValues(i));
+ return value.map(i => this._walkShapeAndGetValues(i));
}
if (value instanceof Animated.Node) {
return value.__getValue();
@@ -51,7 +49,7 @@ class AnimatedShape extends AnimatedWithChildren {
_walkAndProcess(value, cb) {
if (Array.isArray(value)) {
- value.forEach((i) => this._walkAndProcess(i, cb));
+ value.forEach(i => this._walkAndProcess(i, cb));
} else if (value instanceof Animated.Node) {
cb(value);
} else if (typeof value === 'object') {
@@ -62,11 +60,11 @@ class AnimatedShape extends AnimatedWithChildren {
}
__attach() {
- this._walkAndProcess(this.shape, (v) => v.__addChild(this));
+ this._walkAndProcess(this.shape, v => v.__addChild(this));
}
__detach() {
- this._walkAndProcess(this.shape, (v) => v.__removeChild(this));
+ this._walkAndProcess(this.shape, v => v.__removeChild(this));
super.__detach();
}
}
diff --git a/javascript/utils/deprecation.js b/javascript/utils/deprecation.js
index c38a198f7..d0e079bac 100644
--- a/javascript/utils/deprecation.js
+++ b/javascript/utils/deprecation.js
@@ -2,7 +2,7 @@
* Copy properties from origObject to newObject, which not exists in newObject,
* calls onDeprecatedCalled callback in case a copied property is invoked.
*/
-// eslint-disable-next-line class-methods-use-this
+
export function copyPropertiesAsDeprecated(
origObject,
newObject,
diff --git a/javascript/utils/index.js b/javascript/utils/index.js
index e811fe6d4..bc3c28fe1 100644
--- a/javascript/utils/index.js
+++ b/javascript/utils/index.js
@@ -95,8 +95,8 @@ export function cloneReactChildrenWithProps(children, propsToAdd = {}) {
foundChildren = children;
}
- const filteredChildren = foundChildren.filter((child) => !!child); // filter out falsy children, since some can be null
- return React.Children.map(filteredChildren, (child) =>
+ const filteredChildren = foundChildren.filter(child => !!child); // filter out falsy children, since some can be null
+ return React.Children.map(filteredChildren, child =>
React.cloneElement(child, propsToAdd),
);
}
diff --git a/package.json b/package.json
index b5cf534c8..f1114c8e6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@react-native-mapbox-gl/maps",
"description": "A Mapbox GL react native module for creating custom maps",
- "version": "8.1.0-rc.9",
+ "version": "8.5.0",
"publishConfig": {
"access": "public"
},
@@ -25,55 +25,61 @@
"test": "npm run lint && npm run unittest",
"unittest": "jest",
"unittest:single": "jest --testNamePattern",
- "format": "npm run lint -- --fix",
- "lint": "eslint . --ignore-pattern 'example' --fix && cd example/ && eslint ./src --fix"
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "prepare": "yarn build:plugin",
+ "test:plugin": "expo-module test plugin",
+ "build:plugin": "tsc --build plugin",
+ "lint:plugin": "eslint plugin/src/*"
},
"peerDependencies": {
"prop-types": ">=15.5.8",
- "react": "^16.6.1",
+ "react": ">=16.6.1",
"react-native": ">=0.59.9"
},
"dependencies": {
+ "@expo/config-plugins": "^4.0.3",
"@mapbox/geo-viewport": ">= 0.4.0",
- "@turf/along": ">= 4.0.0 <7.0.0",
- "@turf/distance": ">= 4.0.0 <7.0.0",
- "@turf/nearest-point-on-line": ">= 4.0.0 <7.0.0",
- "@turf/helpers": ">= 4.6.0 <7.0.0",
- "@turf/length": ">= 4.6.0 <7.0.0",
+ "@turf/along": "6.5.0",
+ "@turf/distance": "6.5.0",
+ "@turf/helpers": "6.5.0",
+ "@turf/length": "6.5.0",
+ "@turf/nearest-point-on-line": "6.5.0",
"@types/geojson": "^7946.0.7",
"debounce": "^1.2.0"
},
"devDependencies": {
"@babel/core": "7.5.0",
- "@babel/plugin-proposal-class-properties": "7.10.4",
- "@babel/plugin-transform-exponentiation-operator": "7.10.4",
- "@babel/plugin-transform-flow-strip-types": "7.10.4",
- "@babel/plugin-transform-runtime": "7.11.5",
+ "@babel/plugin-proposal-class-properties": "7.16.0",
+ "@babel/plugin-transform-runtime": "7.16.4",
"@babel/runtime": "7.11.2",
+ "@react-native-community/eslint-config": "^3.0.1",
+ "@sinonjs/fake-timers": "^8.0.1",
+ "@testing-library/react-native": "^8.0.0",
+ "@typescript-eslint/eslint-plugin": "^5.2.0",
+ "@typescript-eslint/parser": "^5.8.0",
"babel-core": "6.26.3",
"babel-eslint": "^10.0.1",
- "documentation": "13.0.2",
+ "documentation": "13.2.5",
"ejs": "^3.1.3",
"ejs-lint": "^1.1.0",
- "eslint": "^7.3.0",
- "@react-native-community/eslint-config": "^2.0.0",
+ "eslint": "^7.32.0 ",
+ "eslint-config-prettier": "^8.1.0",
"eslint-plugin-fp": "^2.3.0",
- "eslint-plugin-import": "2.22.0",
- "flow-bin": "^0.134.0",
- "husky": "4.3.0",
+ "eslint-plugin-import": "2.25.3",
+ "expo-module-scripts": "^2.0.0",
+ "husky": "4.3.8",
"jest": "25.5.4",
- "@sinonjs/fake-timers": "^6.0.0",
"jest-cli": "25.5.4",
- "lint-staged": "^10.1.3",
+ "lint-staged": "^12.1.2",
"metro-react-native-babel-preset": "0.49.1",
"node-dir": "0.1.17",
- "react": "16.8.6",
+ "prettier": "^2.0.4",
+ "react": "16.8.3",
"react-docgen": "^5.0.0-beta.1",
"react-native": "0.59.10",
- "typescript": "4.0.3",
- "react-native-testing-library": "^6.0.0",
"react-test-renderer": "16.8.3",
- "prettier": "^2.0.4"
+ "typescript": "^4.4.3"
},
"jest": {
"preset": "react-native",
@@ -86,7 +92,8 @@
],
"modulePathIgnorePatterns": [
"example",
- "__tests__/__mocks__"
+ "__tests__/__mocks__",
+ "fixtures"
]
},
"husky": {
@@ -95,8 +102,6 @@
}
},
"lint-staged": {
- "*.js": [
- "yarn lint"
- ]
+ "*.{js,jsx,ts,tsx}": "yarn lint"
}
}
diff --git a/plugin/install.md b/plugin/install.md
new file mode 100644
index 000000000..8fbd7e5f8
--- /dev/null
+++ b/plugin/install.md
@@ -0,0 +1,32 @@
+# Expo installation
+
+> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).
+
+First install the package with yarn, npm, or [`expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install).
+
+```sh
+expo install @react-native-mapbox-gl/maps
+```
+
+After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": ["@react-native-mapbox-gl/maps"]
+ }
+}
+```
+
+Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide.
+
+## API
+
+This plugin doesn't currently provide any additional properties for customization. The plugin simply generates the pre-install block in the `ios/Podfile` (the post-install block is not required for Expo support). No additional changes are done on Android.
+
+## Manual Setup
+
+For bare workflow projects, you can follow the manual setup guides:
+
+- [iOS](/ios/install.md)
+- [Android](/android/install.md)
diff --git a/plugin/jest.config.js b/plugin/jest.config.js
new file mode 100644
index 000000000..e539b3b18
--- /dev/null
+++ b/plugin/jest.config.js
@@ -0,0 +1 @@
+module.exports = require('expo-module-scripts/jest-preset-plugin');
diff --git a/plugin/src/__tests__/__snapshots__/withMapbox-test.ts.snap b/plugin/src/__tests__/__snapshots__/withMapbox-test.ts.snap
new file mode 100644
index 000000000..d377b4836
--- /dev/null
+++ b/plugin/src/__tests__/__snapshots__/withMapbox-test.ts.snap
@@ -0,0 +1,198 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`applyCocoaPodsModifications adds blocks to a expo prebuild template podfile 1`] = `
+"
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
+ pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-5a7ed0a20d5aff2d61639bc5bb4fd5551233d57c
+ $RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+ end
+# @generated end pre_installer
+ use_react_native!(:path => config[\\"reactNativePath\\"])
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+"
+`;
+
+exports[`applyCocoaPodsModifications adds blocks to a expo prebuild template podfile with custom modifications 1`] = `
+"
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
+ pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-5a7ed0a20d5aff2d61639bc5bb4fd5551233d57c
+ $RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+ end
+# @generated end pre_installer
+ use_react_native!(:path => config[\\"reactNativePath\\"])
+
+ # pre_install do |installer|
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+"
+`;
+
+exports[`applyCocoaPodsModifications adds blocks to a react native template podfile 1`] = `
+"
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ config = use_native_modules!
+
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
+ pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-5a7ed0a20d5aff2d61639bc5bb4fd5551233d57c
+ $RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+ end
+# @generated end pre_installer
+ use_react_native!(
+ :path => config[:reactNativePath],
+ # to enable hermes on iOS, change \`false\` to \`true\` and then install pods
+ :hermes_enabled => false
+ )
+
+ target 'HelloWorldTests' do
+ inherit! :complete
+ # Pods for testing
+ end
+
+ # Enables Flipper.
+ #
+ # Note that if you have use_frameworks! enabled, Flipper will not work and
+ # you should disable the next line.
+ use_flipper!()
+
+ post_install do |installer|
+ react_native_post_install(installer)
+ end
+end
+"
+`;
+
+exports[`applyCocoaPodsModifications does not work with revisions to blocks after comments 1`] = `
+"
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+ # pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-5a7ed0a20d5aff2d61639bc5bb4fd5551233d57c
+ $RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+
+# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-00old-id-2
+INVALID_post_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-001
+ INVALID_$RNMBGL.post_install(installer)
+# @generated end @react-native-mapbox-gl/maps-post_installer
+end
+# @generated end post_installer
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
+ pre_install do |installer|
+ end
+# @generated end pre_installer
+ use_react_native!(:path => config[\\"reactNativePath\\"])
+
+
+end
+"
+`;
+
+exports[`applyCocoaPodsModifications works after revisions to blocks 1`] = `
+"
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-00old-id-2
+INVALID_post_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-001
+ INVALID_$RNMBGL.post_install(installer)
+# @generated end @react-native-mapbox-gl/maps-post_installer
+end
+# @generated end post_installer
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
+ pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-5a7ed0a20d5aff2d61639bc5bb4fd5551233d57c
+ $RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+ end
+# @generated end pre_installer
+ use_react_native!(:path => config[\\"reactNativePath\\"])
+
+ # pre_install do |installer|
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+"
+`;
diff --git a/plugin/src/__tests__/fixtures/cocoapodFiles.ts b/plugin/src/__tests__/fixtures/cocoapodFiles.ts
new file mode 100644
index 000000000..c50f17102
--- /dev/null
+++ b/plugin/src/__tests__/fixtures/cocoapodFiles.ts
@@ -0,0 +1,173 @@
+export const reactNativeTemplatePodfile = `
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ config = use_native_modules!
+
+ use_react_native!(
+ :path => config[:reactNativePath],
+ # to enable hermes on iOS, change \`false\` to \`true\` and then install pods
+ :hermes_enabled => false
+ )
+
+ target 'HelloWorldTests' do
+ inherit! :complete
+ # Pods for testing
+ end
+
+ # Enables Flipper.
+ #
+ # Note that if you have use_frameworks! enabled, Flipper will not work and
+ # you should disable the next line.
+ use_flipper!()
+
+ post_install do |installer|
+ react_native_post_install(installer)
+ end
+end
+`;
+
+export const expoTemplatePodfile = `
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+ use_react_native!(:path => config["reactNativePath"])
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+`;
+
+export const customExpoTemplatePodfile = `
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+ use_react_native!(:path => config["reactNativePath"])
+
+ # pre_install do |installer|
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+`;
+
+// This tests that if an invalid revision is pushed, the plugin can correct it based on the ID.
+export const expoTemplateWithRevisions = `
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-00old-id
+INVALID_pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-00
+ INVALID_$RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+end
+# @generated end pre_installer
+# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-00old-id-2
+INVALID_post_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-001
+ INVALID_$RNMBGL.post_install(installer)
+# @generated end @react-native-mapbox-gl/maps-post_installer
+end
+# @generated end post_installer
+ use_react_native!(:path => config["reactNativePath"])
+
+ # pre_install do |installer|
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+end
+`;
+
+export const expoTemplateWithRevisionsAfterComments = `
+require_relative '../node_modules/react-native/scripts/react_native_pods'
+require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+ use_unimodules!
+ config = use_native_modules!
+ # pre_install do |installer|
+ # end
+
+ # Uncomment to opt-in to using Flipper
+ #
+ # if !ENV['CI']
+ # use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
+ # post_install do |installer|
+ # flipper_post_install(installer)
+ # end
+ # end
+
+# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-00old-id
+INVALID_pre_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-00
+ INVALID_$RNMBGL.pre_install(installer)
+# @generated end @react-native-mapbox-gl/maps-pre_installer
+end
+# @generated end pre_installer
+# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-00old-id-2
+INVALID_post_install do |installer|
+# @generated begin @react-native-mapbox-gl/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-001
+ INVALID_$RNMBGL.post_install(installer)
+# @generated end @react-native-mapbox-gl/maps-post_installer
+end
+# @generated end post_installer
+ use_react_native!(:path => config["reactNativePath"])
+
+
+end
+`;
+
+export const blankTemplatePodfile = `
+platform :ios, '11.0'
+
+target 'HelloWorld' do
+end
+`;
diff --git a/plugin/src/__tests__/withMapbox-test.ts b/plugin/src/__tests__/withMapbox-test.ts
new file mode 100644
index 000000000..886a58c0a
--- /dev/null
+++ b/plugin/src/__tests__/withMapbox-test.ts
@@ -0,0 +1,50 @@
+import {applyCocoaPodsModifications} from '../withMapbox';
+
+import * as fixtures from './fixtures/cocoapodFiles';
+
+describe(applyCocoaPodsModifications, () => {
+ it('adds blocks to a react native template podfile', () => {
+ expect(
+ applyCocoaPodsModifications(fixtures.reactNativeTemplatePodfile),
+ ).toMatchSnapshot();
+ });
+ it('adds blocks to a expo prebuild template podfile', () => {
+ expect(
+ applyCocoaPodsModifications(fixtures.expoTemplatePodfile),
+ ).toMatchSnapshot();
+ });
+ it('adds blocks to a expo prebuild template podfile with custom modifications ', () => {
+ expect(
+ applyCocoaPodsModifications(fixtures.customExpoTemplatePodfile),
+ ).toMatchSnapshot();
+ });
+ it('fails to add blocks to a bare podfile', () => {
+ expect(() =>
+ applyCocoaPodsModifications(fixtures.blankTemplatePodfile),
+ ).toThrow('Failed to match');
+ expect(() => applyCocoaPodsModifications('')).toThrow('Failed to match');
+ });
+ it('does not re add blocks to an applied template podfile', () => {
+ const runOnce = applyCocoaPodsModifications(
+ fixtures.reactNativeTemplatePodfile,
+ );
+
+ expect(applyCocoaPodsModifications(runOnce)).toMatch(runOnce);
+ });
+ it('works after revisions to blocks', () => {
+ const runOnce = applyCocoaPodsModifications(
+ fixtures.expoTemplateWithRevisions,
+ );
+
+ expect(runOnce).toMatchSnapshot();
+ });
+ // A known issue is that the regex won't work if the template
+ // has a pre_install/post_install block commented out, before the `use_react_native` function.
+ it('does not work with revisions to blocks after comments', () => {
+ const runOnce = applyCocoaPodsModifications(
+ fixtures.expoTemplateWithRevisionsAfterComments,
+ );
+
+ expect(runOnce).toMatchSnapshot();
+ });
+});
diff --git a/plugin/src/withMapbox.ts b/plugin/src/withMapbox.ts
new file mode 100644
index 000000000..5be61479c
--- /dev/null
+++ b/plugin/src/withMapbox.ts
@@ -0,0 +1,143 @@
+import {promises} from 'fs';
+import path from 'path';
+
+import {
+ ConfigPlugin,
+ createRunOncePlugin,
+ withDangerousMod,
+ withXcodeProject,
+ XcodeProject,
+} from '@expo/config-plugins';
+import {
+ mergeContents,
+ removeGeneratedContents,
+} from '@expo/config-plugins/build/utils/generateCode';
+
+let pkg: {name: string; version?: string} = {
+ name: '@react-native-mapbox-gl/maps',
+};
+try {
+ pkg = require('@react-native-mapbox-gl/maps/package.json');
+} catch {
+ // empty catch block
+}
+
+type InstallerBlockName = 'pre' | 'post';
+
+/**
+ * Dangerously adds the custom installer hooks to the Podfile.
+ * In the future this should be removed in favor of some custom hooks provided by Expo autolinking.
+ *
+ * https://github.com/react-native-mapbox-gl/maps/blob/master/ios/install.md#react-native--0600
+ * @param config
+ * @returns
+ */
+const withCocoaPodsInstallerBlocks: ConfigPlugin = c => {
+ return withDangerousMod(c, [
+ 'ios',
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+ async config => {
+ const file = path.join(config.modRequest.platformProjectRoot, 'Podfile');
+
+ const contents = await promises.readFile(file, 'utf8');
+
+ await promises.writeFile(
+ file,
+ applyCocoaPodsModifications(contents),
+ 'utf-8',
+ );
+ return config;
+ },
+ ]);
+};
+
+// Only the preinstaller block is required, the post installer block is
+// used for spm (swift package manager) which Expo doesn't currently support.
+export function applyCocoaPodsModifications(contents: string): string {
+ // Ensure installer blocks exist
+ let src = addInstallerBlock(contents, 'pre');
+ // src = addInstallerBlock(src, "post");
+ src = addMapboxInstallerBlock(src, 'pre');
+ // src = addMapboxInstallerBlock(src, "post");
+ return src;
+}
+
+export function addInstallerBlock(
+ src: string,
+ blockName: InstallerBlockName,
+): string {
+ const matchBlock = new RegExp(`${blockName}_install do \\|installer\\|`);
+ const tag = `${blockName}_installer`;
+ for (const line of src.split('\n')) {
+ const contents = line.trim();
+ // Ignore comments
+ if (!contents.startsWith('#')) {
+ // Prevent adding the block if it exists outside of comments.
+ if (contents.match(matchBlock)) {
+ // This helps to still allow revisions, since we enabled the block previously.
+ // Only continue if the generated block exists...
+ const modified = removeGeneratedContents(src, tag);
+ if (!modified) {
+ return src;
+ }
+ }
+ }
+ }
+
+ return mergeContents({
+ tag,
+ src,
+ newSrc: [` ${blockName}_install do |installer|`, ' end'].join('\n'),
+ anchor: /use_react_native/,
+ // We can't go after the use_react_native block because it might have parameters, causing it to be multi-line (see react-native template).
+ offset: 0,
+ comment: '#',
+ }).contents;
+}
+
+export function addMapboxInstallerBlock(
+ src: string,
+ blockName: InstallerBlockName,
+): string {
+ return mergeContents({
+ tag: `@react-native-mapbox-gl/maps-${blockName}_installer`,
+ src,
+ newSrc: ` $RNMBGL.${blockName}_install(installer)`,
+ anchor: new RegExp(`${blockName}_install do \\|installer\\|`),
+ offset: 1,
+ comment: '#',
+ }).contents;
+}
+
+/**
+ * Exclude building for arm64 on simulator devices in the pbxproj project.
+ * Without this, production builds targeting simulators will fail.
+ */
+export function setExcludedArchitectures(project: XcodeProject): XcodeProject {
+ const configurations = project.pbxXCBuildConfigurationSection();
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ for (const {buildSettings} of Object.values(configurations || {})) {
+ // Guessing that this is the best way to emulate Xcode.
+ // Using `project.addToBuildSettings` modifies too many targets.
+ if (typeof buildSettings?.PRODUCT_NAME !== 'undefined') {
+ buildSettings['"EXCLUDED_ARCHS[sdk=iphonesimulator*]"'] = '"arm64"';
+ }
+ }
+
+ return project;
+}
+
+const withExcludedSimulatorArchitectures: ConfigPlugin = c => {
+ return withXcodeProject(c, config => {
+ config.modResults = setExcludedArchitectures(config.modResults);
+ return config;
+ });
+};
+
+const withMapbox: ConfigPlugin = config => {
+ config = withExcludedSimulatorArchitectures(config);
+ return withCocoaPodsInstallerBlocks(config);
+};
+
+export default createRunOncePlugin(withMapbox, pkg.name, pkg.version);
diff --git a/plugin/tsconfig.json b/plugin/tsconfig.json
new file mode 100644
index 000000000..354bddb43
--- /dev/null
+++ b/plugin/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "expo-module-scripts/tsconfig.plugin",
+ "compilerOptions": {
+ "outDir": "build",
+ "rootDir": "src"
+ },
+ "include": ["./src"],
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
+}
diff --git a/react-native-mapbox-gl.podspec b/react-native-mapbox-gl.podspec
index 959c24b5e..0ea7ce140 100644
--- a/react-native-mapbox-gl.podspec
+++ b/react-native-mapbox-gl.podspec
@@ -2,12 +2,84 @@ require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
-default_ios_mapbox_version = '~> 5.8.0'
+default_ios_mapbox_version = '~> 5.9.0'
rnmbgl_ios_version = $ReactNativeMapboxGLIOSVersion || ENV["REACT_NATIVE_MAPBOX_MAPBOX_IOS_VERSION"] || default_ios_mapbox_version
if ENV.has_key?("REACT_NATIVE_MAPBOX_MAPBOX_IOS_VERSION")
puts "REACT_NATIVE_MAPBOX_MAPBOX_IOS_VERSION env is deprecated please use `$ReactNativeMapboxGLIOSVersion = \"#{rnmbgl_ios_version}\"`"
end
+TargetsToChangeToDynamic = ['MapboxMobileEvents']
+
+$RNMBGL = Object.new
+
+def $RNMBGL._add_spm_to_target(project, target, url, requirement, product_name)
+ pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
+ ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
+ pkg = project.root_object.package_references.find { |p| p.class == pkg_class && p.repositoryURL == url }
+ if !pkg
+ pkg = project.new(pkg_class)
+ pkg.repositoryURL = url
+ pkg.requirement = requirement
+ project.root_object.package_references << pkg
+ end
+ ref = target.package_product_dependencies.find { |r| r.class == ref_class && r.package == pkg && r.product_name == product_name }
+ if !ref
+ ref = project.new(ref_class)
+ ref.package = pkg
+ ref.product_name = product_name
+ target.package_product_dependencies << ref
+ end
+end
+
+def $RNMBGL.post_install(installer)
+ if $RNMBGL_Use_SPM
+ spm_spec = {
+ url: "https://github.com/maplibre/maplibre-gl-native-distribution",
+ requirement: {
+ kind: "exactVersion",
+ version: "5.12.1"
+ },
+ product_name: "Mapbox"
+ }
+
+ if $RNMBGL_Use_SPM.is_a?(Hash)
+ spm_spec = $RNMBGL_Use_SPM
+ end
+ project = installer.pods_project
+ self._add_spm_to_target(
+ project,
+ project.targets.find { |t| t.name == "react-native-mapbox-gl"},
+ spm_spec[:url],
+ spm_spec[:requirement],
+ spm_spec[:product_name]
+ )
+
+ installer.aggregate_targets.group_by(&:user_project).each do |project, targets|
+ targets.each do |target|
+ target.user_targets.each do |user_target|
+ self._add_spm_to_target(
+ project,
+ user_target,
+ spm_spec[:url],
+ spm_spec[:requirement],
+ spm_spec[:product_name]
+ )
+ end
+ end
+ end
+ end
+end
+
+def $RNMBGL.pre_install(installer)
+ installer.aggregate_targets.each do |target|
+ target.pod_targets.select { |p| TargetsToChangeToDynamic.include?(p.name) }.each do |mobile_events_target|
+ mobile_events_target.instance_variable_set(:@build_type,Pod::BuildType.dynamic_framework)
+ puts "* Changed #{mobile_events_target.name} to #{mobile_events_target.send(:build_type)}"
+ fail "Unable to change build_type" unless mobile_events_target.send(:build_type) == Pod::BuildType.dynamic_framework
+ end
+ end
+end
+
Pod::Spec.new do |s|
s.name = "react-native-mapbox-gl"
s.summary = "React Native Component for Mapbox GL"
@@ -18,19 +90,24 @@ Pod::Spec.new do |s|
s.license = "MIT"
s.platform = :ios, "8.0"
+ if !$RNMBGL_Use_SPM
s.dependency 'Mapbox-iOS-SDK', rnmbgl_ios_version
+ end
s.dependency 'React-Core'
s.dependency 'React'
s.subspec 'DynamicLibrary' do |sp|
sp.source_files = "ios/RCTMGL/**/*.{h,m}"
+ if $RNMGL_USE_MAPLIBRE
+ sp.compiler_flags = '-DRNMGL_USE_MAPLIBRE=1'
+ end
end
if ENV["REACT_NATIVE_MAPBOX_GL_USE_FRAMEWORKS"]
s.default_subspecs= ['DynamicLibrary']
else
s.subspec 'StaticLibraryFixer' do |sp|
- s.dependency '@react-native-mapbox-gl-mapbox-static', rnmbgl_ios_version
+ # s.dependency '@react-native-mapbox-gl-mapbox-static', rnmbgl_ios_version
end
s.default_subspecs= ['DynamicLibrary', 'StaticLibraryFixer']
diff --git a/scripts/upgradeExample.sh b/scripts/upgradeExample.sh
deleted file mode 100644
index fb623f17a..000000000
--- a/scripts/upgradeExample.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-react-native init RNMapboxGLExample
-mv example/src RNMapboxGLExample/src
-mv example/scripts RNMapboxGLExample/scripts
-rm -rf example
-mv RNMapboxGLExample example
-
-# Edit package.json
-
-cd example
-npx json -I -f package.json -e 'this.scripts["copy:changes"]="node ./scripts/watch_rngl.js"'
-npx json -I -f package.json -e 'this.scripts["pack:gl"]="./scripts/npm_pack_rngl.sh"'
-npx json -I -f package.json -e 'this.scripts["clean:node:modules"]="./scripts/clean_node_modules.sh"'
-npx json -I -f package.json -e 'this.scripts.preinstall="npm run pack:gl"'
-npx json -I -f package.json -e 'this.scripts.postinstall="node ./scripts/set_access_token.js"'
-npx json -I -f package.json -e 'this.scripts["reset:from:gl"]="npm run clean:node:modules && npm install"'
-npx json -I -f package.json -e 'this.dependencies["@react-native-mapbox-gl/maps"]="file:../react-native-mapbox-gl-maps.tgz"'
-
-# Install depencies
-touch accesstoken
-yarn add @mapbox/geo-viewport@0.4.0 @turf/along@5.1.5 @turf/bearing@5.1.5 @turf/distance@5.1.5 @turf/helpers@4.7.3 @turf/line-distance@4.7.3 @turf/nearest@4.7.3 buffer@5.1.0 install@0.12.2 @mapbox/mapbox-sdk@0.6.0 moment@2.24.0 npm@5.10.0 prop-types@15.7.2 react-native-elements@1.1.0 react-native-vector-icons react-native-safe-area-view@0.13.1 react-navigation@2.18.3 url@0.11.0
-react-native link
-rm accesstoken
-cd ios && pod install && cd ../
diff --git a/setup-jest.js b/setup-jest.js
index f9b555fc9..f9ab416b5 100644
--- a/setup-jest.js
+++ b/setup-jest.js
@@ -2,7 +2,7 @@ import {NativeModules} from 'react-native';
function keyMirror(keys) {
const obj = {};
- keys.forEach((key) => (obj[key] = key));
+ keys.forEach(key => (obj[key] = key));
return obj;
}
@@ -87,7 +87,7 @@ NativeModules.MGLModule = {
};
NativeModules.MGLOfflineModule = {
- createPack: (packOptions) => {
+ createPack: packOptions => {
return Promise.resolve({
bounds: packOptions.bounds,
metadata: JSON.stringify({name: packOptions.name}),
@@ -115,8 +115,8 @@ NativeModules.MGLLocationModule = {
pause: jest.fn(),
};
-// Mock for AbortController. Will probably not be required during testing.
-window = {};
-window.AbortController = class {
+// Mock for global AbortController
+global.AbortController = class {
+ signal = 'test-signal';
abort = jest.fn();
};
diff --git a/example/tsconfig.json b/tsconfig.json
similarity index 100%
rename from example/tsconfig.json
rename to tsconfig.json