document.addEventListener('DOMContentLoaded', () => { // Leafletマップ初期化 (東京中心) const map = L.map('map').setView([35.6762, 139.6503], 13); // 中心座標とズームレベル L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); const gridSize = 10; // 10x10仮想グリッド let elevationGrid = []; // 標高データ (ランダム) let start = null; let goal = null; let pathLayer = null; // ルート表示レイヤー let startMarker = null; let goalMarker = null; // 標高データをランダム生成 (0-100) function generateElevation() { elevationGrid = []; for (let y = 0; y < gridSize; y++) { elevationGrid[y] = []; for (let x = 0; x < gridSize; x++) { elevationGrid[y][x] = Math.floor(Math.random() * 100); // ランダム標高 } } } // 緯度経度をグリッドインデックスに変換 function latLngToGrid(latLng) { const bounds = map.getBounds(); const north = bounds.getNorth(); const south = bounds.getSouth(); const east = bounds.getEast(); const west = bounds.getWest(); const x = Math.floor(((latLng.lng - west) / (east - west)) * gridSize); const y = Math.floor(((north - latLng.lat) / (north - south)) * gridSize); return { x: Math.max(0, Math.min(gridSize - 1, x)), y: Math.max(0, Math.min(gridSize - 1, y)) }; } // グリッドインデックスを緯度経度に変換 function gridToLatLng(grid) { const bounds = map.getBounds(); const north = bounds.getNorth(); const south = bounds.getSouth(); const east = bounds.getEast(); const west = bounds.getWest(); const lat = north - (grid.y / gridSize) * (north - south); const lng = west + (grid.x / gridSize) * (east - west); return L.latLng(lat, lng); } // A*パスファインディング (坂を考慮) function findPath(startGrid, goalGrid) { const openSet = []; const cameFrom = {}; const gScore = {}; const fScore = {}; const getKey = (node) => `${node.x},${node.y}`; const heuristic = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y); openSet.push(startGrid); gScore[getKey(startGrid)] = 0; fScore[getKey(startGrid)] = heuristic(startGrid, goalGrid); while (openSet.length > 0) { openSet.sort((a, b) => fScore[getKey(a)] - fScore[getKey(b)]); const current = openSet.shift(); if (current.x === goalGrid.x && current.y === goalGrid.y) { const path = []; let temp = current; while (temp) { path.push(temp); temp = cameFrom[getKey(temp)]; } return path.reverse(); } const neighbors = [ {x: current.x + 1, y: current.y}, {x: current.x - 1, y: current.y}, {x: current.x, y: current.y + 1}, {x: current.x, y: current.y - 1} ]; for (let neighbor of neighbors) { if (neighbor.x < 0 || neighbor.x >= gridSize || neighbor.y < 0 || neighbor.y >= gridSize) continue; const elevDiff = Math.abs(elevationGrid[current.y][current.x] - elevationGrid[neighbor.y][neighbor.x]); const tentativeG = gScore[getKey(current)] + 1 + elevDiff * 0.5; if (!gScore[getKey(neighbor)] || tentativeG < gScore[getKey(neighbor)]) { cameFrom[getKey(neighbor)] = current; gScore[getKey(neighbor)] = tentativeG; fScore[getKey(neighbor)] = tentativeG + heuristic(neighbor, goalGrid); if (!openSet.some(n => n.x === neighbor.x && n.y === neighbor.y)) { openSet.push(neighbor); } } } } return []; } // ルートを描画 function drawPath(path) { if (pathLayer) map.removeLayer(pathLayer); if (path.length === 0) return; const latLngs = path.map(grid => gridToLatLng(grid)); pathLayer = L.polyline(latLngs, { color: 'blue', weight: 3 }).addTo(map); } // クリックイベント (地図上でポイント選択) map.on('click', (event) => { const grid = latLngToGrid(event.latlng); if (!start) { start = grid; if (startMarker) map.removeLayer(startMarker); startMarker = L.marker(event.latlng, { color: 'green' }).addTo(map); } else if (!goal) { goal = grid; if (goalMarker) map.removeLayer(goalMarker); goalMarker = L.marker(event.latlng, { color: 'red' }).addTo(map); const path = findPath(start, goal); drawPath(path); } }); // リセットボタンイベント document.getElementById('resetButton').addEventListener('click', resetMap); // リセット function resetMap() { start = null; goal = null; if (startMarker) map.removeLayer(startMarker); if (goalMarker) map.removeLayer(goalMarker); if (pathLayer) map.removeLayer(pathLayer); generateElevation(); // 新しい標高生成 } // 初期化 generateElevation(); });