Flask+Folium卫星追踪应用:仅更新Marker而非整幅地图
卫星追踪Web应用局部更新Marker问题
我正在开发基于Flask和Folium的卫星追踪Web应用,调用n2yo.com的API获取卫星数据。输入卫星的Norad ID和周期后,会在地图上显示卫星的位置与运行路径。目前尝试用htmx实现卫星移动时更新标记,但htmx会刷新整个地图,我需要仅更新Folium Marker对象。
Python代码
from flask import Flask, render_template, redirect, request, jsonify import requests import json import folium from folium.plugins import AntPath, Fullscreen app = Flask(__name__) LAT = 6.524379 LON = 3.379206 ALT = 10 APIKEY = "An_api_key" # 需填入n2yo的API密钥,可免费获取 @app.route("/", methods=['GET', 'POST']) def home(): if request.method == "POST": id = request.form["id"] period = request.form["period"] return redirect(f"/data/{id}/{period}") return render_template("home.html") @app.route("/data/<int:id>/<period>") def data(id, period): periods = float(period) * 60 content = requests.get(f'https://api.n2yo.com/rest/v1/satellite/positions/{id}/{LAT}/{LON}/{ALT}/{periods}/&apiKey={APIKEY}').content content = content.decode('utf8').replace("'", '"') my_json = json.loads(content) pos = my_json["positions"] posi = pos[0] lat = posi["satlatitude"] long = posi["satlongitude"] azimuth = posi["azimuth"] elevation = posi["elevation"] altitude = posi["sataltitude"] name = my_json["info"]["satname"] # 计算卫星路径点 pathlist = [(pos[p]["satlatitude"], pos[p]["satlongitude"]) for p in range(len(pos) - 1)] return render_template("data.html", id=id, latitude=lat, longitude=long, azimuth=azimuth, elevation=elevation, altitude=altitude, name=name, period=period, pathlist=pathlist, LAT=LAT, LON=LON) @app.route("/satellite/<int:id>") def satellite_position(id): # 获取卫星最新单点位数据 content = requests.get(f'https://api.n2yo.com/rest/v1/satellite/positions/{id}/{LAT}/{LON}/{ALT}/1/&apiKey={APIKEY}').content content = content.decode('utf8').replace("'", '"') my_json = json.loads(content) pos = my_json["positions"][0] return jsonify({ "lat": pos["satlatitude"], "lon": pos["satlongitude"], "azimuth": pos["azimuth"], "elevation": pos["elevation"], "altitude": pos["sataltitude"] }) if __name__ == "__main__": app.run(host="0.0.0.0", port=80, debug=True)
注意:需填入n2yo的API密钥才能运行,该API可免费获取。另外使用PNG图片作为标记,可忽略marker方法中的icon参数。
原模板文件
data.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Satellite Tracking Map </title> </head> <body> <h1>Satellite Tracking Map ({{id}}) {{name}}</h1> <div> <div style="position: relative;"> <div hx-get="/data/{{id}}/{{period}}" hx-trigger="every 5s" hx-target="#update">{{iframe | safe}}</div> <span id="update"> {%include 'map.html'%} </span> </div> </div> <script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script> </body> </html>
map.html
<div id="overlay" style="position: absolute; top: 10px; right: 10px; background-color: rgba(255, 255, 255, 0.05); padding: 10px; border-radius: 5px;"> <h3>Satellite Data</h3> <p>Latitude: {{latitude}}</p> <p>Longitude: {{longitude}}</p> <p>Altitude: {{altitude}}</p> <p>Azimuth: {{azimuth}}</p> <p>Elevation: {{elevation}}</p> </div>
解决方案
当前代码的核心问题是每次请求都重新生成整个Folium地图并返回完整iframe,导致整个地图刷新。要实现仅更新Marker,需换思路:让地图在前端初始化一次,之后只通过htmx获取卫星最新坐标,用JavaScript动态更新Marker位置,而非重新渲染整个地图。
步骤1:修改后端接口
新增接口专门返回卫星最新位置的JSON数据,而非完整地图(已在上面的Python代码中添加/satellite/<int:id>接口)。
步骤2:替换data.html模板
让地图仅初始化一次,用htmx定时拉取坐标并通过Leaflet API更新Marker:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Satellite Tracking Map </title> <!-- 引入Leaflet依赖(Folium基于Leaflet开发) --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> </head> <body> <h1>Satellite Tracking Map ({{id}}) {{name}}</h1> <div style="position: relative;"> <!-- 地图容器 --> <div id="map" style="width: 600px; height: 600px;"></div> <!-- 数据面板 --> <div id="overlay" style="position: absolute; top: 10px; right: 10px; background-color: rgba(255, 255, 255, 0.8); padding: 10px; border-radius: 5px;"> <h3>Satellite Data</h3> <p>Latitude: <span id="lat">{{latitude}}</span></p> <p>Longitude: <span id="lon">{{longitude}}</span></p> <p>Altitude: <span id="alt">{{altitude}}</span></p> <p>Azimuth: <span id="az">{{azimuth}}</span></p> <p>Elevation: <span id="el">{{elevation}}</span></p> </div> </div> <script> // 初始化地图 const map = L.map('map').setView([{{LAT}}, {{LON}}], 1); L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors © CARTO', subdomains: 'abcd', maxZoom: 19 }).addTo(map); // 初始化卫星Marker const satelliteIcon = L.icon({ iconUrl: '/static/assets/sat.png', iconSize: [30, 30] }); let marker = L.marker([{{latitude}}, {{longitude}}], {icon: satelliteIcon}).addTo(map); // 初始化卫星路径 const pathPoints = [{% for p in pathlist %}[{{p[0]}}, {{p[1]}}],{% endfor %}]; L.polyline(pathPoints, {color: 'red', dashArray: '30,15', weight:3}).addTo(map); // 监听htmx数据更新事件 document.body.addEventListener('htmx:afterSwap', function(evt) { if (evt.detail.target.id === 'sat-data') { const data = JSON.parse(evt.detail.xhr.responseText); // 更新Marker位置 marker.setLatLng([data.lat, data.lon]); // 更新数据面板 document.getElementById('lat').textContent = data.lat.toFixed(4); document.getElementById('lon').textContent = data.lon.toFixed(4); document.getElementById('alt').textContent = data.altitude.toFixed(1); document.getElementById('az').textContent = data.azimuth.toFixed(1); document.getElementById('el').textContent = data.elevation.toFixed(1); } }); </script> <!-- 隐藏的htmx请求容器,定时拉取卫星数据 --> <div id="sat-data" hx-get="/satellite/{{id}}" hx-trigger="every 5s" style="display: none;"></div> <script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script> </body> </html>
方案优势
- 地图仅初始化一次,避免重复渲染iframe的性能开销
- 每次仅拉取少量JSON数据,带宽占用低
- 用Leaflet原生API动态更新Marker,实现局部刷新,无闪烁或地图状态重置问题
内容的提问来源于stack exchange,提问作者Alabi Oluwabusayo Joseph
相关产品推荐
相关产品推荐

