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

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

    const START_POS = [38.891, 139.824];
    const REVEAL_RADIUS = 40; // m

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

    let map;
    let playerMarker;
    let watchId = null;

    // === 解除済みポイント(世界座標)===
    const revealedPoints = [];

    // === Canvas霧レイヤ ===
    const FogLayer = L.GridLayer.extend({
      createTile: function (coords) {
        const tile = document.createElement('canvas');
        const size = this.getTileSize();
        tile.width = size.x;
        tile.height = size.y;

        const ctx = tile.getContext('2d');

        // 霧を塗る
        ctx.fillStyle = 'rgba(0,0,0,0.85)';
        ctx.fillRect(0, 0, size.x, size.y);

        // タイル境界の緯度経度
        const bounds = this._tileCoordsToBounds(coords);

        // 解除ポイントを削る
        revealedPoints.forEach(latlng => {
          if (!bounds.contains(latlng)) return;

          const point = map.latLngToContainerPoint(latlng);
          const tilePoint = map.latLngToContainerPoint(bounds.getNorthWest());

          const x = point.x - tilePoint.x;
          const y = point.y - tilePoint.y;

          ctx.globalCompositeOperation = 'destination-out';
          ctx.beginPath();
          ctx.arc(x, y, metersToPixels(REVEAL_RADIUS, latlng.lat), 0, Math.PI * 2);
          ctx.fill();
        });

        return tile;
      }
    });

    let fogLayer;

    function metersToPixels(m, lat) {
      return m / 40075017 * 256 * Math.pow(2, map.getZoom()) / Math.cos(lat * Math.PI / 180);
    }

    function initMap() {
      map = L.map("locationmap").setView(START_POS, 16);

      L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
        attribution: '© OpenStreetMap contributors'
      }).addTo(map);

      fogLayer = new FogLayer();
      fogLayer.addTo(map);

      playerMarker = L.marker(map.getCenter()).addTo(map);
      playerMarker.bindPopup("STARTで探索開始").openPopup();

      map.on("click", e => updateLocation(e.latlng));
    }

    function reveal(latlng) {
      revealedPoints.push(latlng);
      fogLayer.redraw(); // ← ここが重要
    }

    function updateLocation(latlng) {
      map.panTo(latlng);
      playerMarker.setLatLng(latlng);
      reveal(latlng);

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

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

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

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

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

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