Skip to content

Commit adc6266

Browse files
author
Spike Brehm
authored
Merge pull request react-native-maps#595 from airbnb/TileOverlayRebased
Tile overlay, rebased
2 parents 29ae3e8 + 99027e7 commit adc6266

17 files changed

+494
-13
lines changed

android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public class AirMapManager extends ViewGroupManager<AirMapView> {
3737
"standard", GoogleMap.MAP_TYPE_NORMAL,
3838
"satellite", GoogleMap.MAP_TYPE_SATELLITE,
3939
"hybrid", GoogleMap.MAP_TYPE_HYBRID,
40-
"terrain", GoogleMap.MAP_TYPE_TERRAIN
40+
"terrain", GoogleMap.MAP_TYPE_TERRAIN,
41+
"none", GoogleMap.MAP_TYPE_NONE
4142
);
4243

4344
private ReactContext reactContext;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.airbnb.android.react.maps;
2+
3+
import android.content.Context;
4+
5+
import com.google.android.gms.maps.GoogleMap;
6+
import com.google.android.gms.maps.model.TileOverlay;
7+
import com.google.android.gms.maps.model.TileOverlayOptions;
8+
import com.google.android.gms.maps.model.UrlTileProvider;
9+
10+
import java.net.MalformedURLException;
11+
import java.net.URL;
12+
13+
public class AirMapUrlTile extends AirMapFeature {
14+
15+
class AIRMapUrlTileProvider extends UrlTileProvider
16+
{
17+
private String urlTemplate;
18+
public AIRMapUrlTileProvider(int width, int height, String urlTemplate) {
19+
super(width, height);
20+
this.urlTemplate = urlTemplate;
21+
}
22+
@Override
23+
public synchronized URL getTileUrl(int x, int y, int zoom) {
24+
25+
String s = this.urlTemplate
26+
.replace("{x}", Integer.toString(x))
27+
.replace("{y}", Integer.toString(y))
28+
.replace("{z}", Integer.toString(zoom));
29+
URL url = null;
30+
try {
31+
url = new URL(s);
32+
} catch (MalformedURLException e) {
33+
throw new AssertionError(e);
34+
}
35+
return url;
36+
}
37+
38+
public void setUrlTemplate(String urlTemplate) {
39+
this.urlTemplate = urlTemplate;
40+
}
41+
}
42+
43+
private TileOverlayOptions tileOverlayOptions;
44+
private TileOverlay tileOverlay;
45+
private AIRMapUrlTileProvider tileProvider;
46+
47+
private String urlTemplate;
48+
private float zIndex;
49+
50+
public AirMapUrlTile(Context context) {
51+
super(context);
52+
}
53+
54+
public void setUrlTemplate(String urlTemplate) {
55+
this.urlTemplate = urlTemplate;
56+
if (tileProvider != null) {
57+
tileProvider.setUrlTemplate(urlTemplate);
58+
}
59+
if (tileOverlay != null) {
60+
tileOverlay.clearTileCache();
61+
}
62+
}
63+
64+
public void setZIndex(float zIndex) {
65+
this.zIndex = zIndex;
66+
if (tileOverlay != null) {
67+
tileOverlay.setZIndex(zIndex);
68+
}
69+
}
70+
71+
public TileOverlayOptions getTileOverlayOptions() {
72+
if (tileOverlayOptions == null) {
73+
tileOverlayOptions = createTileOverlayOptions();
74+
}
75+
return tileOverlayOptions;
76+
}
77+
78+
private TileOverlayOptions createTileOverlayOptions() {
79+
TileOverlayOptions options = new TileOverlayOptions();
80+
options.zIndex(zIndex);
81+
this.tileProvider = new AIRMapUrlTileProvider(256, 256, this.urlTemplate);
82+
options.tileProvider(this.tileProvider);
83+
return options;
84+
}
85+
86+
@Override
87+
public Object getFeature() {
88+
return tileOverlay;
89+
}
90+
91+
@Override
92+
public void addToMap(GoogleMap map) {
93+
this.tileOverlay = map.addTileOverlay(getTileOverlayOptions());
94+
}
95+
96+
@Override
97+
public void removeFromMap(GoogleMap map) {
98+
tileOverlay.remove();
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.uimanager.ThemedReactContext;
10+
import com.facebook.react.uimanager.ViewGroupManager;
11+
import com.facebook.react.uimanager.annotations.ReactProp;
12+
13+
public class AirMapUrlTileManager extends ViewGroupManager<AirMapUrlTile> {
14+
private DisplayMetrics metrics;
15+
16+
public AirMapUrlTileManager(ReactApplicationContext reactContext) {
17+
super();
18+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
19+
metrics = new DisplayMetrics();
20+
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
21+
.getDefaultDisplay()
22+
.getRealMetrics(metrics);
23+
} else {
24+
metrics = reactContext.getResources().getDisplayMetrics();
25+
}
26+
}
27+
28+
@Override
29+
public String getName() {
30+
return "AIRMapUrlTile";
31+
}
32+
33+
@Override
34+
public AirMapUrlTile createViewInstance(ThemedReactContext context) {
35+
return new AirMapUrlTile(context);
36+
}
37+
38+
@ReactProp(name = "urlTemplate")
39+
public void setUrlTemplate(AirMapUrlTile view, String urlTemplate) {
40+
view.setUrlTemplate(urlTemplate);
41+
}
42+
43+
@ReactProp(name = "zIndex", defaultFloat = -1.0f)
44+
public void setZIndex(AirMapUrlTile view, float zIndex) {
45+
view.setZIndex(zIndex);
46+
}
47+
48+
}

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

+13
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.android.gms.maps.model.LatLng;
4040
import com.google.android.gms.maps.model.LatLngBounds;
4141
import com.google.android.gms.maps.model.Marker;
42+
import com.google.android.gms.maps.model.TileOverlay;
4243

4344
import java.util.ArrayList;
4445
import java.util.Arrays;
@@ -67,6 +68,8 @@ public class AirMapView extends MapView implements GoogleMap.InfoWindowAdapter,
6768
private static final String[] PERMISSIONS = new String[] {
6869
"android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"};
6970

71+
// TODO: don't need tileMap at all???
72+
private HashMap<TileOverlay, AirMapUrlTile> tileMap = new HashMap<>();
7073
private final List<AirMapFeature> features = new ArrayList<>();
7174
private final Map<Marker, AirMapMarker> markerMap = new HashMap<>();
7275
private final ScaleGestureDetector scaleDetector;
@@ -393,6 +396,13 @@ public void addFeature(View child, int index) {
393396
AirMapCircle circleView = (AirMapCircle) child;
394397
circleView.addToMap(map);
395398
features.add(index, circleView);
399+
} else if (child instanceof AirMapUrlTile) {
400+
AirMapUrlTile urlTileView = (AirMapUrlTile) child;
401+
urlTileView.addToMap(map);
402+
features.add(index, urlTileView);
403+
TileOverlay tile = (TileOverlay)urlTileView.getFeature();
404+
// TODO: don't need tileMap at all???
405+
tileMap.put(tile, urlTileView);
396406
} else {
397407
// TODO(lmr): throw? User shouldn't be adding non-feature children.
398408
}
@@ -410,6 +420,9 @@ public void removeFeatureAt(int index) {
410420
AirMapFeature feature = features.remove(index);
411421
if (feature instanceof AirMapMarker) {
412422
markerMap.remove(feature.getFeature());
423+
} else if (feature instanceof AirMapUrlTile) {
424+
// TODO: don't need tileMap at all???
425+
tileMap.remove(feature.getFeature());
413426
}
414427
feature.removeFromMap(map);
415428
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
3838
AirMapCircleManager circleManager = new AirMapCircleManager(reactContext);
3939
AirMapManager mapManager = new AirMapManager(reactContext);
4040
AirMapLiteManager mapLiteManager = new AirMapLiteManager(reactContext);
41+
AirMapUrlTileManager tileManager = new AirMapUrlTileManager(reactContext);
4142

4243
return Arrays.<ViewManager>asList(
4344
calloutManager,
@@ -46,6 +47,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
4647
polygonManager,
4748
circleManager,
4849
mapManager,
49-
mapLiteManager);
50+
mapLiteManager,
51+
tileManager);
5052
}
5153
}

components/MapUrlTile.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, { PropTypes } from 'react';
2+
import {
3+
View,
4+
requireNativeComponent,
5+
} from 'react-native';
6+
7+
const propTypes = {
8+
...View.propTypes,
9+
10+
/**
11+
* The url template of the tile server. The patterns {x} {y} {z} will be replaced at runtime
12+
* For example, http://c.tile.openstreetmap.org/{z}/{x}/{y}.png
13+
*/
14+
urlTemplate: PropTypes.string.isRequired,
15+
16+
/**
17+
* The order in which this tile overlay is drawn with respect to other overlays. An overlay
18+
* with a larger z-index is drawn over overlays with smaller z-indices. The order of overlays
19+
* with the same z-index is arbitrary. The default zIndex is -1.
20+
*
21+
* @platform android
22+
*/
23+
zIndex: PropTypes.number,
24+
};
25+
26+
class MapUrlTile extends React.Component {
27+
render() {
28+
return (
29+
<AIRMapUrlTile
30+
{...this.props}
31+
/>
32+
);
33+
}
34+
}
35+
36+
MapUrlTile.propTypes = propTypes;
37+
38+
const AIRMapUrlTile = requireNativeComponent('AIRMapUrlTile', MapUrlTile);
39+
40+
module.exports = MapUrlTile;

components/MapView.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ import MapPolyline from './MapPolyline';
1414
import MapPolygon from './MapPolygon';
1515
import MapCircle from './MapCircle';
1616
import MapCallout from './MapCallout';
17+
import MapUrlTile from './MapUrlTile';
18+
19+
const MAP_TYPES = {
20+
STANDARD: 'standard',
21+
SATELLITE: 'satellite',
22+
HYBRID: 'hybrid',
23+
TERRAIN: 'terrain',
24+
NONE: 'none',
25+
};
26+
27+
const ANDROID_ONLY_MAP_TYPES = [
28+
MAP_TYPES.TERRAIN,
29+
MAP_TYPES.NONE,
30+
];
1731

1832
const viewConfig = {
1933
uiViewClassName: 'AIRMap',
@@ -171,12 +185,7 @@ const propTypes = {
171185
* - hybrid: satellite view with roads and points of interest overlayed
172186
* - terrain: (Android only) topographic view
173187
*/
174-
mapType: PropTypes.oneOf([
175-
'standard',
176-
'satellite',
177-
'hybrid',
178-
'terrain',
179-
]),
188+
mapType: PropTypes.oneOf(Object.values(MAP_TYPES)),
180189

181190
/**
182191
* The region to be displayed by the map.
@@ -450,8 +459,8 @@ class MapView extends React.Component {
450459
onMapReady: this._onMapReady,
451460
onLayout: this._onLayout,
452461
};
453-
if (Platform.OS === 'ios' && props.mapType === 'terrain') {
454-
props.mapType = 'standard';
462+
if (Platform.OS === 'ios' && ANDROID_ONLY_MAP_TYPES.includes(props.mapType)) {
463+
props.mapType = MAP_TYPES.STANDARD;
455464
}
456465
props.handlePanDrag = !!props.onPanDrag;
457466
} else {
@@ -486,6 +495,8 @@ class MapView extends React.Component {
486495
MapView.propTypes = propTypes;
487496
MapView.viewConfig = viewConfig;
488497

498+
MapView.MAP_TYPES = MAP_TYPES;
499+
489500
const AIRMap = requireNativeComponent('AIRMap', MapView, {
490501
nativeOnly: {
491502
onChange: true,
@@ -506,6 +517,7 @@ MapView.Marker = MapMarker;
506517
MapView.Polyline = MapPolyline;
507518
MapView.Polygon = MapPolygon;
508519
MapView.Circle = MapCircle;
520+
MapView.UrlTile = MapUrlTile;
509521
MapView.Callout = MapCallout;
510522

511523
MapView.Animated = Animated.createAnimatedComponent(MapView);

example/App.js

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import LoadingMap from './examples/LoadingMap';
2424
import TakeSnapshot from './examples/TakeSnapshot';
2525
import FitToSuppliedMarkers from './examples/FitToSuppliedMarkers';
2626
import LiteMapView from './examples/LiteMapView';
27+
import CustomTiles from './examples/CustomTiles';
2728

2829
class App extends React.Component {
2930
constructor(props) {
@@ -95,6 +96,7 @@ class App extends React.Component {
9596
[LoadingMap, 'Map with loading'],
9697
[FitToSuppliedMarkers, 'Focus Map On Markers'],
9798
[LiteMapView, 'Android Lite MapView'],
99+
[CustomTiles, 'Custom Tiles'],
98100
]);
99101
}
100102
}

0 commit comments

Comments
 (0)