79499353

Date: 2025-03-10 23:22:48
Score: 0.5
Natty:
Report link

Obviously it is possible to set up offline maps using a node.js web server to provide tiles from an .mbtiles file. However, I managed to set it up without any server. I used @capacitor-community/sqlite to extract tiles and serve them to openLayers. My code is

--- map.page.ts ----

async createMap() {
(...)
case 'offline':
  credits = '© MapTiler © OpenStreetMap contributors'
  await this.server.openMbtiles('offline.mbtiles');
  const olSource = await this.createSource();
  if (!olSource) return;
  olLayer = new VectorTileLayer({ source: olSource, style: vectorTileStyle });
break;
(...)
// Create map
this.map = new Map({
  target: 'map',
  layers: [olLayer, this.currentLayer, this.archivedLayer, this.multiLayer],
  view: new View({ center: currentPosition, zoom: 9 }),
  controls: [new Zoom(), new ScaleLine(), new Rotate(), new CustomControl(this.fs)],
});
(...)

createSource() {
  try {
    // Create vector tile source
    return new VectorTileSource({
      format: new MVT(),
      tileClass: VectorTile,
      tileGrid: new TileGrid({
        extent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
        resolutions: Array.from({ length: 20 }, (_, z) => 156543.03392804097 / Math.pow(2, z)),
        tileSize: [256, 256],
      }),
      // Tile load function
      tileLoadFunction: async (tile) => {
        const vectorTile = tile as VectorTile;
        const [z, x, y] = vectorTile.getTileCoord();
        try {
          // Get vector tile
          const rawData = await this.server.getVectorTile(z, x, y);
          if (!rawData?.byteLength) {
            vectorTile.setLoader(() => {});
            vectorTile.setState(TileState.EMPTY);
            return;
          }
          // Decompress
          const decompressed = pako.inflate(new Uint8Array(rawData));
          // Read features
          const features = new MVT().readFeatures(decompressed, {
            extent: vectorTile.extent ?? [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
            featureProjection: 'EPSG:3857',
          });
          // Set features to vector tile
          vectorTile.setFeatures(features);
        } catch (error) {
          vectorTile.setState(TileState.ERROR);
        }
      },
      tileUrlFunction: ([z, x, y]) => `${z}/${x}/${y}`,
    });
  } catch (e) {
    console.error('Error in createSource:', e);
    return null;
  }
}

---- server.service.ts -----

async getVectorTile(zoom: number, x: number, y: number): Promise<ArrayBuffer | null> {
  console.log(`🔍 Trying to get vector tile z=${zoom}, x=${x}, y=${y}`);
  if (!this.db) {
    console.error('❌ Database connection is not open.');
    return null;
  }
  // Query the database for the tile using XYZ coordinates
  const resultXYZ = await this.db.query(
    `SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?;`,
    [zoom, x, y]  
  );
  if (resultXYZ?.values?.length) {
    console.log(`✅ Tile found: z=${zoom}, x=${x}, y=${y}`);
    const tileData = resultXYZ.values[0].tile_data;
    // Ensure tileData is returned as an ArrayBuffer
    if (tileData instanceof ArrayBuffer) {
      return tileData;
    } else if (Array.isArray(tileData)) {
      return new Uint8Array(tileData).buffer; // Convert array to ArrayBuffer
    } else {
      console.error(`❌ Unexpected tile_data format for ${zoom}/${x}/${y}`, tileData);
      return null;
    }
  } else {
    console.log(`❌ No tile found: z=${zoom}, x=${x}, y=${y}`);
    return null;
  }
}
Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Filler text (0.5): --------
  • Low reputation (1):
Posted by: Enric Terradellas