| import gradio as gr | |
| import math | |
| treasures = [ | |
| {"name": "寶藏第1個", "lat": 24.985703, "lon": 121.288680}, | |
| {"name": "寶藏第2個", "lat": 24.985900, "lon": 121.289000}, | |
| {"name": "寶藏第3個", "lat": 24.956398, "lon": 121.297729}, | |
| ] | |
| def haversine(lat1, lon1, lat2, lon2): | |
| R = 6371000 | |
| phi1, phi2 = math.radians(lat1), math.radians(lat2) | |
| d_phi = math.radians(lat2 - lat1) | |
| d_lambda = math.radians(lon2 - lon1) | |
| a = math.sin(d_phi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(d_lambda/2)**2 | |
| c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) | |
| return R * c | |
| def check_nearest(lat, lon, found): | |
| if not lat or not lon: | |
| return "❌ 請先取得 GPS", "", "", found | |
| try: | |
| lat = float(lat) | |
| lon = float(lon) | |
| except: | |
| return "❌ 座標格式錯誤", "", "", found | |
| nearest = None | |
| nearest_idx = None | |
| min_distance = float("inf") | |
| for idx, treasure in enumerate(treasures): | |
| dist = haversine(lat, lon, treasure["lat"], treasure["lon"]) | |
| if dist < min_distance: | |
| min_distance = dist | |
| nearest = treasure | |
| nearest_idx = idx | |
| dist = haversine(lat, lon, nearest["lat"], nearest["lon"]) | |
| gmap = f"https://www.google.com/maps/dir/{lat},{lon}/{nearest['lat']},{nearest['lon']}" | |
| if dist <= 30 and nearest_idx not in found: | |
| found.append(nearest_idx) | |
| status = f"🎯 找到 {nearest['name']}!距離:{dist:.2f} 公尺 ✅" | |
| html = f""" | |
| <h3>{nearest['name']} 🎉 已找到!</h3> | |
| <img src="https://media.giphy.com/media/5GoVLqeAOo6PK/giphy.gif" style="width:100%; max-height:200px;"> | |
| """ | |
| elif nearest_idx in found: | |
| status = f"✅ {nearest['name']} 已找到!目前距離:{dist:.2f} 公尺" | |
| html = f'<a href="{gmap}" target="_blank">➡️ 再次導航至 {nearest["name"]}</a>' | |
| else: | |
| status = f"📍 距離 {nearest['name']}:{dist:.2f} 公尺,請再靠近一些" | |
| html = f'<a href="{gmap}" target="_blank">➡️ 點我導航至 {nearest["name"]}</a>' | |
| if len(found) == len(treasures): | |
| html += """ | |
| <h2 style="text-align:center;">🎉 所有寶藏已找到!</h2> | |
| <img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGU4YzZhOTdjYjJmODlmZDEwZmExZDcyOTU4NGY5NGY4MzU3ZDM2YSZjdD1n/26n6WywJyh39n1pBu/giphy.gif" style="width:100%; max-height:300px;"> | |
| """ | |
| progress = f"✅ 已找到:{', '.join([treasures[i]['name'] for i in sorted(found)])}" if found else "" | |
| return status, html, progress, found | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 🗺️ GPS 尋寶遊戲") | |
| gr.Markdown("📍 自動找最近寶藏,進入 30 公尺範圍內顯示動畫 🎉") | |
| gr.HTML(""" | |
| <button id="gpsButton" style="padding:10px;font-size:16px;">📍 點我取得 GPS</button> | |
| <script> | |
| document.addEventListener("DOMContentLoaded", function () { | |
| document.getElementById("gpsButton").addEventListener("click", function () { | |
| navigator.geolocation.getCurrentPosition( | |
| function(pos) { | |
| const latbox = document.querySelectorAll("textarea")[0]; | |
| const lonbox = document.querySelectorAll("textarea")[1]; | |
| latbox.value = pos.coords.latitude; | |
| lonbox.value = pos.coords.longitude; | |
| latbox.dispatchEvent(new Event('input')); | |
| lonbox.dispatchEvent(new Event('input')); | |
| }, | |
| function(err) { | |
| alert("⚠️ GPS 錯誤:" + err.message); | |
| } | |
| ); | |
| }); | |
| }); | |
| </script> | |
| """) | |
| with gr.Row(): | |
| lat = gr.Textbox(label="Latitude") | |
| lon = gr.Textbox(label="Longitude") | |
| status = gr.Markdown() | |
| link = gr.HTML() | |
| progress = gr.Markdown() | |
| found_state = gr.State([]) | |
| btn = gr.Button("🔍 檢查距離 + 導航") | |
| btn.click(fn=check_nearest, inputs=[lat, lon, found_state], | |
| outputs=[status, link, progress, found_state]) | |
| demo.launch() | |