diff --git a/map4.js b/map4.js index 6f4fe95..3dd96c7 100644 --- a/map4.js +++ b/map4.js @@ -1,9 +1,11 @@ // =================================== // Supabase 設定 // =================================== -const SUPABASE_URL = "https://ogtlmtnjkpsxsqzqlacj.supabase.co"; +const SUPABASE_URL = + "https://ogtlmtnjkpsxsqzqlacj.supabase.co"; const SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9ndGxtdG5qa3BzeHNxenFsYWNqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjMyOTU3NjUsImV4cCI6MjA3ODg3MTc2NX0.JnCE7oUQwrSgGqiu-QRbwnaLBZrO8JX1_RUb37VIMFI"; + const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); // =================================== @@ -16,9 +18,7 @@ } // =================================== -// 変数 -// =================================== -let allMembers = []; // ← 一度参加したメンバーは全員ここに保存 +let allMembers = []; let currentGroup = ""; let currentUser = ""; let map; @@ -28,19 +28,19 @@ let lastLat = null; let lastLng = null; -let isHost = false; // ← Supabase で判定する本物のホスト判定 +let isHost = false; // 駅関連 let stationMarkers = []; -// 目的地共有関連 +// 目的地関連 let targetMarker = null; let targetLat = null; let targetLng = null; let arrived = false; let lastShownDistance = null; -// デフォルト(皇居) +// 皇居 const DEFAULT_LAT = 35.681236; const DEFAULT_LNG = 139.767125; @@ -66,8 +66,6 @@ }); // =================================== -// URL のグループ / ユーザー読み込み -// =================================== function loadParams() { const p = new URLSearchParams(location.search); currentGroup = p.get("group"); @@ -77,33 +75,21 @@ } // =================================== -// 本物のホスト判定(Supabase groups.host_name) -// =================================== async function decideHost() { - const { data, error } = await supa + const { data } = await supa .from("groups") .select("host_name") .eq("group_name", currentGroup) .maybeSingle(); - if (error) { - console.error("ホスト情報の取得に失敗:", error); - isHost = false; - } else if (data && data.host_name) { - isHost = data.host_name === currentUser; - } else { - isHost = false; - } + isHost = data?.host_name === currentUser; - // 画面表示へ反映 const hostLabel = document.getElementById("hostStatus"); - if (hostLabel) - hostLabel.textContent = isHost ? "あなたはホストです" : "一般メンバーです"; + hostLabel.textContent = + isHost ? "あなたはホストです" : "一般メンバーです"; } // =================================== -// 小型ピン -// =================================== function createLabeledMarker(lat, lng, name, isSelf) { const pinUrl = isSelf ? "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png" @@ -132,69 +118,45 @@ navigator.geolocation.getCurrentPosition( (pos) => res(pos.coords), rej, - { - enableHighAccuracy: true, - } + { enableHighAccuracy: true } ); }); } // =================================== -// マップ初期化(正しい構造版) -// =================================== function initMap(lat, lng) { map = L.map("map").setView([lat, lng], 16); - L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { - maxZoom: 19, - }).addTo(map); + L.tileLayer( + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + { maxZoom: 19 } + ).addTo(map); - // ★目的地設定(ホストのみ) + // ホストだけ目的地設定 map.on("click", async (e) => { - if (!isHost) { - alert("目的地を設定できるのはホストだけです!"); - return; - } + if (!isHost) return; - const newLat = e.latlng.lat; - const newLng = e.latlng.lng; - - // すでに目的地がある場合=再設定確認 - if (targetLat !== null && targetLng !== null) { - const confirmReset = confirm("目的地を再設定しますか?"); - if (!confirmReset) return; - } - - // ▼到着通知の初期化 - arrived = false; - lastShownDistance = null; - - const box = document.getElementById("targetInfo"); - box.style.background = ""; - box.style.borderLeft = ""; - box.textContent = "目的地を設定中…"; - - // ▼目的地更新 - targetLat = newLat; - targetLng = newLng; + targetLat = e.latlng.lat; + targetLng = e.latlng.lng; if (targetMarker) map.removeLayer(targetMarker); - targetMarker = L.marker([targetLat, targetLng], { icon: targetIcon }).addTo(map); - // ▼Supabaseへ保存 + targetMarker = L.marker([targetLat, targetLng], { + icon: targetIcon, + }).addTo(map); + await supa.from("shared_target").upsert({ group_name: currentGroup, lat: targetLat, - lng: targetLng + lng: targetLng, }); - box.textContent = "目的地をグループに共有しました!"; + document.getElementById("targetInfo").textContent = + "目的地を設定しました!"; }); } // =================================== -// 自分の位置保存 -// =================================== async function saveMyLocation(lat, lng) { await supa .from("locations") @@ -217,64 +179,48 @@ selfMarker = createLabeledMarker(lastLat, lastLng, currentUser, true); selfMarker.addTo(map); } + // =================================== -// メンバー一覧(永続保持版・チカチカゼロ) -// =================================== -function showMemberList(latestByDevice) { +function showMemberList(latest) { const list = document.getElementById("memberList"); - // ① 新しい人を allMembers に追加(順番は固定) - Object.values(latestByDevice).forEach(row => { - if (!allMembers.some(m => m.device_id === row.device_id)) { + Object.values(latest).forEach((row) => { + if (!allMembers.some((m) => m.device_id === row.device_id)) { allMembers.push({ device_id: row.device_id, - user_name: row.user_name + user_name: row.user_name, }); } }); - // ② allMembers の順番で表示する(位置が変わらない!) - allMembers.forEach(member => { - let li = list.querySelector(`li[data-id="${member.device_id}"]`); - - // 初登場なら li を作成 + allMembers.forEach((m) => { + let li = list.querySelector(`li[data-id="${m.device_id}"]`); if (!li) { li = document.createElement("li"); - li.dataset.id = member.device_id; - + li.dataset.id = m.device_id; li.innerHTML = `
- - `; + `; list.appendChild(li); } - // オンライン判定(いない場合はオフライン扱い) - const row = latestByDevice[member.device_id]; + li.querySelector(".member-name").textContent = m.user_name; + + const row = latest[m.device_id]; const online = row && (Date.now() - new Date(row.updated_at)) / 1000 < 6; - // テキスト更新 - li.querySelector(".member-name").textContent = member.user_name; - - // 色(自分は緑、他人は黄) - const iconColor = - member.device_id === deviceId ? "#58c16b" : "#FFD700"; - li.querySelector(".member-icon").style.background = iconColor; + li.querySelector(".member-icon").style.background = + m.device_id === deviceId ? "#58c16b" : "#FFD700"; li.querySelector(".member-status").textContent = online ? "🟢" : "🔴"; - - li.classList.toggle("online", online); - li.classList.toggle("offline", !online); }); } // =================================== -// 他ユーザー取得 -// =================================== async function showOtherUsers() { const { data } = await supa .from("locations") @@ -294,89 +240,32 @@ Object.values(latestByDevice).forEach((row) => { if (row.device_id === deviceId) return; - const mk = createLabeledMarker(row.lat, row.lng, row.user_name, false); + const mk = createLabeledMarker( + row.lat, + row.lng, + row.user_name, + false + ); mk.addTo(map); otherMarkers.push(mk); }); } // =================================== -// 距離計算 -// =================================== -function distanceMeters(lat1, lng1, lat2, lng2) { +function distanceMeters(a, b, c, d) { const R = 6371000; - const toRad = (deg) => deg * Math.PI / 180; - - const dLat = toRad(lat2 - lat1); - const dLng = toRad(lng2 - lng1); - - const a = + const toRad = (x) => (x * Math.PI) / 180; + const dLat = toRad(c - a); + const dLng = toRad(d - b); + const y = Math.sin(dLat / 2) ** 2 + - Math.cos(toRad(lat1)) * - Math.cos(toRad(lat2)) * - Math.sin(dLng / 2) ** 2; - - return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + Math.cos(toRad(a)) * + Math.cos(toRad(c)) * + Math.sin(dLng / 2) ** 2; + return R * 2 * Math.atan2(Math.sqrt(y), Math.sqrt(1 - y)); } // =================================== -// 到着判定(OKボタン付き) -// =================================== -function checkArrival() { - if (targetLat === null || targetLng === null) return; - - const d = distanceMeters(lastLat, lastLng, targetLat, targetLng); - const km = (d / 1000).toFixed(2); - const box = document.getElementById("targetInfo"); - - // ▼50m以上変わったときだけ更新 - if (lastShownDistance === null || Math.abs(d - lastShownDistance) > 50) { - box.style.background = ""; - box.style.borderLeft = ""; - box.innerHTML = `目的地まで 約 ${km} km`; - lastShownDistance = d; - } - - // ▼到着判定(初回のみ) - if (d < 120 && !arrived) { - arrived = true; - - box.style.background = "#ffe4e1"; - box.style.borderLeft = "5px solid #ff4d6d"; - - // OK ボタン付き表示 - box.innerHTML = ` - 🎉 目的地に到着しました! 🎉
- - `; - - // ボタンクリックで状態をリセット - setTimeout(() => { - const btn = document.getElementById("okArrivalBtn"); - if (btn) { - btn.onclick = () => { - arrived = false; - lastShownDistance = null; - box.style.background = ""; - box.style.borderLeft = ""; - box.textContent = "目的地情報を待っています…"; - }; - } - }, 100); - } -} - -// =================================== -// 駅取得 -// =================================== async function fetchStationsAround(lat, lng) { const query = ` [out:json]; @@ -402,7 +291,7 @@ stationMarkers = []; if (stations.length === 0) { - box.textContent = "駅が見つかりませんでした。"; + box.textContent = "駅なし"; return; } @@ -410,35 +299,25 @@ let nearestDist = Infinity; stations.forEach((st) => { - const sLat = st.lat; - const sLng = st.lon; - const name = st.tags?.name || "駅名不明"; - - const d = distanceMeters(lastLat, lastLng, sLat, sLng); + const d = distanceMeters(lastLat, lastLng, st.lat, st.lon); if (d < nearestDist) { - nearest = { name, lat: sLat, lng: sLng }; nearestDist = d; + nearest = st; } - - const mk = L.marker([sLat, sLng], { icon: stationIcon }).addTo(map); - mk.bindPopup(name + "駅"); + const mk = L.marker([st.lat, st.lon], { + icon: stationIcon, + }).addTo(map); stationMarkers.push(mk); }); - box.textContent = - "最寄り駅:" + - nearest.name + - "(約 " + - (nearestDist / 1000).toFixed(2) + - " km)"; + box.textContent = `最寄り駅:${nearest.tags.name}(約 ${(nearestDist / + 1000).toFixed(2)} km)`; } catch { - box.textContent = "駅情報を取得できませんでした。"; + box.textContent = "駅情報エラー"; } } // =================================== -// 共有目的地の読込 -// =================================== async function loadSharedTarget() { const { data } = await supa .from("shared_target") @@ -453,23 +332,21 @@ targetLat = data.lat; targetLng = data.lng; arrived = false; - lastShownDistance = null; if (targetMarker) map.removeLayer(targetMarker); + targetMarker = L.marker([targetLat, targetLng], { icon: targetIcon, }).addTo(map); document.getElementById("targetInfo").textContent = - "目的地が共有されました!距離計算中…"; + "目的地が共有されました!"; } // =================================== -// main -// =================================== async function main() { loadParams(); - await decideHost(); // ← 本物のホスト判定! + await decideHost(); let lat = DEFAULT_LAT; let lng = DEFAULT_LNG; @@ -497,15 +374,13 @@ const pos = await getPosition(); lastLat = pos.latitude; lastLng = pos.longitude; - renderSelfMarker(); await saveMyLocation(lastLat, lastLng); - checkArrival(); } catch {} }, 2000); - setInterval(showOtherUsers, 1000); - setInterval(loadSharedTarget, 10000); // ← 上書きしない安全な更新 + setInterval(showOtherUsers, 1200); + setInterval(loadSharedTarget, 10000); document.getElementById("exitBtn").onclick = () => { location.href = "index.html"; @@ -516,18 +391,19 @@ window.addEventListener("DOMContentLoaded", main); -// Supabase クライアント -const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); +// =================================== +// ★ チャット機能(ここから下だけ追加) +// =================================== -// ユーザー名(localStorage) -let userName = localStorage.getItem("userName"); -if (!userName) { - userName = prompt("名前を入力してください"); - localStorage.setItem("userName", userName); +// ユーザー名 +let chatUser = localStorage.getItem("userName"); +if (!chatUser) { + chatUser = prompt("名前を入力してください"); + localStorage.setItem("userName", chatUser); } -// グループ名(すでにあなたのマップに存在する想定) -const groupName = localStorage.getItem("group"); +// グループ +const chatGroup = localStorage.getItem("group"); // DOM const chatList = document.getElementById("chatList"); @@ -540,25 +416,30 @@ if (text === "") return; await supa.from("messages").insert({ - group_name: groupName, - user_name: userName, - message: text + group_name: chatGroup, + user_name: chatUser, + message: text, }); chatInput.value = ""; }); -// メッセージ取得(2秒ごと) +// メッセージ取得 async function loadMessages() { const { data } = await supa .from("messages") .select("*") - .eq("group_name", groupName) + .eq("group_name", chatGroup) .order("created_at", { ascending: true }); chatList.innerHTML = data - .map(m => `
${m.user_name}: ${m.message}
`) + .map( + (m) => + `
${m.user_name}: ${m.message}
` + ) .join(""); + + chatList.scrollTop = chatList.scrollHeight; } setInterval(loadMessages, 2000);