diff --git a/map4.js b/map4.js index c84cdf3..179a882 100644 --- a/map4.js +++ b/map4.js @@ -3,8 +3,7 @@ // =================================== const SUPABASE_URL = "https://ogtlmtnjkpsxsqzqlacj.supabase.co"; const SUPABASE_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9ndGxtdG5qa3BzeHNxenFsYWNqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjMyOTU3NjUsImV4cCI6MjA3ODg3MTc2NX0.JnCE7oUQwrSgGqiu-QRbwnaLBZrO8JX1_RUb37VIMFI"; - + "eyJhbGciOiJIUzI1NiIsInR5cCI...(省略)"; const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); // =================================== @@ -24,25 +23,23 @@ let map; let selfMarker = null; let otherMarkers = []; -let latestByDevice = {}; +let latestByDevice = []; let allMembers = []; let lastLat = null; let lastLng = null; let targetMarker = null; -let stationMarkers = []; let targetLat = null; let targetLng = null; -// -------------------------------------- -// ステータス:デフォルトは「移動中」 -// -------------------------------------- -let myStatus = localStorage.getItem("myStatus") || "移動中"; +let stationMarkers = []; // =================================== -// ステータス保存 +// ステータス:デフォルトは「移動中」 // =================================== +let myStatus = localStorage.getItem("myStatus") || "移動中"; + async function updateStatus(newStatus) { myStatus = newStatus; localStorage.setItem("myStatus", newStatus); @@ -56,33 +53,15 @@ showOtherUsers(); } -// =================================== -// ステータスボタン -// =================================== function setupStatusButtons() { const buttons = document.querySelectorAll(".stBtn"); - buttons.forEach((btn) => { - btn.addEventListener("click", () => { - const newStatus = btn.dataset.status; - updateStatus(newStatus); - }); - }); + buttons.forEach((btn) => + btn.addEventListener("click", () => updateStatus(btn.dataset.status)) + ); } // =================================== -// 待ち合わせピン -// =================================== -const meetIcon = L.icon({ - iconUrl: - "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-violet.png", - shadowUrl: - "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png", - iconSize: [25, 41], - iconAnchor: [12, 41], -}); - -// =================================== -// URL パラメータ +// URL パラメータ読み込み // =================================== function loadParams() { const p = new URLSearchParams(location.search); @@ -97,7 +76,7 @@ } // =================================== -// 位置取得 +// 現在位置取得 // =================================== function getPosition() { return new Promise((resolve, reject) => { @@ -119,7 +98,7 @@ maxZoom: 19, }).addTo(map); - // ホストのみ待ち合わせ設定 + // ホストのみ待ち合わせを設定 map.on("click", async (e) => { if (!isHost) return; @@ -149,41 +128,18 @@ } // =================================== -// 自位置保存(ステータス含む) +// ピン作成 // =================================== -async function saveMyLocation(lat, lng) { - await supa - .from("locations") - .delete() - .eq("group_name", currentGroup) - .eq("device_id", deviceId); +const meetIcon = L.icon({ + iconUrl: + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-violet.png", + shadowUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], +}); - await supa.from("locations").insert({ - group_name: currentGroup, - user_name: currentUser, - device_id: deviceId, - lat, - lng, - status: myStatus, - }); -} - -// =================================== -// 自ピン表示 -// =================================== -function renderSelfMarker() { - if (!map || lastLat === null || lastLng === null) return; - - if (selfMarker) map.removeLayer(selfMarker); - - // ★ 修正:status を渡す - selfMarker = createLabeledMarker(lastLat, lastLng, currentUser, true, myStatus); - selfMarker.addTo(map); -} - -// =================================== -// ピン生成(ステータス色付き) -// =================================== +// 自分のピン色 function createLabeledMarker(lat, lng, name, isSelf, status) { const pinUrl = isSelf ? "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png" @@ -200,11 +156,14 @@
${name}
-
- ${status} -
+
${status}
`; @@ -219,14 +178,14 @@ } // =================================== -// 距離計算(Haversine) +// 距離計算 // =================================== function distanceMeters(lat1, lng1, lat2, lng2) { const R = 6371000; const toRad = (deg) => (deg * Math.PI) / 180; const dLat = toRad(lat2 - lat1); - const dLng = toRad(lng2 - lng1); + const dLng = toRad(lat2 - lng1); const a = Math.sin(dLat / 2) ** 2 + @@ -237,7 +196,73 @@ return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } +// =================================== +// 待ち合わせ距離表示 +// =================================== +function showDistanceToTarget() { + if (targetLat === null) return; + const d = distanceMeters(lastLat, lastLng, targetLat, targetLng); + const km = (d / 1000).toFixed(2); + + document.getElementById("targetInfo").innerHTML = + `待ち合わせ場所まで 約 ${km} km`; +} + +// =================================== +// 駅検索(Overpass) +// =================================== +async function fetchStationsAround(lat, lng) { + const query = ` + [out:json]; + node["railway"="station"](around:5000,${lat},${lng}); + out; + `; + + const res = await fetch( + "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) + ); + + return (await res.json()).elements || []; +} + +// =================================== +// 最寄り駅のピン+表示 +// =================================== +async function loadStations() { + const box = document.getElementById("nearestBox"); + + const stations = await fetchStationsAround(lastLat, lastLng); + + // 前のピンを削除 + stationMarkers.forEach((m) => map.removeLayer(m)); + stationMarkers = []; + + if (stations.length === 0) { + box.textContent = "駅が見つかりませんでした。"; + return; + } + + let nearest = null; + let nearestDist = Infinity; + + stations.forEach((st) => { + const name = st.tags?.name || "駅名不明"; + const d = distanceMeters(lastLat, lastLng, st.lat, st.lon); + + if (d < nearestDist) { + nearest = { name, lat: st.lat, lng: st.lon }; + nearestDist = d; + } + + const mk = L.marker([st.lat, st.lon]).addTo(map); + mk.bindPopup(name + "駅"); + stationMarkers.push(mk); + }); + + box.textContent = + `最寄り駅:${nearest.name}(約 ${(nearestDist / 1000).toFixed(2)} km)`; +} // =================================== // メンバー一覧表示(ステータス付き) // =================================== @@ -272,17 +297,19 @@ const status = row?.status || "移動中"; li.innerHTML = ` -
+
${initial}
${crown}${member.user_name}
-
+
${status} @@ -291,9 +318,9 @@ `; li.addEventListener("click", () => { - const latestRow = latestByDevice[member.device_id]; - if (!latestRow) return; - map.setView([latestRow.lat, latestRow.lng], 17, { animate: true }); + const r = latestByDevice[member.device_id]; + if (!r) return; + map.setView([r.lat, r.lng], 17, { animate: true }); }); list.appendChild(li); @@ -310,7 +337,7 @@ } // =================================== -// 他ユーザーピン表示(★修正済) +// 他メンバーの位置表示 // =================================== async function showOtherUsers() { const { data } = await supa @@ -333,14 +360,44 @@ Object.values(latestByDevice).forEach((r) => { if (r.device_id === deviceId) return; - const status = r.status || "移動中"; - - // ★ 修正:status を渡す - const mk = createLabeledMarker(r.lat, r.lng, r.user_name, false, status); - + const mk = createLabeledMarker(r.lat, r.lng, r.user_name, false, r.status); mk.addTo(map); otherMarkers.push(mk); }); + + checkAllArrived(); +} + +// =================================== +// 待ち合わせ到着判定 +// =================================== +async function checkAllArrived() { + if (targetLat === null) return; + + const users = Object.values(latestByDevice); + if (users.length === 0) return; + + const ok = users.every((u) => { + const d = distanceMeters(u.lat, u.lng, targetLat, targetLng); + return d < 120; + }); + + if (!ok) return; + + document.getElementById("targetInfo").innerHTML = ` + 🎉 全員が待ち合わせ場所に到着しました! 🎉
+ ホストは新しい待ち合わせを設定できます。 + `; + + targetLat = null; + targetLng = null; + + if (targetMarker) { + map.removeLayer(targetMarker); + targetMarker = null; + } + + await supa.from("shared_target").delete().eq("group_name", currentGroup); } // =================================== @@ -359,11 +416,12 @@ targetLng = data.lng; if (targetMarker) map.removeLayer(targetMarker); - targetMarker = L.marker([targetLat, targetLng], { icon: meetIcon }).addTo(map); document.getElementById("targetInfo").textContent = "待ち合わせ場所が共有されました!"; + + showDistanceToTarget(); } // =================================== @@ -457,13 +515,11 @@ isHost = currentUser === currentHostName; } - document.getElementById("hostStatus").textContent = isHost - ? "あなたはホストです" - : "一般メンバーです"; + document.getElementById("hostStatus").textContent = + isHost ? "あなたはホストです" : "一般メンバーです"; - document.getElementById("disbandBtn").style.display = isHost - ? "inline-block" - : "none"; + document.getElementById("disbandBtn").style.display = + isHost ? "inline-block" : "none"; } // =================================== @@ -484,7 +540,7 @@ } // =================================== -// グループ状態確認 +// グループ状態監視 // =================================== async function checkGroupActive() { const { data } = await supa @@ -500,7 +556,7 @@ } // =================================== -// メイン +// メイン処理 // =================================== async function main() { loadParams(); @@ -519,21 +575,24 @@ lastLng = lng; initMap(lat, lng); + renderSelfMarker(); await saveMyLocation(lat, lng); await showOtherUsers(); loadSharedTarget(); loadMessages(); + loadStations(); - // 定期更新 setInterval(async () => { try { const pos = await getPosition(); lastLat = pos.latitude; lastLng = pos.longitude; + renderSelfMarker(); await saveMyLocation(lastLat, lastLng); + showDistanceToTarget(); } catch {} }, 2500); @@ -542,6 +601,7 @@ setInterval(loadMessages, 2500); setInterval(checkHostAuto, 4000); setInterval(checkGroupActive, 4000); + setInterval(loadStations, 15000); document.getElementById("exitBtn").onclick = () => (location.href = "index.html"); @@ -558,29 +618,29 @@ // =================================== window.addEventListener("DOMContentLoaded", async () => { const p = new URLSearchParams(location.search); - const groupName = p.get("group"); - const user = p.get("user"); + const g = p.get("group"); + const u = p.get("user"); - const { data: group } = await supa + const { data } = await supa .from("groups") .select("*") - .eq("group_name", groupName) + .eq("group_name", g) .maybeSingle(); - if (!group) { + if (!data) { alert("グループが存在しません"); location.href = "index.html"; return; } - if (group.is_active === false) { + if (data.is_active === false) { alert("このグループは解散されています"); - location.href = `archive.html?group=${groupName}`; + location.href = `archive.html?group=${g}`; return; } - currentHostName = group.host_name; - isHost = currentHostName === user; + currentHostName = data.host_name; + isHost = currentHostName === u; main(); });