Skip to main content

Offline Tilesets

Uses offline manager tileset option to include additional tilesets in the offline pack.

To test:

  • First load the example with wifi/netowork off, make sure the map is blank, and also pressing "Center Offline Location" is blank
  • Close the test, enable wifi, open test again
  • Disable wifi/network presss "Center Offline Location" make sure blue line which shows country contours show up
import React from 'react';
import { Button, View, Alert } from 'react-native';
import { LinearProgress } from '@rneui/base';
import {
  MapView,
  Camera,
  ShapeSource,
  LineLayer,
  offlineManager,
  VectorSource,
} from '@rnmapbox/maps';

import { ExampleWithMetadata } from '../common/ExampleMetadata';

const packName = 'map-with-3d-terrain-pack';
const STYLE_URL = 'mapbox://styles/mapbox/satellite-streets-v11';
const DISTANCE = 0.85;
const CENTER: [number, number] = [6.58968, 45.39701];
const bounds: [number, number, number, number] = [
  CENTER[0] - DISTANCE,
  CENTER[1] - DISTANCE,
  CENTER[0] + DISTANCE,
  CENTER[1] + DISTANCE,
];

function Menu({ cameraRef }: { cameraRef: React.RefObject<Camera | null> }) {
  const [progress, setProgress] = React.useState(0);

  function formatError(err: unknown) {
    if (!err) return 'Unknown error';
    if (typeof err === 'string') return err;
    if (err instanceof Error) return err.message;
    try {
      return JSON.stringify(err, null, 2);
    } catch {
      return String(err);
    }
  }

  return (
    <View>
      <Button
        title="Delete"
        onPress={async () => {
          try {
            const pack = await offlineManager.getPack(packName);
            if (pack) {
              await offlineManager.deletePack(packName);
              setProgress(0);
            }
          } catch (error) {
            Alert.alert('Offline Error', formatError(error));
            console.error('Error deleting pack:', error);
          }
        }}
      />
      <Button
        title="Create"
        onPress={() => {
          (async () => {
            try {
              await offlineManager.createPack(
                {
                  name: packName,
                  styleURL: STYLE_URL,
                  tilesets: ['mapbox://mapbox.country-boundaries-v1'],
                  bounds: [
                    [bounds[0], bounds[1]],
                    [bounds[2], bounds[3]],
                  ] as [[number, number], [number, number]],
                  minZoom: 7,
                  maxZoom: 9,
                  metadata: {
                    whatIsThat: 'foo',
                  },
                },
                (region, status) => {
                  setProgress(status?.percentage ?? 0);
                },
                (pack, error) => {
                  setProgress(0);
                  if (error) {
                    Alert.alert('Offline Error', formatError(error));
                  }
                  console.log('=> callback pack:', pack, 'error:', error);
                },
              );
            } catch (error) {
              Alert.alert('Offline Error', formatError(error));
              console.error('#Error creating pack:', error);
            }
          })();
        }}
      />
      <View style={{ marginVertical: 8 }}>
        <LinearProgress
          variant="determinate"
          value={progress / 100}
          color="primary"
        />
      </View>
      <Button
        title="Center to Offline Location"
        onPress={() => {
          cameraRef.current?.setCamera({
            centerCoordinate: CENTER,
            zoomLevel: 7.5,
            heading: 162,
            pitch: 0,
            animationDuration: 1000,
          });
        }}
      />
    </View>
  );
}

export default function OfflineTilesets() {
  const cameraRef = React.useRef<Camera>(null);
  return (
    <>
      <Menu cameraRef={cameraRef} />
      <MapView style={{ flex: 1 }} styleURL={STYLE_URL}>
        <Camera
          ref={cameraRef}
          defaultSettings={{
            centerCoordinate: [0, 0], //CENTER,
            zoomLevel: 12.3,
            heading: 162,
            pitch: 76,
          }}
        />
        {/* Bounds visualization */}
        <ShapeSource
          id="bounds-source"
          shape={
            {
              type: 'Feature',
              geometry: {
                type: 'LineString',
                coordinates: [
                  [bounds[0], bounds[1]],
                  [bounds[0], bounds[3]],
                  [bounds[2], bounds[3]],
                  [bounds[2], bounds[1]],
                  [bounds[0], bounds[1]],
                ],
              },
              properties: {},
            } as const
          }
        >
          <LineLayer
            id="bounds-line"
            style={{
              lineColor: '#FF0000',
              lineWidth: 3,
              lineOpacity: 0.8,
            }}
          />
        </ShapeSource>

        <VectorSource
          id="countryShapeSource"
          url="mapbox://mapbox.country-boundaries-v1"
        >
          <LineLayer
            id="countryFillLayer"
            sourceLayerID="country_boundaries" // Check the source's layer name with Mapbox Studio or the Mapbox API for the
            existing
            style={{
              lineColor: '#0000FF',
              lineWidth: 8,
              lineOpacity: 0.8,
            }}
          />
        </VectorSource>
      </MapView>
    </>
  );
}


}