diff --git a/map4.js b/map4.js index 179a882..8712c08 100644 --- a/map4.js +++ b/map4.js @@ -3,7 +3,8 @@ // =================================== const SUPABASE_URL = "https://ogtlmtnjkpsxsqzqlacj.supabase.co"; const SUPABASE_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI...(省略)"; + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9ndGxtdG5qa3BzeHNxenFsYWNqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjMyOTU3NjUsImV4cCI6MjA3ODg3MTc2NX0.JnCE7oUQwrSgGqiu-QRbwnaLBZrO8JX1_RUb37VIMFI"; + const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); // =================================== @@ -23,7 +24,7 @@ let map; let selfMarker = null; let otherMarkers = []; -let latestByDevice = []; +let latestByDevice = {}; // ★ オブジェクトとして利用 let allMembers = []; let lastLat = null; @@ -50,7 +51,7 @@ .eq("group_name", currentGroup) .eq("device_id", deviceId); - showOtherUsers(); + showOtherUsers(); // メンバーリスト更新 } function setupStatusButtons() { @@ -73,6 +74,11 @@ localStorage.setItem("group", currentGroup); localStorage.setItem("userName", currentUser); + + const hostLabel = document.getElementById("hostStatus"); + if (hostLabel) { + hostLabel.textContent = isHost ? "あなたはホストです" : "一般メンバーです"; + } } // =================================== @@ -89,6 +95,18 @@ } // =================================== +// 待ち合わせピン(violet) +// =================================== +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], +}); + +// =================================== // 地図初期化 // =================================== function initMap(lat, lng) { @@ -98,7 +116,7 @@ maxZoom: 19, }).addTo(map); - // ホストのみ待ち合わせを設定 + // ホストだけ待ち合わせを設定 map.on("click", async (e) => { if (!isHost) return; @@ -114,7 +132,9 @@ targetLng = newLng; if (targetMarker) map.removeLayer(targetMarker); - targetMarker = L.marker([targetLat, targetLng], { icon: meetIcon }).addTo(map); + targetMarker = L.marker([targetLat, targetLng], { icon: meetIcon }).addTo( + map + ); await supa.from("shared_target").upsert({ group_name: currentGroup, @@ -128,18 +148,46 @@ } // =================================== -// ピン作成 +// 自分の位置保存(ステータス込み) // =================================== -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], -}); +async function saveMyLocation(lat, lng) { + await supa + .from("locations") + .delete() + .eq("group_name", currentGroup) + .eq("device_id", deviceId); -// 自分のピン色 + 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); + + 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" @@ -178,14 +226,14 @@ } // =================================== -// 距離計算 +// 距離計算(修正版) // =================================== function distanceMeters(lat1, lng1, lat2, lng2) { const R = 6371000; const toRad = (deg) => (deg * Math.PI) / 180; const dLat = toRad(lat2 - lat1); - const dLng = toRad(lat2 - lng1); + const dLng = toRad(lng2 - lng1); // ★ 正しく lng2 - lng1 const a = Math.sin(dLat / 2) ** 2 + @@ -220,7 +268,8 @@ `; const res = await fetch( - "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) + "https://overpass-api.de/api/interpreter?data=" + + encodeURIComponent(query) ); return (await res.json()).elements || []; @@ -234,7 +283,6 @@ const stations = await fetchStationsAround(lastLat, lastLng); - // 前のピンを削除 stationMarkers.forEach((m) => map.removeLayer(m)); stationMarkers = []; @@ -263,6 +311,7 @@ box.textContent = `最寄り駅:${nearest.name}(約 ${(nearestDist / 1000).toFixed(2)} km)`; } + // =================================== // メンバー一覧表示(ステータス付き) // =================================== @@ -297,7 +346,7 @@ const status = row?.status || "移動中"; li.innerHTML = ` -
+
${initial}
@@ -335,7 +384,6 @@ "到着": "#9c27b0", }[s] || "#ccc"; } - // =================================== // 他メンバーの位置表示 // =================================== @@ -347,7 +395,7 @@ .order("updated_at", { ascending: false }); latestByDevice = {}; - data.forEach((r) => { + (data || []).forEach((r) => { if (!latestByDevice[r.device_id]) latestByDevice[r.device_id] = r; }); @@ -416,7 +464,9 @@ targetLng = data.lng; if (targetMarker) map.removeLayer(targetMarker); - targetMarker = L.marker([targetLat, targetLng], { icon: meetIcon }).addTo(map); + targetMarker = L.marker([targetLat, targetLng], { icon: meetIcon }).addTo( + map + ); document.getElementById("targetInfo").textContent = "待ち合わせ場所が共有されました!"; @@ -436,7 +486,7 @@ .eq("group_name", currentGroup) .order("created_at", { ascending: true }); - chatList.innerHTML = data + chatList.innerHTML = (data || []) .map((m) => { const isMe = m.user_name === currentUser; const side = isMe ? "chat-right" : "chat-left"; @@ -515,11 +565,15 @@ isHost = currentUser === currentHostName; } - document.getElementById("hostStatus").textContent = - isHost ? "あなたはホストです" : "一般メンバーです"; + const hostLabel = document.getElementById("hostStatus"); + if (hostLabel) { + hostLabel.textContent = isHost ? "あなたはホストです" : "一般メンバーです"; + } - document.getElementById("disbandBtn").style.display = - isHost ? "inline-block" : "none"; + const disbandBtn = document.getElementById("disbandBtn"); + if (disbandBtn) { + disbandBtn.style.display = isHost ? "inline-block" : "none"; + } } // =================================== @@ -563,7 +617,7 @@ setupStatusButtons(); let lat = 35.681236, - lng = 139.767125; + lng = 139.767125; // 皇居(初期値) try { const pos = await getPosition(); @@ -575,7 +629,6 @@ lastLng = lng; initMap(lat, lng); - renderSelfMarker(); await saveMyLocation(lat, lng); @@ -584,6 +637,7 @@ loadMessages(); loadStations(); + // 定期更新 setInterval(async () => { try { const pos = await getPosition(); @@ -603,14 +657,25 @@ setInterval(checkGroupActive, 4000); setInterval(loadStations, 15000); - document.getElementById("exitBtn").onclick = () => - (location.href = "index.html"); - document.getElementById("disbandBtn").onclick = disbandGroup; + const exitBtn = document.getElementById("exitBtn"); + if (exitBtn) { + exitBtn.onclick = () => (location.href = "index.html"); + } + const disbandBtn = document.getElementById("disbandBtn"); + if (disbandBtn) { + disbandBtn.onclick = disbandGroup; + } - document.getElementById("chatSend").onclick = sendMessage; - document.getElementById("chatInput").addEventListener("keydown", (e) => { - if (e.key === "Enter") sendMessage(); - }); + const chatSend = document.getElementById("chatSend"); + const chatInput = document.getElementById("chatInput"); + if (chatSend && chatInput) { + chatSend.onclick = sendMessage; + chatInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") sendMessage(); + }); + } + + setTimeout(() => map.invalidateSize(), 500); } // =================================== @@ -639,8 +704,10 @@ return; } + currentGroup = g; + currentUser = u; currentHostName = data.host_name; - isHost = currentHostName === u; + isHost = currentHostName === currentUser; main(); });