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);
+ }
+ });
+ });
+});