document.addEventListener('DOMContentLoaded', () => { // 地図オブジェクトを作成し、中心座標とズームレベルを設定 const map = L.map('map').setView([38.9175, 139.8353], 16); const markers = [];// 全てのマーカーを保存する配列(フィルタ用) // 地図タイルを読み込み(OpenStreetMap使用) L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); // 現在地表示 if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(position => { const lat = position.coords.latitude; const lng = position.coords.longitude; L.marker([lat, lng], { icon: L.icon({ iconUrl: 'images/favicon-32x32-now.png', iconSize: [24, 24], iconAnchor: [12, 12] }) }).addTo(map).bindPopup('現在地'); }); } // 曜日を日本語に変換する配列 const dayMap = ['日', '月', '火', '水', '木', '金', '土']; const today = new Date(); const todayStr = dayMap[today.getDay()];// 今日の曜日を文字列で取得 // ───── 関数:本日が定休日かどうかを判定 ───── function isClosedToday(desc1) { if (!desc1) return false; const closed = desc1.replace('定休日:', '').replace(/:/g, '').split(/[、,/・\/\s]/).filter(Boolean);// 曜日や不定休を抽出 for (let entry of closed) { entry = entry.trim(); if (entry.includes('不定休')) return true; if (entry.includes(todayStr) && !entry.match(/第\d/)) return true; // 第○○曜日のパターン const nthMatch = entry.match(/第(\d)(.)曜日/); if (nthMatch) { const nth = parseInt(nthMatch[1], 10); const day = nthMatch[2]; if (day !== todayStr) continue; // 今月のその曜日が第何回か判定 let count = 0; for (let d = 1; d <= 31; d++) { const date = new Date(today.getFullYear(), today.getMonth(), d); if (date.getMonth() !== today.getMonth()) break; if (date.getDay() === dayMap.indexOf(day)) count++; if (date.getDate() === today.getDate() && count === nth) return true; } } } return false; } // ───── 関数:現在時間が営業時間内か判定 ───── function isOpenNow(desc2) { if (!desc2) return false; const now = new Date(); const day = now.getDay(); const time = now.getHours() * 60 + now.getMinutes();// 現在時刻(分単位) // 複数の営業時間パターン(曜日ごと)を考慮 const segments = desc2.split(/\/|\\n|\\r\\n/); for (let segment of segments) { // 曜日を含む営業時間フォーマットにマッチ const match = segment.match(/(月|火|水|木|金|土|日)(?:~(月|火|水|木|金|土|日))?[::]?\s*(\d{1,2})[::](\d{2})\s*〜\s*(\d{1,2})[::](\d{2})/); if (match) { const [_, startDay, endDay, sh, sm, eh, em] = match; const startMin = parseInt(sh) * 60 + parseInt(sm); let endMin = parseInt(eh) * 60 + parseInt(em); const dayIndex = { '日': 0, '月': 1, '火': 2, '水': 3, '木': 4, '金': 5, '土': 6 }; let validDay = false; if (!endDay) { validDay = day === dayIndex[startDay]; } else { const startIdx = dayIndex[startDay]; const endIdx = dayIndex[endDay]; validDay = startIdx <= endIdx ? day >= startIdx && day <= endIdx : day >= startIdx || day <= endIdx; } if (!validDay) continue; if (endMin <= startMin) endMin += 1440;// 翌日まで営業する場合 const currentTime = time < startMin ? time + 1440 : time; if (currentTime >= startMin && currentTime <= endMin) return true; } // シンプルな時間フォーマット(曜日なし) const simple = segment.match(/(\d{1,2})[::](\d{2})\s*〜\s*(\d{1,2})[::](\d{2})/); if (simple) { const startMin = parseInt(simple[1]) * 60 + parseInt(simple[2]); let endMin = parseInt(simple[3]) * 60 + parseInt(simple[4]); if (endMin <= startMin) endMin += 1440; const currentTime = time < startMin ? time + 1440 : time; if (currentTime >= startMin && currentTime <= endMin) return true; } } return false; } // ───── CSVファイルから店舗情報を読み込んでマーカーを配置 ───── fetch('snack.csv') .then(response => response.text()) .then(text => { const rows = text.split('\n').filter(row => row.trim()); const headers = rows[0].split(',').map(h => h.trim()); for (let i = 1; i < rows.length; i++) { const values = rows[i].split(',').map(v => v.replace(/^"|"$/g, '').trim()); const data = {}; headers.forEach((key, index) => data[key] = values[index] || ''); const lat = parseFloat(data.latitude); const lng = parseFloat(data.longitude); if (isNaN(lat) || isNaN(lng)) continue; // 営業中アイコンか、休業中アイコンを選択 const isOpen = !isClosedToday(data.description1) && isOpenNow(data.description2); const iconPath = isOpen ? (data.icon || 'images/snack-icon.png') : 'images/favicon-door-32x32.png'; const customIcon = L.icon({ iconUrl: iconPath, iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -32] }); // 店舗の説明文と画像 const description = [data.description1, data.description2, data.description3, data.mama] .filter(Boolean) .map(d => d.trim()) .join('<br>'); const image1 = data.img1 && data.img1.trim() !== '' ? data.img1 : 'images/snacktitle.png'; const image2 = data.img2 && data.img2.trim() !== '' ? data.img2 : 'images/snacktitle.png'; // ポップアップのHTML内容 const popupContent = ` <div class="popup-content"> <h3>${data.name.replace(/"$/, '')}</h3> <p>${description}</p> <img src="${image1}" width="106" height="73" style="cursor: pointer;" onclick="openModal('${image1}')" /> <img src="${image2}" width="106" height="73" style="cursor: pointer;" onclick="openModal('${image2}')" /> <button onclick="addToFavorites('${data.name}')">行きたいお店リストに追加</button> </div>`; const marker = L.marker([lat, lng], { icon: customIcon }).addTo(map).bindPopup(popupContent); marker.isOpen = isOpen; markers.push(marker);// フィルタ機能のために保存 } }) .catch(error => { console.error('CSV読み込みエラー:', error); }); // ───── 「空いてる店だけ表示」ボタンのUIと機能 ───── const toggleBtn = document.createElement('button'); toggleBtn.textContent = '営業中の店を表示'; toggleBtn.style.position = 'absolute'; toggleBtn.style.top = '10px'; toggleBtn.style.right = '10px'; toggleBtn.style.zIndex = 1000; toggleBtn.style.padding = '10px 10px'; toggleBtn.style.background = '#f3e8ff'; toggleBtn.style.color = '#3e004f'; toggleBtn.style.border = '1px solid #3e004f'; toggleBtn.style.cursor = 'pointer'; document.body.appendChild(toggleBtn); let showingOnlyOpen = false; toggleBtn.addEventListener('click', () => { showingOnlyOpen = !showingOnlyOpen; toggleBtn.textContent = showingOnlyOpen ? '全ての店を表示' : '営業中の店を表示'; markers.forEach(marker => { if (showingOnlyOpen) { if (marker.isOpen) marker.addTo(map); else map.removeLayer(marker); } else { marker.addTo(map); } }); }); }); // ローカルストレージに店舗名を追加(お気に入り機能) window.addToFavorites = function(name) { const favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); favorites.push({ name, addedAt: new Date().toISOString() }); localStorage.setItem('favorites', JSON.stringify(favorites)); alert(`${name}を行きたいお店リストに追加しました!`); }; // 画像モーダル表示用 window.openModal = function(src) { const modal = document.getElementById("modal"); const modalImg = document.getElementById("modal-img"); modal.style.display = "block"; modalImg.src = src; }; // モーダル閉じる window.closeModal = function() { document.getElementById("modal").style.display = "none"; };