Skip to content
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
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,39 @@ For Android: LocalTile is still just overlay over original map tiles. It means t

See [OSM Wiki](https://wiki.openstreetmap.org/wiki/Category:Tile_downloading) for how to download tiles for offline usage.


#### Tile Overlay using MbTile

Tiles can be stored locally in a MBTiles database on the device using xyz tiling scheme. The locally stored tiles can be displayed as a tile overlay. This can be used for displaying maps offline. Manging many tiles in a database is especially useful if larger areas are covered by an offline map. Keeping all the files locally and "raw" on the device most likely results in bad performance as well as troublesome datahandling. Please make sure that your database follows the MBTiles [specifications](https://github.com/mapbox/mbtiles-spec). This only works with tiles stored in the [xyz scheme](https://gist.github.com/tmcw/4954720) as used by Google, OSM, MapBox, ... Make sure to include the ending .mbtiles when you pass your pathTemplate.

```jsx
import MapView from 'react-native-maps';

<MapView
region={this.state.region}
onRegionChange={this.onRegionChange}
>
<MapView.MbTile
/**
* The path template of the locally stored MBTiles database.
/storage/emulated/0/path/to/mBTilesDatabase.mbtiles
*/
pathTemplate={this.state.pathTemplate}
/**
* The size of provided local tiles (usually 256 or 512).
*/
tileSize={256}
/>
</MapView>
```

For Android: LocalTile is still just overlay over original map tiles. It means that if device is online, underlying tiles will be still downloaded. If original tiles download/display is not desirable set mapType to 'none'. For example:
```
<MapView
mapType={Platform.OS == "android" ? "none" : "standard"}
>
```

### Customizing the map style

Create the json object, or download a generated one from the [google style generator](https://mapstyle.withgoogle.com/).
Expand Down
91 changes: 91 additions & 0 deletions example/examples/MbTileOverlay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { Component } from 'react';
import {
StyleSheet,
View,
Dimensions,
TouchableOpacity,
Text,
Platform,
} from 'react-native';
import MapView from 'react-native-maps';

const { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE = 23.736906;
const LONGITUDE = 90.397768;
const LATITUDE_DELTA = 0.022;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;


type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);

this.state = {
offlineMap: false,
};
}

_toggleOfflineMap = () => {
this.setState({
offlineMap: !this.state.offlineMap,
});
}

render() {
return (
<View
style={styles.container}
>
<MapView
style={styles.map}
initialRegion={{
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
loadingEnabled
loadingIndicatorColor="#666666"
loadingBackgroundColor="#eeeeee"
mapType={Platform.OS === 'android' && this.state.offlineMap ? 'none' : 'standard'}
>
{this.state.offlineMap ?
<MapView.MbTile
pathTemplate={'Path/to/mBTilesDatabase.mbtiles'}
tileSize={256}
/> : null}
</MapView>
<TouchableOpacity
style={styles.button}
onPress={() => this._toggleOfflineMap()}
>
<Text> {this.state.offlineMap ? 'Switch to Online Map' : 'Switch to Offline Map'} </Text>
</TouchableOpacity>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
},
button: {
position: 'absolute',
bottom: 20,
backgroundColor: 'lightblue',
zIndex: 999999,
height: 50,
width: width / 2,
borderRadius: width / 2,
justifyContent: 'center',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
});
12 changes: 11 additions & 1 deletion index.d.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ declare module "react-native-maps" {
}

export default class MapView extends React.Component<MapViewProps, any> {
animateToNavigation(location: LatLng, bearing: number, angle: number, duration?: number): void;
animateToRegion(region: Region, duration?: number): void;
animateToCoordinate(latLng: LatLng, duration?: number): void;
animateToBearing(bearing: number, duration?: number): void;
Expand Down Expand Up @@ -365,7 +366,7 @@ declare module "react-native-maps" {
}

// =======================================================================
// UrlTile & LocalTile
// UrlTile, LocalTile & MbTile
// =======================================================================

export interface MapUrlTileProps extends ViewProperties {
Expand All @@ -386,6 +387,15 @@ declare module "react-native-maps" {
export class LocalTile extends React.Component<MapLocalTileProps, any> {
}

export interface MapMbTileProps extends ViewProperties {
pathTemplate: string;
tileSize: number;
zIndex?: number;
}

export class MbTile extends React.Component<MapMbTileProps, any> {
}

// =======================================================================
// Overlay
// =======================================================================
Expand Down
16 changes: 5 additions & 11 deletions index.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import MapView, { Animated, MAP_TYPES, ProviderPropType } from './lib/components/MapView';
import Marker from './lib/components/MapMarker.js';
import Overlay from './lib/components/MapOverlay.js';
import MapView from './lib/components/MapView';

export { default as Marker } from './lib/components/MapMarker.js';
export { default as Polyline } from './lib/components/MapPolyline.js';
export { default as Polygon } from './lib/components/MapPolygon.js';
export { default as Circle } from './lib/components/MapCircle.js';
export { default as UrlTile } from './lib/components/MapUrlTile.js';
export { default as LocalTile } from './lib/components/MapLocalTile.js';
export { default as MbTile } from './lib/components/MapMbTile.js';
export { default as Overlay } from './lib/components/MapOverlay.js';
export { default as Callout } from './lib/components/MapCallout.js';
export { default as AnimatedRegion } from './lib/components/AnimatedRegion.js';

export { Marker, Overlay };
export { Animated, MAP_TYPES, ProviderPropType };

export { Animated, ProviderPropType, MAP_TYPES } from './lib/components/MapView.js';
export const PROVIDER_GOOGLE = MapView.PROVIDER_GOOGLE;
export const PROVIDER_DEFAULT = MapView.PROVIDER_DEFAULT;

export const MarkerAnimated = Marker.Animated;
export const OverlayAnimated = Overlay.Animated;

export default MapView;

Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.airbnb.android.react.maps;

import android.content.Context;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;

import java.io.File;

import android.os.Environment;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDatabaseLockedException;
import android.database.Cursor;

/**
* Created by Christoph Lambio on 30/03/2018.
* Based on AirMapLocalTileManager.java
* Copyright (c) zavadpe
*/

public class AirMapMbTile extends AirMapFeature {

class AIRMapMbTileProvider implements TileProvider {
private static final int BUFFER_SIZE = 16 * 1024;
private int tileSize;
private String pathTemplate;


public AIRMapMbTileProvider(int tileSizet, String pathTemplate) {
this.tileSize = tileSizet;
this.pathTemplate = pathTemplate;
}

@Override
public Tile getTile(int x, int y, int zoom) {
byte[] image = readTileImage(x, y, zoom);
return image == null ? TileProvider.NO_TILE : new Tile(this.tileSize, this.tileSize, image);
}

public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
}

public void setTileSize(int tileSize) {
this.tileSize = tileSize;
}

private byte[] readTileImage(int x, int y, int zoom) {
String rawQuery = "SELECT * FROM map INNER JOIN images ON map.tile_id = images.tile_id WHERE map.zoom_level = {z} AND map.tile_column = {x} AND map.tile_row = {y}";
byte[] tile = null;
try {
SQLiteDatabase offlineDataDatabase = SQLiteDatabase.openDatabase(this.pathTemplate, null, SQLiteDatabase.OPEN_READONLY);
String query = rawQuery.replace("{x}", Integer.toString(x))
.replace("{y}", Integer.toString(y))
.replace("{z}", Integer.toString(zoom));
Cursor cursor = offlineDataDatabase.rawQuery(query, null);
if (cursor.moveToFirst()) {
tile = cursor.getBlob(5);
}
cursor.close();
offlineDataDatabase.close();
} catch (SQLiteCantOpenDatabaseException e) {
e.printStackTrace();
throw e;
} catch (SQLiteDatabaseCorruptException e) {
e.printStackTrace();
throw e;
} catch (SQLiteDatabaseLockedException e) {
e.printStackTrace();
throw e;
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
return tile;
}
}
}

private TileOverlayOptions tileOverlayOptions;
private TileOverlay tileOverlay;
private AirMapMbTile.AIRMapMbTileProvider tileProvider;

private String pathTemplate;
private float tileSize;
private float zIndex;

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

public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
if (tileProvider != null) {
tileProvider.setPathTemplate(pathTemplate);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}

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

public void setTileSize(float tileSize) {
this.tileSize = tileSize;
if (tileProvider != null) {
tileProvider.setTileSize((int)tileSize);
}
}

public TileOverlayOptions getTileOverlayOptions() {
if (tileOverlayOptions == null) {
tileOverlayOptions = createTileOverlayOptions();
}
return tileOverlayOptions;
}

private TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
this.tileProvider = new AirMapMbTile.AIRMapMbTileProvider((int)this.tileSize, this.pathTemplate);
options.tileProvider(this.tileProvider);
return options;
}

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

@Override
public void addToMap(GoogleMap map) {
this.tileOverlay = map.addTileOverlay(getTileOverlayOptions());
}

@Override
public void removeFromMap(GoogleMap map) {
tileOverlay.remove();
}
}
Loading