diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraStop.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraStop.java index f45c07e..135ee18 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraStop.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraStop.java @@ -25,7 +25,11 @@ public class CameraStop { private LatLng mLatLng; private LatLngBounds mBounds; - private int mBoundsPadding = 0; + private int mBoundsPaddingLeft = 0; + private int mBoundsPaddingRight = 0; + private int mBoundsPaddingBottom = 0; + private int mBooundsPaddingTop = 0; + private int mMode = CameraMode.EASE; private int mDuration = 2000; private MapboxMap.CancelableCallback mCallback; @@ -56,9 +60,12 @@ public class CameraStop { mCallback = callback; } - public void setBounds(LatLngBounds bounds, int padding) { + public void setBounds(LatLngBounds bounds, int paddingLeft, int paddingRight, int paddingTop, int paddingBottom) { mBounds = bounds; - mBoundsPadding = padding; + mBoundsPaddingLeft = paddingLeft; + mBoundsPaddingRight = paddingRight; + mBooundsPaddingTop = paddingTop; + mBoundsPaddingBottom = paddingBottom; } public void setMode(@CameraMode.Mode int mode) { @@ -67,7 +74,8 @@ public class CameraStop { public CameraUpdateItem toCameraUpdate() { if (mBounds != null) { - CameraUpdate update = CameraUpdateFactory.newLatLngBounds(mBounds, mBoundsPadding); + CameraUpdate update = CameraUpdateFactory.newLatLngBounds(mBounds, mBoundsPaddingLeft, + mBooundsPaddingTop, mBoundsPaddingRight, mBoundsPaddingBottom); return new CameraUpdateItem(update, mDuration, mCallback, CameraMode.FLIGHT); } @@ -117,9 +125,14 @@ public class CameraStop { } if (readableMap.hasKey("bounds")) { - int padding = readableMap.hasKey("boundsPadding") ? readableMap.getInt("boundsPadding") : 0; + int paddingTop = getBoundsPaddingByKey(readableMap, "boundsPaddingTop"); + int paddingRight = getBoundsPaddingByKey(readableMap, "boundsPaddingRight"); + int paddingBottom = getBoundsPaddingByKey(readableMap, "boundsPaddingBottom"); + int paddingLeft = getBoundsPaddingByKey(readableMap, "boundsPaddingLeft"); + FeatureCollection collection = FeatureCollection.fromJson(readableMap.getString("bounds")); - stop.setBounds(ConvertUtils.toLatLngBounds(collection), padding); + stop.setBounds(ConvertUtils.toLatLngBounds(collection), paddingLeft, paddingRight, + paddingTop, paddingBottom); } if (readableMap.hasKey("mode")) { @@ -138,4 +151,8 @@ public class CameraStop { stop.setCallback(callback); return stop; } + + private static int getBoundsPaddingByKey(ReadableMap map, String key) { + return map.hasKey(key) ? map.getInt(key) : 0; + } } diff --git a/docs/MapView.md b/docs/MapView.md index d5e4512..1c7d9fb 100644 --- a/docs/MapView.md +++ b/docs/MapView.md @@ -39,6 +39,36 @@ | onSetCameraComplete | `func` | `none` | `false` | This event is triggered once the camera is finished after calling setCamera | ### methods +#### queryRenderedFeaturesAtPoint(coordinate[, filter][, layerIDs]) +##### arguments +| Name | Type | Required | Description | +| ---- | :--: | :------: | :----------: | +| `coordinate` | `Array` | `Yes` | A point expressed in the map view’s coordinate system. | +| `filter` | `Array` | `No` | A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array. | +| `layerIDs` | `Array` | `No` | A array of layer id's to filter the features by | + + + +```javascript +this._map.queryRenderedFeaturesAtPoint([30, 40], ['==', 'type', 'Point'], ['id1', 'id2']) +``` + + +#### queryRenderedFeaturesInRect(bbox[, filter][, layerIDs]) +##### arguments +| Name | Type | Required | Description | +| ---- | :--: | :------: | :----------: | +| `bbox` | `Array` | `Yes` | A rectangle expressed in the map view’s coordinate system. | +| `filter` | `Array` | `No` | A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array. | +| `layerIDs` | `Array` | `No` | A array of layer id's to filter the features by | + + + +```javascript +this._map.queryRenderedFeaturesInRect([30, 40, 20, 10], ['==', 'type', 'Point'], ['id1', 'id2']) +``` + + #### fitBounds(northEastCoordinates, southWestCoordinates[, padding][, duration]) ##### arguments | Name | Type | Required | Description | @@ -52,7 +82,9 @@ ```javascript this.map.fitBounds([lng, lat], [lng, lat]) -this.map.fitBounds([lng, lat], [lng, lat], 20, 1000) +this.map.fitBounds([lng, lat], [lng, lat], 20, 1000) // padding for all sides +this.map.fitBounds([lng, lat], [lng, lat], [verticalPadding, horizontalPadding], 1000) +this.map.fitBounds([lng, lat], [lng, lat], [top, right, bottom, left], 1000) ``` diff --git a/docs/docs.json b/docs/docs.json index 4d5b783..3b4384c 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -778,9 +778,93 @@ "MapView": { "description": "MapView backed by Mapbox Native GL", "methods": [ + { + "name": "queryRenderedFeaturesAtPoint", + "docblock": "Returns an array of rendered map features that intersect with a given point.\n\n@example\nthis._map.queryRenderedFeaturesAtPoint([30, 40], ['==', 'type', 'Point'], ['id1', 'id2'])\n\n@param {Array} coordinate - A point expressed in the map view’s coordinate system.\n@param {Array=} filter - A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array.\n@param {Array=} layerIDs - A array of layer id's to filter the features by\n@return {FeatureCollection}", + "modifiers": [ + "async" + ], + "params": [ + { + "name": "coordinate", + "description": "A point expressed in the map view’s coordinate system.", + "type": { + "name": "Array" + } + }, + { + "name": "filter", + "description": "A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array.", + "type": { + "name": "Array" + }, + "optional": true + }, + { + "name": "layerIDs", + "description": "A array of layer id's to filter the features by", + "type": { + "name": "Array" + }, + "optional": true + } + ], + "returns": { + "description": null, + "type": { + "name": "FeatureCollection" + } + }, + "description": "Returns an array of rendered map features that intersect with a given point.", + "examples": [ + "\nthis._map.queryRenderedFeaturesAtPoint([30, 40], ['==', 'type', 'Point'], ['id1', 'id2'])\n\n" + ] + }, + { + "name": "queryRenderedFeaturesInRect", + "docblock": "Returns an array of rendered map features that intersect with the given rectangle,\nrestricted to the given style layers and filtered by the given predicate.\n\n@example\nthis._map.queryRenderedFeaturesInRect([30, 40, 20, 10], ['==', 'type', 'Point'], ['id1', 'id2'])\n\n@param {Array} bbox - A rectangle expressed in the map view’s coordinate system.\n@param {Array=} filter - A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array.\n@param {Array=} layerIDs - A array of layer id's to filter the features by\n@return {FeatureCollection}", + "modifiers": [ + "async" + ], + "params": [ + { + "name": "bbox", + "description": "A rectangle expressed in the map view’s coordinate system.", + "type": { + "name": "Array" + } + }, + { + "name": "filter", + "description": "A set of strings that correspond to the names of layers defined in the current style. Only the features contained in these layers are included in the returned array.", + "type": { + "name": "Array" + }, + "optional": true + }, + { + "name": "layerIDs", + "description": " A array of layer id's to filter the features by", + "type": { + "name": "Array" + }, + "optional": true + } + ], + "returns": { + "description": null, + "type": { + "name": "FeatureCollection" + } + }, + "description": "Returns an array of rendered map features that intersect with the given rectangle,\nrestricted to the given style layers and filtered by the given predicate.", + "examples": [ + "\nthis._map.queryRenderedFeaturesInRect([30, 40, 20, 10], ['==', 'type', 'Point'], ['id1', 'id2'])\n\n" + ] + }, { "name": "fitBounds", - "docblock": "Map camera transitions to fit provided bounds\n\n@example\nthis.map.fitBounds([lng, lat], [lng, lat])\nthis.map.fitBounds([lng, lat], [lng, lat], 20, 1000)\n\n@param {Array} northEastCoordinates - North east coordinate of bound\n@param {Array} southWestCoordinates - South west coordinate of bound\n@param {Number=} padding - Camera padding for bound\n@param {Number=} duration - Duration of camera animation\n@return {void}", + "docblock": "Map camera transitions to fit provided bounds\n\n@example\nthis.map.fitBounds([lng, lat], [lng, lat])\nthis.map.fitBounds([lng, lat], [lng, lat], 20, 1000) // padding for all sides\nthis.map.fitBounds([lng, lat], [lng, lat], [verticalPadding, horizontalPadding], 1000)\nthis.map.fitBounds([lng, lat], [lng, lat], [top, right, bottom, left], 1000)\n\n@param {Array} northEastCoordinates - North east coordinate of bound\n@param {Array} southWestCoordinates - South west coordinate of bound\n@param {Number=} padding - Camera padding for bound\n@param {Number=} duration - Duration of camera animation\n@return {void}", "modifiers": [], "params": [ { @@ -822,7 +906,7 @@ }, "description": "Map camera transitions to fit provided bounds", "examples": [ - "\nthis.map.fitBounds([lng, lat], [lng, lat])\nthis.map.fitBounds([lng, lat], [lng, lat], 20, 1000)\n\n" + "\nthis.map.fitBounds([lng, lat], [lng, lat])\nthis.map.fitBounds([lng, lat], [lng, lat], 20, 1000) // padding for all sides\nthis.map.fitBounds([lng, lat], [lng, lat], [verticalPadding, horizontalPadding], 1000)\nthis.map.fitBounds([lng, lat], [lng, lat], [top, right, bottom, left], 1000)\n\n" ] }, { diff --git a/ios/RCTMGL/CameraStop.h b/ios/RCTMGL/CameraStop.h index 1e1c069..7efe3bc 100644 --- a/ios/RCTMGL/CameraStop.h +++ b/ios/RCTMGL/CameraStop.h @@ -10,10 +10,13 @@ @interface CameraStop : NSObject -@property (nonatomic, assign) NSNumber* pitch; -@property (nonatomic, assign) NSNumber* heading; -@property (nonatomic, assign) NSNumber* zoom; -@property (nonatomic, assign) NSNumber *boundsPadding; +@property (nonatomic, assign) NSNumber *pitch; +@property (nonatomic, assign) NSNumber *heading; +@property (nonatomic, assign) NSNumber *zoom; +@property (nonatomic, assign) NSNumber *boundsPaddingLeft; +@property (nonatomic, assign) NSNumber *boundsPaddingRight; +@property (nonatomic, assign) NSNumber *boundsPaddingTop; +@property (nonatomic, assign) NSNumber *boundsPaddingBottom; @property (nonatomic, assign) NSNumber *mode; @property (nonatomic, assign) NSTimeInterval duration; diff --git a/ios/RCTMGL/CameraStop.m b/ios/RCTMGL/CameraStop.m index 0280680..78ae35a 100644 --- a/ios/RCTMGL/CameraStop.m +++ b/ios/RCTMGL/CameraStop.m @@ -52,8 +52,20 @@ if (args[@"bounds"]) { stop.bounds = [RCTMGLUtils fromFeatureCollection:args[@"bounds"]]; - if (args[@"boundsPadding"]) { - stop.boundsPadding = args[@"boundsPadding"]; + if (args[@"boundsPaddingLeft"]) { + stop.boundsPaddingLeft = args[@"boundsPaddingLeft"]; + } + + if (args[@"boundsPaddingRight"]) { + stop.boundsPaddingRight = args[@"boundsPaddingRight"]; + } + + if (args[@"boundsPaddingTop"]) { + stop.boundsPaddingTop = args[@"boundsPaddingTop"]; + } + + if (args[@"boundsPaddingBottom"]) { + stop.boundsPaddingBottom = args[@"boundsPaddingBottom"]; } } diff --git a/ios/RCTMGL/CameraUpdateItem.m b/ios/RCTMGL/CameraUpdateItem.m index 8773caa..7cfcf43 100644 --- a/ios/RCTMGL/CameraUpdateItem.m +++ b/ios/RCTMGL/CameraUpdateItem.m @@ -47,7 +47,10 @@ - (void)_fitBoundsCamera:(RCTMGLMapView*)mapView withCompletionHandler:(void (^)(void))completionHandler { MGLCoordinateBounds bounds = _cameraStop.bounds; - CGFloat padding = [_cameraStop.boundsPadding floatValue]; + CGFloat paddingTop = [_cameraStop.boundsPaddingTop floatValue]; + CGFloat paddingRight = [_cameraStop.boundsPaddingRight floatValue]; + CGFloat paddingBottom = [_cameraStop.boundsPaddingBottom floatValue]; + CGFloat paddingLeft = [_cameraStop.boundsPaddingLeft floatValue]; CLLocationCoordinate2D coordinates[] = { { bounds.ne.latitude, bounds.sw.longitude }, @@ -55,10 +58,10 @@ { bounds.sw.latitude, bounds.ne.longitude }, bounds.ne }; - + [mapView setVisibleCoordinates:coordinates count:4 - edgePadding:UIEdgeInsetsMake(padding, padding, padding, padding) + edgePadding:UIEdgeInsetsMake(paddingTop, paddingLeft, paddingBottom, paddingRight) direction:mapView.direction duration:_cameraStop.duration animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut] diff --git a/javascript/components/MapView.js b/javascript/components/MapView.js index 693147a..ff59475 100644 --- a/javascript/components/MapView.js +++ b/javascript/components/MapView.js @@ -275,7 +275,7 @@ class MapView extends React.Component { * * @param {Array} northEastCoordinates - North east coordinate of bound * @param {Array} southWestCoordinates - South west coordinate of bound - * @param {Number=} padding - Camera padding for bound + * @param {(Number|Array)=} padding - Camera padding for bound * @param {Number=} duration - Duration of camera animation * @return {void} */ @@ -283,11 +283,38 @@ class MapView extends React.Component { if (!this._nativeRef) { return; } + + let pad = { + paddingLeft: 0, + paddingRight: 0, + paddingTop: 0, + paddingBottom: 0, + }; + + if (Array.isArray(padding)) { + if (padding.length === 2) { + pad.paddingTop = padding[0]; + pad.paddingBottom = padding[0]; + pad.paddingLeft = padding[1]; + pad.paddingRight = padding[1]; + } else if (padding.length === 4) { + pad.paddingTop = padding[0]; + pad.paddingRight = padding[1]; + pad.paddingBottom = padding[2]; + pad.paddingLeft = padding[3]; + } + } else { + pad.paddingLeft = padding; + pad.paddingRight = padding; + pad.paddingTop = padding; + pad.paddingBottom = padding; + } + return this.setCamera({ bounds: { ne: northEastCoordinates, sw: southWestCoordinates, - padding: padding, + ...pad, }, duration: duration, mode: MapboxGL.CameraModes.Flight, @@ -403,9 +430,12 @@ class MapView extends React.Component { } if (config.bounds && config.bounds.ne && config.bounds.sw) { - const { ne, sw, padding } = config.bounds; + const { ne, sw, paddingLeft, paddingRight, paddingTop, paddingBottom } = config.bounds; stopConfig.bounds = toJSONString(makeLatLngBounds(ne, sw)); - stopConfig.boundsPadding = padding; + stopConfig.boundsPaddingTop = paddingTop || 0; + stopConfig.boundsPaddingRight = paddingRight || 0; + stopConfig.boundsPaddingBottom = paddingBottom || 0; + stopConfig.boundsPaddingLeft = paddingLeft || 0; } return stopConfig; diff --git a/javascript/utils/geoUtils.js b/javascript/utils/geoUtils.js index 7ddf612..157db2b 100644 --- a/javascript/utils/geoUtils.js +++ b/javascript/utils/geoUtils.js @@ -1,7 +1,7 @@ import turfHelpers from '@turf/helpers'; -export function makePoint (coordinates) { - return turfHelpers.point(coordinates); +export function makePoint (coordinates, properties) { + return turfHelpers.point(coordinates, properties); } export function makeLatLngBounds (northEastCoordinates, southWestCoordinates) { @@ -10,3 +10,17 @@ export function makeLatLngBounds (northEastCoordinates, southWestCoordinates) { turfHelpers.point(southWestCoordinates), ]); } + +export function makeFeature(geometry, properties) { + return turfHelpers.feature(geometry, properties); +} + +export function makeFeatureCollection (features = []) { + return turfHelpers.featureCollection(features); +} + +export function addToFeatureCollection (featureCollection, feature) { + let shallowFeatureCollection = Object.assign({}, featureCollection); + featureCollection.features.push(feature); + return featureCollection; +}