You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: '&copy; OpenStreetMap contributors &copy; 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:25:01