247 lines
8.8 KiB
JavaScript
247 lines
8.8 KiB
JavaScript
// 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
|
||
}).addHTML('The Black Grimoire: Cursebreaker').addTo(map);
|
||
|
||
console.log('Map initialized successfully');
|
||
|
||
} catch (error) {
|
||
console.error('Error initializing map:', error);
|
||
document.getElementById('map-stats').innerHTML =
|
||
'<p style="color: #ff6b6b;">Error loading map data</p>';
|
||
}
|
||
}
|
||
|
||
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: `<div style="background: rgba(0,0,0,0.7); color: #fff; padding: 2px 5px; border-radius: 3px; font-size: 11px; white-space: nowrap;">
|
||
DB: (${mergedX},${mergedY})<br/>
|
||
Z: ${dbZoom}
|
||
</div>`,
|
||
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 = `
|
||
<p><strong>Bounds:</strong></p>
|
||
<p>X: ${bounds.min_x} to ${bounds.max_x}</p>
|
||
<p>Y: ${bounds.min_y} to ${bounds.max_y}</p>
|
||
<p><strong>Size:</strong> ${width} × ${height} tiles</p>
|
||
<p><strong>Zoom levels:</strong> ${config.minZoom}-${config.maxZoom}</p>
|
||
<p><strong>Debug mode:</strong> ${config.debug ? 'ON' : 'OFF'}</p>
|
||
${config.debug ? '<p style="color: #8b5cf6; font-size: 12px;">Red boxes show tile boundaries</p>' : ''}
|
||
`;
|
||
}
|
||
|
||
// Toggle sidebar
|
||
document.getElementById('toggle-sidebar').addEventListener('click', function() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
sidebar.classList.toggle('collapsed');
|
||
});
|
||
|
||
// Initialize map when page loads
|
||
window.addEventListener('DOMContentLoaded', initMap);
|