Skip to content

Commit a6b8729

Browse files
alveligrborn
authored andcommitted
Merge Ground Overlay Support (PR-1586) (react-native-maps#1918)
1 parent 9c3ad31 commit a6b8729

File tree

9 files changed

+307
-1
lines changed

9 files changed

+307
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ versions you should add `react` as a dependency in your `package.json`.
3636

3737
[`<MapView.Circle />` Component API](docs/circle.md)
3838

39+
[`<MapView.Overlay />` Component API](docs/overlay.md)
40+
3941
## General Usage
4042

4143
```js

docs/overlay.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# `<MapView.Overlay />` Component API
2+
3+
## Props
4+
5+
| Prop | Type | Default | Note |
6+
|---|---|---|---|
7+
| `image` | `ImageSource` | A custom image to be used as the overlay. Only required local image resources and uri (as for images located in the net) are allowed to be used.
8+
| `bounds` | `Array<LatLng>` | | The coordinates for the image (left-top corner, right-bottom corner).
9+
10+
## Types
11+
12+
```
13+
type LatLng {
14+
latitude: Number,
15+
longitude: Number,
16+
}
17+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.airbnb.android.react.maps;
2+
3+
import android.content.Context;
4+
import android.graphics.Bitmap;
5+
6+
import com.facebook.react.bridge.ReadableArray;
7+
import com.facebook.react.bridge.ReadableMap;
8+
import com.google.android.gms.maps.GoogleMap;
9+
import com.google.android.gms.maps.model.BitmapDescriptor;
10+
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
11+
import com.google.android.gms.maps.model.GroundOverlay;
12+
import com.google.android.gms.maps.model.GroundOverlayOptions;
13+
import com.google.android.gms.maps.model.LatLng;
14+
import com.google.android.gms.maps.model.LatLngBounds;
15+
16+
import java.util.ArrayList;
17+
18+
public class AirMapOverlay extends AirMapFeature {
19+
20+
private GroundOverlayOptions groundOverlayOptions;
21+
private GroundOverlay groundOverlay;
22+
private LatLngBounds bounds;
23+
private BitmapDescriptor iconBitmapDescriptor;
24+
private float zIndex;
25+
private float transparency;
26+
27+
28+
public AirMapOverlay(Context context) {
29+
super(context);
30+
}
31+
32+
33+
public void setBounds(ReadableArray bounds) {
34+
ArrayList<ReadableArray> tmpBounds = new ArrayList<>(bounds.size());
35+
for (int i = 0; i < bounds.size(); i++) {
36+
tmpBounds.add(bounds.getArray(i));
37+
}
38+
this.bounds = new LatLngBounds(
39+
new LatLng(Double.parseDouble(bounds.getArray(0).getString(0)), Double.parseDouble(bounds.getArray(0)
40+
.getString(1))),
41+
new LatLng(Double.parseDouble(bounds.getArray(1).getString(0)), Double
42+
.parseDouble(bounds.getArray(1)
43+
.getString(1)))
44+
);
45+
if (groundOverlay != null) {
46+
groundOverlay.setPositionFromBounds(this.bounds);
47+
}
48+
}
49+
50+
public void setZIndex(float zIndex) {
51+
this.zIndex = zIndex;
52+
if (groundOverlay != null) {
53+
groundOverlay.setZIndex(zIndex);
54+
}
55+
}
56+
57+
// public void setTransparency(float transparency) {
58+
// this.transparency = transparency;
59+
// if (groundOverlay != null) {
60+
// groundOverlay.setTransparency(transparency);
61+
// }
62+
// }
63+
64+
public void setImage(String uri) {
65+
this.iconBitmapDescriptor = getBitmapDescriptorByName(uri);
66+
67+
if (groundOverlay != null) {
68+
groundOverlay.setImage(this.iconBitmapDescriptor);
69+
}
70+
}
71+
72+
73+
public GroundOverlayOptions getGroundOverlayOptions() {
74+
if (groundOverlayOptions == null) {
75+
groundOverlayOptions = createGroundOverlayOptions();
76+
}
77+
return groundOverlayOptions;
78+
}
79+
80+
private GroundOverlayOptions createGroundOverlayOptions() {
81+
GroundOverlayOptions options = new GroundOverlayOptions();
82+
options.image(iconBitmapDescriptor);
83+
options.positionFromBounds(bounds);
84+
// options.transparency(transparency);
85+
options.zIndex(zIndex);
86+
return options;
87+
}
88+
89+
private BitmapDescriptor getBitmapDescriptorByName(String name) {
90+
Bitmap bitmap = ImageUtil.convert(name);
91+
return BitmapDescriptorFactory.fromBitmap(bitmap);
92+
}
93+
94+
@Override
95+
public Object getFeature() {
96+
return groundOverlay;
97+
}
98+
99+
@Override
100+
public void addToMap(GoogleMap map) {
101+
groundOverlay = map.addGroundOverlay(getGroundOverlayOptions());
102+
groundOverlay.setClickable(true);
103+
}
104+
105+
@Override
106+
public void removeFromMap(GoogleMap map) {
107+
groundOverlay.remove();
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.airbnb.android.react.maps;
2+
3+
import android.content.Context;
4+
import android.os.Build;
5+
import android.util.DisplayMetrics;
6+
import android.view.WindowManager;
7+
8+
import com.facebook.react.bridge.ReactApplicationContext;
9+
import com.facebook.react.bridge.ReadableArray;
10+
import com.facebook.react.common.MapBuilder;
11+
import com.facebook.react.uimanager.ThemedReactContext;
12+
import com.facebook.react.uimanager.ViewGroupManager;
13+
import com.facebook.react.uimanager.annotations.ReactProp;
14+
15+
import java.util.Map;
16+
17+
import javax.annotation.Nullable;
18+
19+
public class AirMapOverlayManager extends ViewGroupManager<AirMapOverlay> {
20+
private final DisplayMetrics metrics;
21+
22+
public AirMapOverlayManager(ReactApplicationContext reactContext) {
23+
super();
24+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
25+
metrics = new DisplayMetrics();
26+
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
27+
.getDefaultDisplay()
28+
.getRealMetrics(metrics);
29+
} else {
30+
metrics = reactContext.getResources().getDisplayMetrics();
31+
}
32+
}
33+
34+
@Override
35+
public String getName() {
36+
return "AIRMapOverlay";
37+
}
38+
39+
@Override
40+
public AirMapOverlay createViewInstance(ThemedReactContext context) {
41+
return new AirMapOverlay(context);
42+
}
43+
44+
@ReactProp(name = "bounds")
45+
public void setBounds(AirMapOverlay view, ReadableArray bounds) {
46+
view.setBounds(bounds);
47+
}
48+
49+
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
50+
public void setZIndex(AirMapOverlay view, float zIndex) {
51+
view.setZIndex(zIndex);
52+
}
53+
54+
// @ReactProp(name = "transparency", defaultFloat = 1.0f)
55+
// public void setTransparency(AirMapOverlay view, float transparency) {
56+
// view.setTransparency(transparency);
57+
// }
58+
59+
@ReactProp(name = "image")
60+
public void setImage(AirMapOverlay view, @Nullable String source) {
61+
view.setImage(source);
62+
}
63+
64+
65+
@Override
66+
@Nullable
67+
public Map getExportedCustomDirectEventTypeConstants() {
68+
return MapBuilder.of(
69+
"onPress", MapBuilder.of("registrationName", "onPress")
70+
);
71+
}
72+
}

lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java

+4
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ public void addFeature(View child, int index) {
506506
AirMapLocalTile localTileView = (AirMapLocalTile) child;
507507
localTileView.addToMap(map);
508508
features.add(index, localTileView);
509+
} else if (child instanceof AirMapOverlay) {
510+
AirMapOverlay overlayView = (AirMapOverlay) child;
511+
overlayView.addToMap(map);
512+
features.add(index, overlayView);
509513
} else if (child instanceof ViewGroup) {
510514
ViewGroup children = (ViewGroup) child;
511515
for (int i = 0; i < children.getChildCount(); i++) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.airbnb.android.react.maps;
2+
3+
4+
import android.graphics.Bitmap;
5+
import android.graphics.BitmapFactory;
6+
import android.util.Base64;
7+
8+
import java.io.ByteArrayOutputStream;
9+
10+
public class ImageUtil {
11+
public static Bitmap convert(String base64Str) throws IllegalArgumentException {
12+
byte[] decodedBytes = Base64.decode(
13+
base64Str.substring(base64Str.indexOf(",") + 1),
14+
Base64.DEFAULT
15+
);
16+
17+
return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
18+
}
19+
20+
public static String convert(Bitmap bitmap) {
21+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
22+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
23+
24+
return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
25+
}
26+
27+
}

lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
4040
AirMapLiteManager mapLiteManager = new AirMapLiteManager(reactContext);
4141
AirMapUrlTileManager urlTileManager = new AirMapUrlTileManager(reactContext);
4242
AirMapLocalTileManager localTileManager = new AirMapLocalTileManager(reactContext);
43+
AirMapOverlayManager overlayManager = new AirMapOverlayManager(reactContext);
4344

4445
return Arrays.<ViewManager>asList(
4546
calloutManager,
@@ -50,6 +51,8 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
5051
mapManager,
5152
mapLiteManager,
5253
urlTileManager,
53-
localTileManager);
54+
localTileManager,
55+
overlayManager
56+
);
5457
}
5558
}

lib/components/MapOverlay.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { PropTypes, Component } from 'react';
2+
import {
3+
View,
4+
StyleSheet,
5+
Animated,
6+
} from 'react-native';
7+
8+
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
9+
import decorateMapComponent, {
10+
SUPPORTED,
11+
USES_DEFAULT_IMPLEMENTATION,
12+
} from './decorateMapComponent';
13+
14+
const viewConfig = {
15+
uiViewClassName: 'AIR<provider>MapOverlay',
16+
validAttributes: {
17+
image: true,
18+
},
19+
};
20+
21+
const propTypes = {
22+
...View.propTypes,
23+
// A custom image to be used as overlay.
24+
image: PropTypes.any.isRequired,
25+
// Top left and bottom right coordinates for the overlay
26+
bounds: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired,
27+
};
28+
29+
class MapOverlay extends Component {
30+
31+
render() {
32+
let image;
33+
if (this.props.image) {
34+
image = resolveAssetSource(this.props.image) || {};
35+
image = image.uri;
36+
}
37+
38+
const AIRMapOverlay = this.getAirComponent();
39+
40+
return (
41+
<AIRMapOverlay
42+
{...this.props}
43+
image={image}
44+
style={[styles.overlay, this.props.style]}
45+
/>
46+
);
47+
}
48+
}
49+
50+
MapOverlay.propTypes = propTypes;
51+
MapOverlay.viewConfig = viewConfig;
52+
53+
const styles = StyleSheet.create({
54+
overlay: {
55+
position: 'absolute',
56+
backgroundColor: 'transparent',
57+
},
58+
});
59+
60+
MapOverlay.Animated = Animated.createAnimatedComponent(MapOverlay);
61+
62+
module.exports = decorateMapComponent(MapOverlay, {
63+
componentType: 'Overlay',
64+
providers: {
65+
google: {
66+
ios: SUPPORTED,
67+
android: USES_DEFAULT_IMPLEMENTATION,
68+
},
69+
},
70+
});

lib/components/MapView.js

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import MapPolyline from './MapPolyline';
1616
import MapPolygon from './MapPolygon';
1717
import MapCircle from './MapCircle';
1818
import MapCallout from './MapCallout';
19+
import MapOverlay from './MapOverlay';
1920
import MapUrlTile from './MapUrlTile';
2021
import MapLocalTile from './MapLocalTile';
2122
import AnimatedRegion from './AnimatedRegion';
@@ -749,6 +750,7 @@ MapView.Polygon = MapPolygon;
749750
MapView.Circle = MapCircle;
750751
MapView.UrlTile = MapUrlTile;
751752
MapView.LocalTile = MapLocalTile;
753+
MapView.Overlay = MapOverlay;
752754
MapView.Callout = MapCallout;
753755
Object.assign(MapView, ProviderConstants);
754756
MapView.ProviderPropType = PropTypes.oneOf(Object.values(ProviderConstants));

0 commit comments

Comments
 (0)