Newer
Older
sasoh / shonai_tiiki_map_tansaku.js
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 = "<p>まだ訪問した店舗はありません。</p>";
          return;
      }
      visitedStores.forEach((store, idx) => {
          const div = document.createElement("div");
          div.className = "bookmark-item";
 
          div.innerHTML = `
              <strong>${store.name}</strong> (訪問日時: ${store.time})<br>
              <em>おすすめ: ${store.osusume || "〇〇"}</em><br>
              <label>メモ:</label><br>
              <textarea class="memo-textarea" data-index="${idx}">${store.memo || ""}</textarea><br>
              <label>画像アップロード:</label><br>
              <input type="file" accept="image/*" class="image-upload" data-index="${idx}"><br>
              <img class="preview-image" src="${store.image || ''}" style="max-width: 90%; max-height: 150px; display: ${store.image ? 'block' : 'none'};">
              <button class="image-delete" data-index="${idx}">画像を削除</button>
          `;
          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_tansaku.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["名前"],
                  latitude: parseFloat(item["緯度"]),
                  longitude: parseFloat(item["経度"]),
                  osusume: item["おすすめ"] || "",
                  osusumeImage: item["おすすめ画像"] || "",
                  iconUrl: item["アイコン"] || '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}<br>
                        庄内総合高校の子のおすすめは${obj.osusume || "〇〇"}<br>
                        ${s.osusumeImage ? `<img src="${s.osusumeImage}" style="max-width:90%; max-height:150px; margin-top:6px;">` : ""}
                      `;
                      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 : "〇〇";
 
          // ===画像付きポップアップに修正した部分===
          let popupHTML = `
              到着!(${store.name})<br>
              訪問日時: ${visitTime}<br>
              庄内総合高校の子のおすすめは${recommendation}<br>
              ${store.osusumeImage ? `<img src="${store.osusumeImage}" style="max-width:90%; max-height:150px; margin-top:6px;">` : ""}
          `;
          locmarker.setPopupContent(popupHTML).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(popupHTML, {autoClose:false});
          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);
          }
      });
  });
});