I am currently working on an Ionic/Capacitor/React app for Android where the end user needs to display a map while offline.
In order to achieve this, I'm trying to display locally stored vector tiles (in pbf
format) in Open Layers.
I'm able to display the layer just fine, however it seems there is a memory leak somewhere. As I'm trying to move around the map, memory used by the app (as seen in Android Studio Profiler) keeps increasing until it reaches 1.1-1.2GB and the app crashes.
Here is how the Map
object is initialized :
export async function initMap(
mapElement: React.RefObject<HTMLDivElement>,
uri: string,
availableTilesPath: Set<string> | null
) {
const mousePositionControl = new MousePosition({
coordinateFormat: createStringXY(4),
projection: "EPSG:4326",
});
const sourceGWCTMSLocalTiles = new VectorTileSource({
format: new MVT(),
url: `${uri}/${vectorArchiveFileName}/{z}/{x}/{-y}.pbf`,
tileGrid: olTilegrid.createXYZ({ maxZoom: 14, tileSize: 2048 }),
tileLoadFunction: (tile, url) =>
customTileLoad(tile as VectorTile, url, availableTilesPath),
});
const vectorLayer = new VectorTileLayer({
renderMode: "vector",
source: sourceGWCTMSLocalTiles,
});
return new Map({
target: mapElement.current || undefined,
layers: [
vectorLayer,
new WebGLTileLayer({
source: sourceGeoTIFF,
}),
],
view: new View({
projection: "EPSG:3857",
zoom: 16,
minZoom: 6,
maxZoom: 20,
center: fromLonLat([-52.159, 4.005]),
constrainResolution: true,
showFullExtent: true,
}),
controls: defaultControls().extend([mousePositionControl]),
});
}
Here is the custom tile load function which is called in order to load data from the app's storage:
export function customTileLoad(
tile: VectorTile,
url: string,
availableTilesPaths: Set<string> | null
) {
tile.setLoader(function (extent, resolution, projection) {
const sliceAt = url.indexOf(vectorArchiveFileName);
const fileUri = url.substring(sliceAt + vectorArchiveFileName.length);
//prevent unecessary calls to the filesystem
if (availableTilesPaths?.has(vectorArchiveFileName + fileUri)) {
Filesystem.readFile({
directory: Directory.External,
path: vectorArchiveFileName + fileUri,
})
.then((res) => {
const str = "data:application/octet-stream;base64," + res.data;
fetch(str)
.then((res) => res.arrayBuffer())
.then(function (data) {
const format = tile.getFormat(); // ol/format/MVT configured as source format
//no more memory leak if I comment out the following lines
const features = format.readFeatures(data, {
extent: extent,
featureProjection: projection,
});
tile.setFeatures(features as Feature<Geometry>[]);
});
})
.catch(() => {
console.log("error trying to load tile");
});
}
});
}
Lib versions :
OpenLayers : 7.4.0,
Ionic : 7.0.12
Capacitor : 5.0.4
React : 18.2.0
At first I thought I was misusing the Capacitor Filesystem plugin and forgetting to call close on a resource or something of the sort.
However, commenting out everything inside the readFile
callback resulted in no memory increase.
Then I thought it might have to do with a memory leak in the fetch
API, as suggested by this issue. I manually converted my base64 string to an array buffer using a for loop, without using fetch and it did not change anything.
Then I thought it might be linked to Ionic's inner browser caching all the tiles, as suggested by this issue. However I was not able to implement the answer as I'm using an ArrayBuffer and OL's MVT
format's readFeatures
method won't allow anything other than an array buffer.