mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-08-07 01:13:01 +02:00
Handle offline tile rendering failures (#306)
This commit is contained in:
@@ -816,11 +816,39 @@ export function initializeApp(config) {
|
||||
function createOfflineTileLayer() {
|
||||
if (!hasLeaflet) return null;
|
||||
const offlineLayer = L.gridLayer({ className: 'map-tiles map-tiles-offline' });
|
||||
/** @type {HTMLElement|null} */
|
||||
let cachedOfflineFallbackTile = null;
|
||||
|
||||
/**
|
||||
* Provide a minimal placeholder tile when canvas rendering is not available.
|
||||
*
|
||||
* @param {number} size Pixel width and height of the tile.
|
||||
* @returns {HTMLElement} Cloned fallback element ready for Leaflet consumption.
|
||||
*/
|
||||
function getOfflineFallbackTile(size) {
|
||||
if (!cachedOfflineFallbackTile) {
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = 'offline-tile-fallback';
|
||||
placeholder.style.width = `${size}px`;
|
||||
placeholder.style.height = `${size}px`;
|
||||
placeholder.style.backgroundColor = 'rgba(33, 66, 110, 0.92)';
|
||||
placeholder.style.display = 'flex';
|
||||
placeholder.style.alignItems = 'center';
|
||||
placeholder.style.justifyContent = 'center';
|
||||
placeholder.style.color = 'rgba(255, 255, 255, 0.6)';
|
||||
placeholder.style.font = 'bold 14px system-ui, sans-serif';
|
||||
placeholder.style.textTransform = 'uppercase';
|
||||
placeholder.textContent = 'Offline tile';
|
||||
cachedOfflineFallbackTile = placeholder;
|
||||
}
|
||||
return /** @type {HTMLElement} */ (cachedOfflineFallbackTile.cloneNode(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a placeholder tile for offline map usage.
|
||||
*
|
||||
* @param {{x: number, y: number, z: number}} coords Tile coordinates supplied by Leaflet.
|
||||
* @returns {HTMLCanvasElement} Canvas element containing placeholder artwork.
|
||||
* @returns {HTMLElement} Tile node containing placeholder artwork.
|
||||
*/
|
||||
offlineLayer.createTile = coords => {
|
||||
const size = 256;
|
||||
@@ -828,51 +856,60 @@ export function initializeApp(config) {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const gradient = ctx.createLinearGradient(0, 0, size, size);
|
||||
gradient.addColorStop(0, 'rgba(33, 66, 110, 0.92)');
|
||||
gradient.addColorStop(1, 'rgba(64, 98, 144, 0.92)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
|
||||
ctx.lineWidth = 1;
|
||||
const steps = 4;
|
||||
for (let i = 1; i < steps; i++) {
|
||||
const pos = (size / steps) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos, 0);
|
||||
ctx.lineTo(pos, size);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, pos);
|
||||
ctx.lineTo(size, pos);
|
||||
ctx.stroke();
|
||||
if (!ctx) {
|
||||
console.warn('Canvas 2D context unavailable for offline tile rendering. Using fallback placeholder.');
|
||||
return getOfflineFallbackTile(size);
|
||||
}
|
||||
try {
|
||||
const gradient = ctx.createLinearGradient(0, 0, size, size);
|
||||
gradient.addColorStop(0, 'rgba(33, 66, 110, 0.92)');
|
||||
gradient.addColorStop(1, 'rgba(64, 98, 144, 0.92)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
const west = tileToLon(coords.x, coords.z);
|
||||
const east = tileToLon(coords.x + 1, coords.z);
|
||||
const north = tileToLat(coords.y, coords.z);
|
||||
const south = tileToLat(coords.y + 1, coords.z);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
|
||||
ctx.lineWidth = 1;
|
||||
const steps = 4;
|
||||
for (let i = 1; i < steps; i++) {
|
||||
const pos = (size / steps) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos, 0);
|
||||
ctx.lineTo(pos, size);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, pos);
|
||||
ctx.lineTo(size, pos);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
ctx.font = '12px system-ui, sans-serif';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(`${west.toFixed(1)}°`, 8, 8);
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(`${east.toFixed(1)}°`, 8, size - 8);
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(`${north.toFixed(1)}°`, size - 8, 8);
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(`${south.toFixed(1)}°`, size - 8, size - 8);
|
||||
const west = tileToLon(coords.x, coords.z);
|
||||
const east = tileToLon(coords.x + 1, coords.z);
|
||||
const north = tileToLat(coords.y, coords.z);
|
||||
const south = tileToLat(coords.y + 1, coords.z);
|
||||
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.35)';
|
||||
ctx.font = 'bold 22px system-ui, sans-serif';
|
||||
ctx.fillText('PotatoMesh offline basemap', size / 2, size / 2);
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
ctx.font = '12px system-ui, sans-serif';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(`${west.toFixed(1)}°`, 8, 8);
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(`${east.toFixed(1)}°`, 8, size - 8);
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(`${north.toFixed(1)}°`, size - 8, 8);
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(`${south.toFixed(1)}°`, size - 8, size - 8);
|
||||
|
||||
return canvas;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.35)';
|
||||
ctx.font = 'bold 22px system-ui, sans-serif';
|
||||
ctx.fillText('PotatoMesh offline basemap', size / 2, size / 2);
|
||||
|
||||
return canvas;
|
||||
} catch (error) {
|
||||
console.error('Failed to render offline tile. Falling back to placeholder element.', error);
|
||||
return getOfflineFallbackTile(size);
|
||||
}
|
||||
};
|
||||
return offlineLayer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user