Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Ground Overlay Support (PR-1586) #1918

Merged
merged 1 commit into from
Dec 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ versions you should add `react` as a dependency in your `package.json`.

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

[`<MapView.Overlay />` Component API](docs/overlay.md)

## General Usage

```js
Expand Down
17 changes: 17 additions & 0 deletions docs/overlay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `<MapView.Overlay />` Component API

## Props

| Prop | Type | Default | Note |
|---|---|---|---|
| `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.
| `bounds` | `Array<LatLng>` | | The coordinates for the image (left-top corner, right-bottom corner).

## Types

```
type LatLng {
latitude: Number,
longitude: Number,
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.airbnb.android.react.maps;

import android.content.Context;
import android.graphics.Bitmap;

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;

import java.util.ArrayList;

public class AirMapOverlay extends AirMapFeature {

private GroundOverlayOptions groundOverlayOptions;
private GroundOverlay groundOverlay;
private LatLngBounds bounds;
private BitmapDescriptor iconBitmapDescriptor;
private float zIndex;
private float transparency;


public AirMapOverlay(Context context) {
super(context);
}


public void setBounds(ReadableArray bounds) {
ArrayList<ReadableArray> tmpBounds = new ArrayList<>(bounds.size());
for (int i = 0; i < bounds.size(); i++) {
tmpBounds.add(bounds.getArray(i));
}
this.bounds = new LatLngBounds(
new LatLng(Double.parseDouble(bounds.getArray(0).getString(0)), Double.parseDouble(bounds.getArray(0)
.getString(1))),
new LatLng(Double.parseDouble(bounds.getArray(1).getString(0)), Double
.parseDouble(bounds.getArray(1)
.getString(1)))
);
if (groundOverlay != null) {
groundOverlay.setPositionFromBounds(this.bounds);
}
}

public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (groundOverlay != null) {
groundOverlay.setZIndex(zIndex);
}
}

// public void setTransparency(float transparency) {
// this.transparency = transparency;
// if (groundOverlay != null) {
// groundOverlay.setTransparency(transparency);
// }
// }

public void setImage(String uri) {
this.iconBitmapDescriptor = getBitmapDescriptorByName(uri);

if (groundOverlay != null) {
groundOverlay.setImage(this.iconBitmapDescriptor);
}
}


public GroundOverlayOptions getGroundOverlayOptions() {
if (groundOverlayOptions == null) {
groundOverlayOptions = createGroundOverlayOptions();
}
return groundOverlayOptions;
}

private GroundOverlayOptions createGroundOverlayOptions() {
GroundOverlayOptions options = new GroundOverlayOptions();
options.image(iconBitmapDescriptor);
options.positionFromBounds(bounds);
// options.transparency(transparency);
options.zIndex(zIndex);
return options;
}

private BitmapDescriptor getBitmapDescriptorByName(String name) {
Bitmap bitmap = ImageUtil.convert(name);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}

@Override
public Object getFeature() {
return groundOverlay;
}

@Override
public void addToMap(GoogleMap map) {
groundOverlay = map.addGroundOverlay(getGroundOverlayOptions());
groundOverlay.setClickable(true);
}

@Override
public void removeFromMap(GoogleMap map) {
groundOverlay.remove();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.airbnb.android.react.maps;

import android.content.Context;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.WindowManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;

import java.util.Map;

import javax.annotation.Nullable;

public class AirMapOverlayManager extends ViewGroupManager<AirMapOverlay> {
private final DisplayMetrics metrics;

public AirMapOverlayManager(ReactApplicationContext reactContext) {
super();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
} else {
metrics = reactContext.getResources().getDisplayMetrics();
}
}

@Override
public String getName() {
return "AIRMapOverlay";
}

@Override
public AirMapOverlay createViewInstance(ThemedReactContext context) {
return new AirMapOverlay(context);
}

@ReactProp(name = "bounds")
public void setBounds(AirMapOverlay view, ReadableArray bounds) {
view.setBounds(bounds);
}

@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(AirMapOverlay view, float zIndex) {
view.setZIndex(zIndex);
}

// @ReactProp(name = "transparency", defaultFloat = 1.0f)
// public void setTransparency(AirMapOverlay view, float transparency) {
// view.setTransparency(transparency);
// }

@ReactProp(name = "image")
public void setImage(AirMapOverlay view, @Nullable String source) {
view.setImage(source);
}


@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@ public void addFeature(View child, int index) {
AirMapLocalTile localTileView = (AirMapLocalTile) child;
localTileView.addToMap(map);
features.add(index, localTileView);
} else if (child instanceof AirMapOverlay) {
AirMapOverlay overlayView = (AirMapOverlay) child;
overlayView.addToMap(map);
features.add(index, overlayView);
} else if (child instanceof ViewGroup) {
ViewGroup children = (ViewGroup) child;
for (int i = 0; i < children.getChildCount(); i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.airbnb.android.react.maps;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;

import java.io.ByteArrayOutputStream;

public class ImageUtil {
public static Bitmap convert(String base64Str) throws IllegalArgumentException {
byte[] decodedBytes = Base64.decode(
base64Str.substring(base64Str.indexOf(",") + 1),
Base64.DEFAULT
);

return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
}

public static String convert(Bitmap bitmap) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);

return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
AirMapLiteManager mapLiteManager = new AirMapLiteManager(reactContext);
AirMapUrlTileManager urlTileManager = new AirMapUrlTileManager(reactContext);
AirMapLocalTileManager localTileManager = new AirMapLocalTileManager(reactContext);
AirMapOverlayManager overlayManager = new AirMapOverlayManager(reactContext);

return Arrays.<ViewManager>asList(
calloutManager,
Expand All @@ -50,6 +51,8 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
mapManager,
mapLiteManager,
urlTileManager,
localTileManager);
localTileManager,
overlayManager
);
}
}
70 changes: 70 additions & 0 deletions lib/components/MapOverlay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { PropTypes, Component } from 'react';
import {
View,
StyleSheet,
Animated,
} from 'react-native';

import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import decorateMapComponent, {
SUPPORTED,
USES_DEFAULT_IMPLEMENTATION,
} from './decorateMapComponent';

const viewConfig = {
uiViewClassName: 'AIR<provider>MapOverlay',
validAttributes: {
image: true,
},
};

const propTypes = {
...View.propTypes,
// A custom image to be used as overlay.
image: PropTypes.any.isRequired,
// Top left and bottom right coordinates for the overlay
bounds: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired,
};

class MapOverlay extends Component {

render() {
let image;
if (this.props.image) {
image = resolveAssetSource(this.props.image) || {};
image = image.uri;
}

const AIRMapOverlay = this.getAirComponent();

return (
<AIRMapOverlay
{...this.props}
image={image}
style={[styles.overlay, this.props.style]}
/>
);
}
}

MapOverlay.propTypes = propTypes;
MapOverlay.viewConfig = viewConfig;

const styles = StyleSheet.create({
overlay: {
position: 'absolute',
backgroundColor: 'transparent',
},
});

MapOverlay.Animated = Animated.createAnimatedComponent(MapOverlay);

module.exports = decorateMapComponent(MapOverlay, {
componentType: 'Overlay',
providers: {
google: {
ios: SUPPORTED,
android: USES_DEFAULT_IMPLEMENTATION,
},
},
});
2 changes: 2 additions & 0 deletions lib/components/MapView.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import MapPolyline from './MapPolyline';
import MapPolygon from './MapPolygon';
import MapCircle from './MapCircle';
import MapCallout from './MapCallout';
import MapOverlay from './MapOverlay';
import MapUrlTile from './MapUrlTile';
import MapLocalTile from './MapLocalTile';
import AnimatedRegion from './AnimatedRegion';
Expand Down Expand Up @@ -749,6 +750,7 @@ MapView.Polygon = MapPolygon;
MapView.Circle = MapCircle;
MapView.UrlTile = MapUrlTile;
MapView.LocalTile = MapLocalTile;
MapView.Overlay = MapOverlay;
MapView.Callout = MapCallout;
Object.assign(MapView, ProviderConstants);
MapView.ProviderPropType = PropTypes.oneOf(Object.values(ProviderConstants));
Expand Down