// Initialize the map when the page loads let map; let bounds; let tileLayerGroup; let debugLayerGroup; async function initMap() { try { // Fetch map bounds from the API const response = await fetch('/api/bounds'); bounds = await response.json(); console.log('Map bounds:', bounds); // Update sidebar with map info updateMapInfo(bounds); // Calculate map dimensions in tiles const width = bounds.max_x - bounds.min_x + 1; const height = bounds.max_y - bounds.min_y + 1; // Get config const config = window.MapConfig; const tileSize = config.tileSize; // Create map with simple CRS (not geographic) map = L.map('map', { crs: L.CRS.Simple, minZoom: config.minZoom, maxZoom: config.maxZoom, attributionControl: false, }); // Calculate bounds for Leaflet (in pixels) // Origin at top-left [0,0], y increases down, x increases right const pixelWidth = width * tileSize; const pixelHeight = height * tileSize; const mapBounds = [ [0, 0], [pixelHeight, pixelWidth] ]; // Set max bounds to prevent panning outside the map map.setMaxBounds(mapBounds); // Fit the map to bounds map.fitBounds(mapBounds); // Create layer groups tileLayerGroup = L.layerGroup().addTo(map); if (config.debug) { debugLayerGroup = L.layerGroup().addTo(map); } // Load tiles for current zoom loadTilesForCurrentZoom(); // Reload tiles when zoom changes map.on('zoomend', function() { loadTilesForCurrentZoom(); }); // Add coordinate display on mouse move map.on('mousemove', function(e) { const lat = e.latlng.lat; const lng = e.latlng.lng; // Convert pixel coordinates to tile coordinates const tileX = Math.floor(lng / tileSize); const tileY = Math.floor(lat / tileSize); const leafletZoom = map.getZoom(); const zoomConfig = config.getZoomConfig(leafletZoom); // Calculate which merged tile this is in const mergedTileX = Math.floor(tileX / zoomConfig.mergeFactor); const mergedTileY = Math.floor(tileY / zoomConfig.mergeFactor); document.getElementById('coord-text').textContent = `Tile (${tileX}, ${tileY}) | Merged (${mergedTileX}, ${mergedTileY}) | Zoom ${leafletZoom} (DB ${zoomConfig.dbZoom})`; }); // Add attribution L.control.attribution({ position: 'bottomright', prefix: false }).addAttribution('The Black Grimoire: Cursebreaker').addTo(map); // Add sidebar toggle control const SidebarControl = L.Control.extend({ options: { position: 'topleft' }, onAdd: function(map) { const container = L.DomUtil.create('div', 'leaflet-bar leaflet-control'); const button = L.DomUtil.create('a', 'leaflet-control-sidebar', container); button.innerHTML = '☰'; button.href = '#'; button.title = 'Toggle Sidebar'; L.DomEvent.on(button, 'click', function(e) { L.DomEvent.preventDefault(e); const sidebar = document.getElementById('sidebar'); sidebar.classList.toggle('collapsed'); }); return container; } }); map.addControl(new SidebarControl()); console.log('Map initialized successfully'); // Load resources asynchronously loadResources().catch(error => { console.error('Failed to load resources:', error); }); } catch (error) { console.error('Error initializing map:', error); document.getElementById('map-stats').innerHTML = '

Error loading map data

'; } } function loadTilesForCurrentZoom() { // Clear existing tiles tileLayerGroup.clearLayers(); if (debugLayerGroup) { debugLayerGroup.clearLayers(); } const currentZoom = map.getZoom(); const config = window.MapConfig; const tileSize = config.tileSize; // Get zoom configuration const zoomConfig = config.getZoomConfig(currentZoom); const dbZoom = zoomConfig.dbZoom; const mergeFactor = zoomConfig.mergeFactor; console.log(`\n=== Loading tiles at Leaflet zoom ${currentZoom} ===`); console.log(`Database zoom: ${dbZoom}, Merge factor: ${mergeFactor} (${zoomConfig.label})`); console.log(`Bounds: X [${bounds.min_x}, ${bounds.max_x}], Y [${bounds.min_y}, ${bounds.max_y}]`); // Calculate which merged tiles we need to load // The database stores merged tile coordinates starting from 0 // For a 2x2 merge of tiles (0,0), (0,1), (1,0), (1,1), the database stores it at (0,0) // For original tiles at min_x=0, with mergeFactor=2, we need tiles starting at x=0/2=0 const minMergedX = Math.floor(bounds.min_x / mergeFactor); const maxMergedX = Math.floor(bounds.max_x / mergeFactor); const minMergedY = Math.floor(bounds.min_y / mergeFactor); const maxMergedY = Math.floor(bounds.max_y / mergeFactor); console.log(`Merged tile range: X [${minMergedX}, ${maxMergedX}], Y [${minMergedY}, ${maxMergedY}]`); let tileCount = 0; let loadedCount = 0; let errorCount = 0; // Load each merged tile for (let mergedY = minMergedY; mergedY <= maxMergedY; mergedY++) { for (let mergedX = minMergedX; mergedX <= maxMergedX; mergedX++) { // Calculate the pixel bounds for this merged tile // The merged tile at (mergedX, mergedY) covers original tiles starting at: // (mergedX * mergeFactor, mergedY * mergeFactor) const startTileX = mergedX * mergeFactor; const startTileY = mergedY * mergeFactor; const pixelMinX = startTileX * tileSize; const pixelMinY = startTileY * tileSize; const pixelMaxX = (startTileX + mergeFactor) * tileSize; const pixelMaxY = (startTileY + mergeFactor) * tileSize; const tileBounds = [ [pixelMinY, pixelMinX], [pixelMaxY, pixelMaxX] ]; // Request the merged tile from the API const imageUrl = `/api/tiles/${dbZoom}/${mergedX}/${mergedY}`; if (config.debug && tileCount < 5) { console.log(` Tile ${tileCount}: DB(${mergedX},${mergedY}) → Pixels [${pixelMinX},${pixelMinY}] to [${pixelMaxX},${pixelMaxY}]`); console.log(` URL: ${imageUrl}`); } const overlay = L.imageOverlay(imageUrl, tileBounds, { opacity: 1, errorOverlayUrl: '', }); overlay.on('load', function() { loadedCount++; if (config.debug && loadedCount <= 3) { console.log(` ✓ Loaded tile (${mergedX}, ${mergedY})`); } }); overlay.on('error', function() { errorCount++; console.warn(` ✗ Failed to load tile (${mergedX}, ${mergedY}) from ${imageUrl}`); }); overlay.addTo(tileLayerGroup); tileCount++; // Add debug overlay if enabled if (config.debug && debugLayerGroup) { // Draw rectangle showing tile boundaries const rect = L.rectangle(tileBounds, { color: '#ff0000', weight: 1, fillOpacity: 0, interactive: false }).addTo(debugLayerGroup); // Add label showing tile coordinates const center = [ (pixelMinY + pixelMaxY) / 2, (pixelMinX + pixelMaxX) / 2 ]; const label = L.marker(center, { icon: L.divIcon({ className: 'tile-label', html: `
DB: (${mergedX},${mergedY})
Z: ${dbZoom}
`, iconSize: [60, 30], iconAnchor: [30, 15] }), interactive: false }).addTo(debugLayerGroup); } } } console.log(`Requested ${tileCount} tiles (merge factor ${mergeFactor}x${mergeFactor})`); // Wait a bit and report results setTimeout(() => { console.log(`Results: ${loadedCount} loaded, ${errorCount} errors, ${tileCount - loadedCount - errorCount} pending`); }, 2000); } function updateMapInfo(bounds) { const width = bounds.max_x - bounds.min_x + 1; const height = bounds.max_y - bounds.min_y + 1; const config = window.MapConfig; document.getElementById('map-stats').innerHTML = `

Bounds:

X: ${bounds.min_x} to ${bounds.max_x}

Y: ${bounds.min_y} to ${bounds.max_y}

Size: ${width} × ${height} tiles

Zoom levels: ${config.minZoom}-${config.maxZoom}

Debug mode: ${config.debug ? 'ON' : 'OFF'}

${config.debug ? '

Red boxes show tile boundaries

' : ''} `; } // Initialize map when page loads window.addEventListener('DOMContentLoaded', initMap);