rnmapbox / maps

A Mapbox react native module for creating custom maps
MIT License
2.26k stars 846 forks source link

[Bug]: Local Filesystem raster tiles #3323

Open Cdingram opened 9 months ago

Cdingram commented 9 months ago

Mapbox Implementation

Mapbox

Mapbox Version

default

Platform

Android

@rnmapbox/maps version

10.1.6

Standalone component to reproduce

import React, { useEffect} from 'react';
import Mapbox from '@rnmapbox/maps';
import { StyleSheet, View, Alert } from 'react-native';
import * as FileSystem from 'expo-file-system';

const styles = StyleSheet.create({
    map: {
        width: '100%',
        height: '100%',
    }
});

const CreateOfflineMaps = () => {
    const TILE_FOLDER = `${FileSystem.documentDirectory}tiles`;
    const MAP_URL = 'http://c.tile.openstreetmap.org';

    useEffect(() => {
        const test = async () => {
            // check if a test png exists, if not then download them
            const fileInfo = await FileSystem.getInfoAsync(`${TILE_FOLDER}/1/0/0.png`)
            if (!fileInfo.exists) {
                downloadTiles();
            }
        }
        test();
    }, []);

    const downloadTiles = async () => {
        // just a few top level tiles to test
        const tiles = [{ x: 0, y: 0, z: 1 }, { x: 1, y: 1, z: 1 }, { x: 0, y: 0, z: 0 }, { x: 1, y: 0, z: 1 }, { x: 0, y: 1, z: 1 }];

        // Create directory for tiles
        for (const tile of tiles) {
            const folder = `${TILE_FOLDER}/${tile.z}/${tile.x}`
            await FileSystem.makeDirectoryAsync(folder, { intermediates: true })
        }

        // Download tiles in batches to avoid excessive promises in flight
        const BATCH_SIZE = 100

        let batch = []

        for (const tile of tiles) {
            const fetchUrl = `${MAP_URL}/${tile.z}/${tile.x}/${tile.y}.png`
            const localLocation = `${TILE_FOLDER}/${tile.z}/${tile.x}/${tile.y}.png`
            const tilePromise = FileSystem.downloadAsync(fetchUrl, localLocation)
            batch.push(tilePromise)

            if (batch.length >= BATCH_SIZE) {
                await Promise.all(batch)
                batch = []
            }
        }

        await Promise.all(batch)

        alert('Finished downloading tiles.')
    }

    // tile url here can be replace with "http://c.tile.openstreetmap.org/{z}/{x}/{y}.png" and it works fine
    const styleJSON = {
        "version": 8,
        "name": "Offline Test",
        "sources": {
            "offline": {
                "type": "raster",
                "tiles": [
                    `${FileSystem.documentDirectory}tiles/{z}/{x}/{y}.png`
                ]        
            }
        },
        "layers": [{
            "id": "test",
            "type": "raster",
            "source": "offline",
        }]
    }

    return (
        <View style={styles.container}>
            <Mapbox.MapView 
                style={styles.map}
                styleJSON={JSON.stringify(styleJSON)}
            >
            </Mapbox.MapView>
        </View>
    );
}

export default CreateOfflineMaps

Observed behavior and steps to reproduce

If you allow the tiles to be downloaded, and reload or leave and re-enter this component, the app will crash on android (development build for expo).

Here are my relevant package versions (I'm using expo):

"@rnmapbox/maps": "^10.1.6",
"expo": "^49.0.21",
"expo-file-system": "~15.4.5",
"react": "18.2.0",
"react-native": "0.72.6",

Expected behavior

Should just show the map tiles

Notes / preliminary analysis

I don't get any errors from my terminal or anything really, it simply crashes. The code works fine if you replace the tile url with an online one like "http://c.tile.openstreetmap.org/{z}/{x}/{y}.png" and it also works as expected with the downloaded tiles on iOS.

Additional links and references

I am trying to do something similar to what is done here and downloading tiles based on this tutorial (this part works great).

Cdingram commented 9 months ago

I'm sure it could be reproduced without expo if you are able to just place some downloaded tiles in any file:// location and reference it in the tiles array like above.