diff --git a/shonai_tiiki_map.csv b/shonai_tiiki_map.csv index 48c40b1..f955fee 100644 --- a/shonai_tiiki_map.csv +++ b/shonai_tiiki_map.csv @@ -1,6 +1,27 @@ -name,latitude,longitude,description1,description2,description3,img1,img2,icon,osusume,osusume_images -例:余目第3小学校,38.854124,139.914247,営業時間 8:00〜15:00,住所 山形県東田川郡庄内町廿六木三百地6−1,開業(設立)年数 1874年,images/daigaku.jpg,images/2枚目.jpg,images/school.png,味噌ラーメン,images/daigaku.jpg -名前1,緯度1,経度1,営業時間 ,住所,地元の人に親しみられているお店です,images/sample1-1.jpeg,images/sample1-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon1.png,ハンバーグ,images/hanbaagu.jpg +名前,緯度,経度,説明1,説明2,説明3,画像1,画像2,アイコン,おすすめ,おすすめ画像 +例:余目第3小学校,38.854124,139.914247,営業時間 8:00〜15:00,住所 山形県東田川郡庄内町廿六木三百地6−1,開業(設立)年数 1874年,images/daigaku.jpg,images/2枚目.jpg,images/school.png,味噌ラーメン,images/daigaku.jpg +香味屋,38.859297,139.911489,10:30~14:30 ,東田川郡庄内町甘六木三ッ車126-2,油そばと背脂ラーメンが有名です。,images/sample1-1.jpeg,images/sample1-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon1.png,ハンバーグ,images/hanbaagu.jpg 名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +風車市場,38.794504,139.984585,営業時間 ,住所,道の駅です,images/fuusyamura.jpg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +町湯,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +かなめ食堂,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前2,緯度2,経度2,営業時間 ,住所,一つ一つ手作りです,images/sample2-1.jpeg,images/sample2-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon2.png,チキンライス,images/tikinraisu.jpg +にくのもり,38.850979,139.904694,営業時間 9:00〜18::00,住所,山形県東田川郡庄内町余目町106のんびり過ごせるお店です,images/にくのもり.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg +名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg 名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg 名前3,緯度3,経度3,営業時間 ,住所,のんびり過ごせるお店です,images/sample3-1.jpeg,images/sample3-2.jpeg(画像が一枚のみの場合は名前のみ削除する),images/icon3.png,味噌ラーメン,images/misoraamen.jpg diff --git a/shonai_tiiki_map.js b/shonai_tiiki_map.js index 340cd4a..9d35ea1 100644 --- a/shonai_tiiki_map.js +++ b/shonai_tiiki_map.js @@ -1,5 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { - const map = L.map('map').setView([38.855235,139.910744], 16); + const map = L.map('map').setView([38.855235, 139.910744], 16); // OSMタイル L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { @@ -12,50 +12,51 @@ header: true, complete: function(results) { results.data.forEach(row => { - if (row.latitude && row.longitude) { - const lat = parseFloat(row.latitude); - const lng = parseFloat(row.longitude); + if (row.緯度 && row.経度) { + const lat = parseFloat(row.緯度); + const lng = parseFloat(row.経度); - // 説明文(空欄は無視) - const descriptions = [row.description1, row.description2, row.description3] + // 説明1~説明9を動的にまとめてポップアップ用HTML生成 + const descriptions = Array.from({ length: 9 }, (_, i) => row[`説明${i + 1}`]) .filter(desc => desc && desc.trim() !== "") .map(desc => `

${desc}

`) .join(""); - // 画像処理(空欄は完全に無視、クリックでモーダル) + // 画像処理(空欄は無視、クリックでモーダル表示) let images = ""; - if (row.img1 && row.img1.trim() !== "") { - images += ``; + if (row.画像1 && row.画像1.trim() !== "") { + images += ``; } - if (row.img2 && row.img2.trim() !== "") { - images += ``; + if (row.画像2 && row.画像2.trim() !== "") { + images += ``; } // ポップアップ内容 const popupContent = ` `; - // アイコンをCSVから取得、なければデフォルト - const iconUrl = row.icon && row.icon.trim() !== "" ? row.icon : "images/defaultpin.png"; + // アイコン設定。CSVのアイコンが空ならデフォルト + const iconUrl = row.アイコン && row.アイコン.trim() !== "" ? row.アイコン : "images/defaultpin.png"; const customIcon = L.icon({ iconUrl: iconUrl, - iconSize: [32, 32], // アイコンサイズ - iconAnchor: [16, 32], // ピンの先端位置 - popupAnchor: [0, -32] // ポップアップの位置 + iconSize: [32, 32], + iconAnchor: [16, 32], + popupAnchor: [0, -32] }); - // マーカー追加(独自アイコン付き) + // マーカー追加してポップアップをバインド L.marker([lat, lng], { icon: customIcon }).addTo(map).bindPopup(popupContent); } }); } }); + // モーダル表示関数 window.openModal = function(imageSrc) { const modal = document.getElementById('image-modal'); const modalImage = document.getElementById('modal-image'); @@ -63,12 +64,13 @@ modal.style.display = 'block'; }; + // モーダル非表示関数 window.closeModal = function() { const modal = document.getElementById('image-modal'); modal.style.display = 'none'; }; - // モーダル外をクリックして閉じる + // モーダル外クリックで閉じる window.onclick = function(event) { const modal = document.getElementById('image-modal'); if (event.target === modal) { diff --git a/shonai_tiiki_map_tansaku.css b/shonai_tiiki_map_tansaku.css new file mode 100644 index 0000000..1a47720 --- /dev/null +++ b/shonai_tiiki_map_tansaku.css @@ -0,0 +1,119 @@ +/* ページ全体 */ +body { + font-family: "Yu Gothic", "Hiragino Kaku Gothic ProN", sans-serif; + margin: 0; + padding: 0; + background-color: #f9f9f6; + color: #333; + text-align: center; +} + +/* タイトル */ +h1 { + margin: 20px 0 10px; + font-size: 1.8em; + color: #2c3e50; + letter-spacing: 0.05em; +} + +/* ボタン配置 */ +p { + margin: 10px 0; +} + +/* ボタンスタイル */ +button { + background-color: #3498db; + border: none; + color: white; + padding: 8px 16px; + margin: 5px; + border-radius: 6px; + font-size: 1em; + cursor: pointer; + transition: 0.2s; +} +button:hover { + background-color: #2980b9; +} +button:active { + transform: scale(0.95); +} + +/* セレクトボックス */ +select { + padding: 6px 10px; + margin-left: 5px; + border-radius: 4px; + border: 1px solid #aaa; + font-size: 1em; +} + +/* 地図部分 */ +#locationmap { + width: 96vw; + height: 55vw; + max-width: 700px; + min-height: 340px; + margin: 16px auto; + border: 2px solid #ccc; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +/* ポップアップの中身 */ +.leaflet-popup-content h3 { + margin: 0 0 5px; + font-size: 1.2em; + color: #34495e; +} +.leaflet-popup-content p { + margin: 5px 0; + font-size: 0.95em; + line-height: 1.4; +} +.leaflet-popup-content img { + margin: 5px; + border-radius: 6px; + border: 1px solid #ccc; +} + +/* メモや入力欄 */ +textarea.memo-textarea { + width: 100%; + box-sizing: border-box; + border-radius: 4px; + border: 1px solid #ccc; + resize: vertical; + font-size: 14px; + padding: 6px; +} + +/* 画像アップロード入力とプレビュー */ +input.image-upload { + margin-top: 4px; + margin-bottom: 6px; + display: block; + width: 100%; +} + +.preview-image { + width: 100%; + max-width: 400px; + height: auto; + display: block; + margin: 10px auto; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + object-fit: contain; +} + +@media screen and (max-width: 600px) { + #locationmap { + width: 96vw; + height: 65vh; + min-height: 320px; + max-width: 100vw; + margin: 12px auto; + } +} diff --git a/shonai_tiiki_map_tansaku.html b/shonai_tiiki_map_tansaku.html new file mode 100644 index 0000000..4eb8f44 --- /dev/null +++ b/shonai_tiiki_map_tansaku.html @@ -0,0 +1,88 @@ + + + + + +庄内町の地域のお店マップ探索と旅の栞 + + + + + + + + + + + + + +

庄内町の地域のお店マップ探索と旅の栞

+

+ + + +

+ +

+ 範囲選択(青サークル): + +

+ +

+ 到達判定範囲(赤サークル): + +

+ +
+ +

訪問達成イメージ:

+ + +
+

旅の栞:訪問済み店舗一覧とメモ

+
+ +
+
+ + + + + diff --git a/shonai_tiiki_map_tansaku.js b/shonai_tiiki_map_tansaku.js new file mode 100644 index 0000000..af8c306 --- /dev/null +++ b/shonai_tiiki_map_tansaku.js @@ -0,0 +1,377 @@ +document.addEventListener("DOMContentLoaded", () => { + var mymap = L.map("locationmap").setView([38.855235,139.910744], 16); + + L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(mymap); + + var playerIcon = L.icon({ + iconUrl: 'images/yuusya.png', + iconSize: [40, 40], + iconAnchor: [20, 40], + popupAnchor: [0, -40] + }); + + var locmarker = L.marker(mymap.getCenter(), { icon: playerIcon }).addTo(mymap); + locmarker.bindPopup("STARTを押してね").openPopup(); + + let circleRadius = 500; + let goalReachRadius = 10; + + var radiusCircle = L.circle(mymap.getCenter(), { + radius: circleRadius, + color: 'blue', + fillColor: 'blue', + fillOpacity: 0.1 + }).addTo(mymap); + + var watchId = null; + const urlParams = new URLSearchParams(window.location.search); + const cheatMode = urlParams.get("cheat") === "true"; + + let stores = []; + let visitedStores = []; + let storeMarkers = []; + + const canvas = document.getElementById("progressImage"); + const ctx = canvas.getContext("2d"); + const totalImages = 7; + const images = []; + + for (let i = 0; i < totalImages; i++) { + images[i] = new Image(); + images[i].src = `images/irasuto${i + 1}.png`; + } + + function saveProgress() { + localStorage.setItem("visitedStores", JSON.stringify(visitedStores)); + } + + function loadProgress() { + const saved = localStorage.getItem("visitedStores"); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (Array.isArray(parsed)) visitedStores = parsed; + } catch (e) { console.error("保存データの読み込みに失敗:", e); } + } + } + + function updateProgressImage() { + if (!canvas) return; + + let storeTotal = stores.length > 0 ? stores.length : 1; + let progress = visitedStores.length / storeTotal; + + let index = Math.floor(progress * totalImages); + if (index >= totalImages) index = totalImages - 1; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (images[index].complete) { + ctx.drawImage(images[index], 0, 0, canvas.width, canvas.height); + } else { + images[index].onload = () => { + ctx.drawImage(images[index], 0, 0, canvas.width, canvas.height); + }; + } + } + + function renderBookmarkList() { + const container = document.getElementById("bookmarkList"); + container.innerHTML = ""; + if (visitedStores.length === 0) { + container.innerHTML = "

まだ訪問した店舗はありません。

"; + return; + } + visitedStores.forEach((store, idx) => { + const div = document.createElement("div"); + div.className = "bookmark-item"; + + div.innerHTML = ` + ${store.name} (訪問日時: ${store.time})
+ おすすめ: ${store.osusume || "〇〇"}
+
+
+
+
+ + + `; + container.appendChild(div); + }); + + const memos = container.querySelectorAll(".memo-textarea"); + memos.forEach(textarea => { + textarea.addEventListener("input", (e) => { + const index = e.target.dataset.index; + visitedStores[index].memo = e.target.value; + saveProgress(); + }); + }); + + const uploads = container.querySelectorAll(".image-upload"); + uploads.forEach(input => { + input.addEventListener("change", (e) => { + const index = e.target.dataset.index; + const file = e.target.files[0]; + if (!file) return; + + if (file.size > 5 * 1024 * 1024) { + alert("ファイルサイズは5MB以下にしてください。"); + e.target.value = ""; + return; + } + + const reader = new FileReader(); + reader.onload = function(event) { + visitedStores[index].image = event.target.result; + saveProgress(); + + const img = e.target.parentElement.querySelector(".preview-image"); + if (img) { + img.src = event.target.result; + img.style.display = "block"; + } + }; + reader.readAsDataURL(file); + }); + }); + + const deletes = container.querySelectorAll(".image-delete"); + deletes.forEach(button => { + button.addEventListener("click", (e) => { + const index = e.target.dataset.index; + visitedStores[index].image = ""; + saveProgress(); + const img = e.target.parentElement.querySelector(".preview-image"); + if (img) { + img.src = ""; + img.style.display = "none"; + } + const inputFile = e.target.parentElement.querySelector(".image-upload"); + if (inputFile) { + inputFile.value = ""; + } + }); + }); + } + + function loadCSV() { + fetch('shonai_tiiki_map.csv') + .then(res => res.ok ? res.text() : Promise.reject("CSV読み込み失敗")) + .then(csvText => { + const results = Papa.parse(csvText, { header: true, skipEmptyLines: true }); + stores = results.data.map(item => ({ + name: item.name, + latitude: parseFloat(item.latitude), + longitude: parseFloat(item.longitude), + osusume: item.osusume || "", + osusumeImage: item.osusume_images || "", + iconUrl: item.icon || 'store.png', + marker: null, + redCircle: null + })).filter(s => !isNaN(s.latitude) && !isNaN(s.longitude)); + + visitedStores.forEach(obj => { + const s = stores.find(st => st.name === obj.name); + if (s) { + const customIcon = L.icon({ + iconUrl: s.iconUrl, + iconSize: [40, 40], + iconAnchor: [20, 40], + popupAnchor: [0, -40] + }); + let popupContent = ` + 訪問日時: ${obj.time}
+ 庄内総合高校の子のおすすめは${obj.osusume || "〇〇"}
+ ${s.osusumeImage ? `おすすめ画像` : ""} + `; + let storeMarker = L.marker([s.latitude, s.longitude], { icon: customIcon }) + .addTo(mymap) + .bindPopup(popupContent, {autoClose:false}); + storeMarkers.push(storeMarker); + } + }); + + if (cheatMode) { + document.getElementById("title").textContent = "チートモード"; + mymap.on("click", e => updateLocation(e.latlng)); + } + updateProgressImage(); + renderBookmarkList(); + }) + .catch(err => alert("CSVの読み込みに失敗しました: " + err)); + } + + function getDistance(lat1, lng1, lat2, lng2) { + return L.latLng(lat1, lng1).distanceTo(L.latLng(lat2, lng2)); + } + + function getNearestStore(latlng) { + let nearest = null, minDist = Infinity; + for (let s of stores) { + if (visitedStores.some(c => c.name === s.name)) continue; + let d = getDistance(latlng.lat, latlng.lng, s.latitude, s.longitude); + if (d < minDist) { minDist = d; nearest = s; } + } + if (!nearest) return null; + return { store: nearest, distance: minDist }; + } + + function updateLocation(latlng) { + mymap.panTo(latlng); + locmarker.setLatLng(latlng); + radiusCircle.setLatLng(latlng); + + stores.forEach(s => { + const d = getDistance(latlng.lat, latlng.lng, s.latitude, s.longitude); + + if (visitedStores.some(c => c.name === s.name)) { + if (s.marker && mymap.hasLayer(s.marker)) mymap.removeLayer(s.marker); + if (s.redCircle && mymap.hasLayer(s.redCircle)) mymap.removeLayer(s.redCircle); + return; + } + + if (d <= circleRadius) { + if (!s.marker) { + s.marker = L.marker([s.latitude, s.longitude]).addTo(mymap).bindPopup(s.name); + } else if (!mymap.hasLayer(s.marker)) { + s.marker.addTo(mymap); + } + + if (!s.redCircle) { + s.redCircle = L.circle([s.latitude, s.longitude], { + radius: goalReachRadius, + color: 'red', + fillColor: 'red', + fillOpacity: 0.3 + }).addTo(mymap); + } else { + s.redCircle.setRadius(goalReachRadius); + if (!mymap.hasLayer(s.redCircle)) { + s.redCircle.addTo(mymap); + } + } + } else { + if (s.marker && mymap.hasLayer(s.marker)) mymap.removeLayer(s.marker); + if (s.redCircle && mymap.hasLayer(s.redCircle)) mymap.removeLayer(s.redCircle); + } + }); + + let nearestData = getNearestStore(latlng); + if (!nearestData) { + locmarker.setPopupContent("すべての店舗を訪問しました!").openPopup(); + return; + } + + const { store, distance } = nearestData; + + if (distance <= goalReachRadius) { + const now = new Date(); + const options = { + year: 'numeric', month: 'long', day: 'numeric', + weekday: 'long', hour: '2-digit', minute: '2-digit' + }; + const visitTime = now.toLocaleString('ja-JP', options); + const recommendation = store.osusume.trim() !== "" ? store.osusume : "〇〇"; + + locmarker.setPopupContent( + `到着!(${store.name})
訪問日時: ${visitTime}
庄内総合高校の子のおすすめは${recommendation}` + ).openPopup(); + + visitedStores.push({ name: store.name, time: visitTime, osusume: recommendation }); + saveProgress(); + + if (store.marker && mymap.hasLayer(store.marker)) mymap.removeLayer(store.marker); + if (store.redCircle && mymap.hasLayer(store.redCircle)) mymap.removeLayer(store.redCircle); + + const customIcon = L.icon({ + iconUrl: store.iconUrl, + iconSize: [40, 40], + iconAnchor: [20, 40], + popupAnchor: [0, -40] + }); + + let newMarker = L.marker([store.latitude, store.longitude], { icon: customIcon }) + .addTo(mymap) + .bindPopup(`訪問日時: ${visitTime}
庄内総合高校の子のおすすめは${recommendation}`); + storeMarkers.push(newMarker); + + updateProgressImage(); + renderBookmarkList(); + + } else if (distance <= circleRadius) { + locmarker.setPopupContent(`もう少しで${store.name}!`).openPopup(); + } else { + locmarker.setPopupContent(`ここは lat=${latlng.lat.toFixed(5)}, lng=${latlng.lng.toFixed(5)} です.`).openPopup(); + } + } + + function resetProgress() { + if (confirm("進行状況をリセットして最初からやり直しますか?")) { + localStorage.removeItem("visitedStores"); + visitedStores = []; + storeMarkers.forEach(m => mymap.removeLayer(m)); + storeMarkers = []; + alert("進行状況をリセットしました。"); + updateProgressImage(); + renderBookmarkList(); + location.reload(); + } + } + + loadProgress(); + loadCSV(); + + if (!cheatMode) { + document.getElementById("start").addEventListener("click", () => { + watchId = navigator.geolocation.watchPosition( + pos => updateLocation(L.latLng(pos.coords.latitude, pos.coords.longitude)), + err => console.log(err), + { maximumAge: 0, timeout: 3000, enableHighAccuracy: true } + ); + }); + } + + document.getElementById("stop").addEventListener("click", () => { + if (watchId != null) navigator.geolocation.clearWatch(watchId); + watchId = null; + }); + document.getElementById("reset").addEventListener("click", resetProgress); + + const radiusSelect = document.getElementById("radiusSelect"); + const goalSelect = document.getElementById("goalSelect"); + const RADIUS_KEY = "selectedRadius"; + const GOAL_KEY = "selectedGoal"; + + let savedRadius = localStorage.getItem(RADIUS_KEY); + if (savedRadius) { + circleRadius = parseInt(savedRadius, 10); + radiusCircle.setRadius(circleRadius); + radiusSelect.value = savedRadius; + } + + let savedGoal = localStorage.getItem(GOAL_KEY); + if (savedGoal) { + goalReachRadius = parseInt(savedGoal, 10); + goalSelect.value = savedGoal; + } + + radiusSelect.addEventListener("change", (e) => { + circleRadius = parseInt(e.target.value, 10); + radiusCircle.setRadius(circleRadius); + localStorage.setItem(RADIUS_KEY, circleRadius); + }); + + goalSelect.addEventListener("change", (e) => { + goalReachRadius = parseInt(e.target.value, 10); + localStorage.setItem(GOAL_KEY, goalReachRadius); + + stores.forEach(s => { + if (s.redCircle) { + s.redCircle.setRadius(goalReachRadius); + } + }); + }); +});