diff --git a/map4.js b/map4.js index f6fbbd9..a505e08 100644 --- a/map4.js +++ b/map4.js @@ -20,9 +20,24 @@ let lastLat = null; let lastLng = null; +// 駅マーカー用 +let stationMarkers = []; + const DEFAULT_LAT = 35.681236; const DEFAULT_LNG = 139.767125; +// 駅ピン用アイコン(青) +const stationIcon = L.icon({ + iconUrl: + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-blue.png", + shadowUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + shadowSize: [41, 41], + shadowAnchor: [13, 41], +}); + // =================================== function loadParams() { const p = new URLSearchParams(location.search); @@ -33,8 +48,7 @@ } // =================================== -// 小型ピン(ズレなし) -// 自分=緑 / 他人=黄 +// 小型ピン(自分=緑 / 他人=黄) // =================================== function createLabeledMarker(lat, lng, name, isSelf) { const pinUrl = isSelf @@ -54,7 +68,7 @@ html: iconHtml, iconSize: [32, 32], iconAnchor: [16, 32], - }) + }), }); } @@ -62,7 +76,7 @@ function getPosition() { return new Promise((res, rej) => { navigator.geolocation.getCurrentPosition( - pos => res(pos.coords), + (pos) => res(pos.coords), rej, { enableHighAccuracy: true } ); @@ -80,7 +94,8 @@ // =================================== async function saveMyLocation(lat, lng) { - await supa.from("locations") + await supa + .from("locations") .delete() .eq("group_name", currentGroup) .eq("device_id", deviceId); @@ -101,25 +116,28 @@ selfMarker = createLabeledMarker(lastLat, lastLng, currentUser, true); selfMarker.addTo(map); - selfMarker.on("click", e => { + selfMarker.on("click", (e) => { map.panTo(e.latlng, { animate: true, duration: 0.4 }); }); } // =================================== +// メンバー一覧 +// =================================== function showMemberList(latestByDevice) { const list = document.getElementById("memberList"); list.innerHTML = ""; - Object.values(latestByDevice).forEach(row => { + Object.values(latestByDevice).forEach((row) => { const li = document.createElement("li"); const diff = (Date.now() - new Date(row.updated_at)) / 1000; - const isOnline = diff < 6; + const isOnline = diff < 6; // 6秒以内ならオンライン li.classList.add(isOnline ? "online" : "offline"); - const circleColor = (row.device_id === deviceId) ? "#58c16b" : "#FFD700"; + // 自分=緑 / 他人=黄 + const circleColor = row.device_id === deviceId ? "#58c16b" : "#FFD700"; const icon = document.createElement("div"); icon.className = "member-icon"; @@ -135,9 +153,10 @@ li.append(icon, name, status); li.onclick = () => { - const marker = (row.device_id === deviceId) - ? selfMarker - : otherMarkers.find(m => m._deviceId === row.device_id); + const marker = + row.device_id === deviceId + ? selfMarker + : otherMarkers.find((m) => m._deviceId === row.device_id); if (marker) { map.panTo(marker.getLatLng(), { animate: true, duration: 0.5 }); @@ -150,7 +169,8 @@ // =================================== async function showOtherUsers() { - const { data } = await supa.from("locations") + const { data } = await supa + .from("locations") .select("*") .eq("group_name", currentGroup) .order("updated_at", { ascending: false }); @@ -165,16 +185,16 @@ showMemberList(latestByDevice); - otherMarkers.forEach(m => map.removeLayer(m)); + otherMarkers.forEach((m) => map.removeLayer(m)); otherMarkers = []; - Object.values(latestByDevice).forEach(row => { + Object.values(latestByDevice).forEach((row) => { if (row.device_id === deviceId) return; const mk = createLabeledMarker(row.lat, row.lng, row.user_name, false); mk._deviceId = row.device_id; - mk.on("click", e => { + mk.on("click", (e) => { map.panTo(e.latlng, { animate: true, duration: 0.4 }); }); @@ -184,10 +204,97 @@ } // =================================== +// ここから駅関連 +// =================================== + +// 距離計算(メートル) +function distanceMeters(lat1, lng1, lat2, lng2) { + const R = 6371000; + const toRad = (d) => (d * Math.PI) / 180; + const dLat = toRad(lat2 - lat1); + const dLng = toRad(lng2 - lng1); + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRad(lat1)) * + Math.cos(toRad(lat2)) * + Math.sin(dLng / 2) * + Math.sin(dLng / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; +} + +// Overpass API で半径5kmの駅を取得 +async function fetchStationsAround(lat, lng) { + const query = ` + [out:json]; + node["railway"="station"](around:5000,${lat},${lng}); + out; + `; + const url = + "https://overpass-api.de/api/interpreter?data=" + + encodeURIComponent(query); + + const res = await fetch(url); + if (!res.ok) throw new Error("Overpass API error"); + const json = await res.json(); + return json.elements || []; +} + +// 駅マーカー追加+最寄り駅表示 +async function loadStations() { + const box = document.getElementById("nearestBox"); + try { + const stations = await fetchStationsAround(lastLat, lastLng); + + // 既存駅マーカー削除 + stationMarkers.forEach((m) => map.removeLayer(m)); + stationMarkers = []; + + if (stations.length === 0) { + box.textContent = "5km以内に駅が見つかりませんでした。"; + return; + } + + let nearest = null; + let nearestDist = Infinity; + + stations.forEach((st) => { + const sLat = st.lat; + const sLng = st.lon; + const name = + (st.tags && (st.tags.name || st.tags["name:ja"])) || "駅名不明"; + + // 距離 + const d = distanceMeters(lastLat, lastLng, sLat, sLng); + if (d < nearestDist) { + nearestDist = d; + nearest = { name, lat: sLat, lng: sLng }; + } + + // 駅マーカー追加 + const mk = L.marker([sLat, sLng], { icon: stationIcon }).addTo(map); + mk.bindPopup(`${name}駅`); + stationMarkers.push(mk); + }); + + if (nearest) { + const distKm = (nearestDist / 1000).toFixed(2); + box.textContent = `ここから一番近いのは「${nearest.name}駅」です!(約 ${distKm}km)`; + } else { + box.textContent = "5km以内に駅が見つかりませんでした。"; + } + } catch (e) { + console.error(e); + box.textContent = "駅情報を取得できませんでした。"; + } +} + +// =================================== async function main() { loadParams(); - let lat = DEFAULT_LAT, lng = DEFAULT_LNG; + let lat = DEFAULT_LAT, + lng = DEFAULT_LNG; try { const pos = await getPosition(); @@ -204,6 +311,10 @@ await showOtherUsers(); + // ★ 駅読み込み(最初の位置で1回だけ) + loadStations(); + + // 自分の位置更新(2秒ごと) setInterval(async () => { try { const pos = await getPosition(); @@ -211,13 +322,16 @@ lastLng = pos.longitude; renderSelfMarker(); await saveMyLocation(lastLat, lastLng); + // 位置が大きく変わったときだけ駅再取得してもよいが、 + // API負荷を考えて今回は初回のみ。 } catch {} - }, 2000); // ← 2秒で自分更新(リアルタイム) + }, 2000); - setInterval(showOtherUsers, 1000); // ← 1秒で他人更新 + // 他ユーザー位置更新(1秒ごと) + setInterval(showOtherUsers, 1000); - document.getElementById("exitBtn").onclick = - () => (location.href = "index.html"); + document.getElementById("exitBtn").onclick = () => + (location.href = "index.html"); setTimeout(() => map.invalidateSize(), 400); }