Newer
Older
2025-shino / fog.js
(() => {
  'use strict';

  document.addEventListener("DOMContentLoaded", () => {

    // ===== 設定 =====
    const START_POS = [38.891, 139.824];
    const FOG_RADIUS = 40;   // 見える半径(m)
    const GRID_SIZE = 20;    // 解除単位(m)

    // ===== DOM =====
    const titleEl = document.getElementById("title");
    const startBtn = document.getElementById("start");
    const stopBtn = document.getElementById("stop");

    // ===== 状態 =====
    let map;
    let playerMarker;
    let watchId = null;

    let fogLayer = null;
    const revealedHoles = [];

    // 解除済みグリッド
    const revealedGrid = new Set();

    // ===== 初期化 =====
    function initMap() {
      map = L.map("locationmap", { tap: true })
        .setView(START_POS, 16);

      L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      }).addTo(map);

      playerMarker = L.marker(map.getCenter()).addTo(map);
      playerMarker.bindPopup("STARTを押して探索開始").openPopup();

      createFogLayer();

      // PC検証用
      map.on("click", (e) => {
        updateLocation(e.latlng);
      });
    }

    // ===== 世界を覆う霧 =====
    function createFogLayer() {
      const world = [
        [-90, -180],
        [-90,  180],
        [ 90,  180],
        [ 90, -180]
      ];

      if (fogLayer) map.removeLayer(fogLayer);

      fogLayer = L.polygon(
        [world, ...revealedHoles],
        {
          fillColor: "#000",
          fillOpacity: 0.85,
          weight: 0
        }
      ).addTo(map);
    }

    // ===== グリッドID計算 =====
    function gridId(latlng) {
      const latSize = GRID_SIZE / 111320;
      const lngSize = GRID_SIZE / (111320 * Math.cos(latlng.lat * Math.PI / 180));

      const gx = Math.floor(latlng.lng / lngSize);
      const gy = Math.floor(latlng.lat / latSize);

      return `${gx}_${gy}`;
    }

    // ===== 霧解除 =====
    function revealFog(latlng) {
      const id = gridId(latlng);

      // すでに解除済み → 何もしない
      if (revealedGrid.has(id)) return;

      revealedGrid.add(id);

      const points = [];
      const steps = 16;

      for (let i = 0; i < steps; i++) {
        const angle = (i / steps) * Math.PI * 2;
        const dx = (FOG_RADIUS / 111320) * Math.cos(angle);
        const dy = (FOG_RADIUS / 111320) * Math.sin(angle);

        points.push([
          latlng.lat + dy,
          latlng.lng + dx
        ]);
      }

      revealedHoles.push(points);
      createFogLayer();
    }

    // ===== 位置更新 =====
    function updateLocation(latlng) {
      map.panTo(latlng);
      playerMarker.setLatLng(latlng);

      revealFog(latlng);

      playerMarker
        .setPopupContent("探索中…")
        .openPopup();
    }

    // ===== Geolocation =====
    function onSuccess(pos) {
      updateLocation(
        L.latLng(pos.coords.latitude, pos.coords.longitude)
      );
    }

    function onError() {
      playerMarker
        .setPopupContent("位置情報を取得できません")
        .openPopup();
    }

    function startWatch() {
      stopWatch();
      watchId = navigator.geolocation.watchPosition(
        onSuccess,
        onError,
        { maximumAge: 0, timeout: 3000, enableHighAccuracy: true }
      );
      titleEl.textContent = "探索中";
    }

    function stopWatch() {
      if (watchId !== null) {
        navigator.geolocation.clearWatch(watchId);
        watchId = null;
        titleEl.textContent = "STOP";
      }
    }

    startBtn.addEventListener("click", startWatch);
    stopBtn.addEventListener("click", stopWatch);

    initMap();
  });
})();