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

ReactJS+Flask整合:点击Leaflet地图触发Python脚本执行

React-Leaflet地图调用Python脚本的整合方案

问题描述

我开发了一个集成React-Leaflet地图的ReactJS前端,点击地图时可获取对应位置的经纬度。我需要在该点击事件触发时调用一个Python脚本,目前已搭建好Flask后端接口,前端也能请求该接口,但不清楚如何将所有环节整合,确保Python脚本正常加载运行。以下是我的代码:

React代码

import { useMapEvents, Popup, Marker} from "react-leaflet";
import { useState } from "react"; // 原代码遗漏该导入

const PopupInfo = () => {
    const [markers, setMarkers] = useState([]);

    const map = useMapEvents({
        async click(e) {
            const newMarker = e.latlng
            setMarkers([...markers, newMarker])
            console.log(e.latlng, "info")
            // 请求后端接口传递坐标
            const response = await fetch(`/coordinates?sel_lat=${e.latlng.lat}&sel_lon=${e.latlng.lng}`,
                {
                method: 'GET',
                headers: {
                Accept: 'application/json',
                }}); 
            console.log(response, 'TESTING PROMISE')  
            if (!response.ok) {
                throw new Error(`Error! status: ${response.status}`);
            }         
            const result = response.json();         
            console.log('result is: ', JSON.stringify(result, null, 4));  
    }
})
    return (
        <>
        {markers.map((marker, index) => 
            <Marker position={marker} key={index}>
                <Popup>Latitude: ({marker.lat})<br></br>Longitude: ({marker.lng})</Popup>
            </Marker>)}
        </>
    );
};

export default PopupInfo;

Python脚本代码

import argparse
import time
import pandas as pd
import datetime as dt
import json

from src.bcolors import bcolors as bc
import src.config as cfg
import src.utils as utils
import src.cfsr as cfsr
import src.gfs as gfs

def parse_args():
    parser = argparse.ArgumentParser(
        description="PURPOSE: Extract current meteorological information\n \
                for a location and give climate context",
    )

    parser.add_argument(
        "--sel_lat", type=float, dest="sel_lat", help="Latitude of requested location"
    )
    parser.add_argument(
        "--sel_lon", type=float, dest="sel_lon", help="Longitude of requested location"
    )

    args = parser.parse_args()
    return args


def main():

    start = time.time()
    args = parse_args()

    print(f"{bc.HEADER}EXTRACT INFO FOR SELECTED LOCATION{bc.ENDC}")
    print(f"{bc.HEADER}({args.sel_lat},{args.sel_lon}){bc.ENDC}")

    slat = args.sel_lat
    slon = args.sel_lon
    slon360 = utils.lon_to_360(args.sel_lon)
    if cfg.debug:
        fin_ij = utils.get_ij_data(cfg.file_ref, slat, slon360)
        print(
            f"{bc.BOLD}Selected grid point: {fin_ij.lat.values}, {fin_ij.lon.values}{bc.ENDC}"
        )

    if cfg.debug:
        fin_ij = utils.get_ij_data(cfg.file_ref, slat, slon360)
        print(
            f"{bc.BOLD}Selected grid point: {fin_ij.lat.values}, {fin_ij.lon.values}{bc.ENDC}"
        )

    print(f"Elapsed time initialization: {time.time()-start}s")
    this_time = time.time()

    sdoy = utils.calc_doy_noleap(cfg.today)

    print(f"Elapsed time doy: {time.time()-this_time}s")
    this_time = time.time()

    # Get data for location
    sdata_doy = cfsr.get_data_doy_loc(slat, slon360, sdoy)
    sdata_all = cfsr.get_data_loc(slat, slon360)

    print(f"Elapsed time load sdata: {time.time()-this_time}s")
    this_time = time.time()

    sqtiles = sdata_doy.sel(time=slice(f"{cfg.bsyear}", f"{cfg.beyear}")).quantile(
        cfg.qtiles
    )
    print(f"Elapsed time qtiles: {time.time()-this_time}s")
    this_time = time.time()

    fcvars = gfs.get_loc_fcvars(slat, slon360)

    print(f"Elapsed time fcvars: {time.time()-this_time}s")
    this_time = time.time()

    # Loading this year data
    sdata_y = cfsr.get_data_this_year_loc(slat, slon360)
    shmap_y = cfsr.get_hmap_this_year_loc(slat, slon360)
    shwcs_y = cfsr.get_hwcs_this_year_loc(slat, slon360)

    # Loading bounds (for max-min plots)
    sbounds = cfsr.get_bounds_loc(slat, slon360)

    print(f"Elapsed time load sdat_ty: {time.time()-this_time}s")
    this_time = time.time()
    doy_temp_ptile = (abs(sqtiles - fcvars)).idxmin(dim="quantile") * 100.0

    print(f"Elapsed time doy qtile: {time.time()-this_time}s")
    this_time = time.time()

    print(
        f"{bc.OKGREEN}Today's tmax {round(fcvars.tmax.values.item(),1)} at the selected point ({slat},{slon}) will be on the {int(doy_temp_ptile.tmax)}th percentile{bc.ENDC}"
    )
    print(
        f"{bc.OKGREEN}Today's tmin {round(fcvars.tmin.values.item(),1)} at the selected point ({slat},{slon}) will be on the {int(doy_temp_ptile.tmin)}th percentile{bc.ENDC}"
    )
    print(
        f"{bc.OKGREEN}Today's tmed {round(fcvars.tmed.values.item(),1)} at the selected point ({slat},{slon}) will be on the {int(doy_temp_ptile.tmed)}th percentile{bc.ENDC}"
    )

    # 写入CSV文件
    sdata_doy.drop(["lat", "lon"]).to_dataframe().round(1).to_csv(
        f"{cfg.wrk_dir}/temp_doy.csv", index=False
    )
    sdata_y.drop(["lat", "lon"]).to_dataframe().round(1).to_csv(
        f"{cfg.wrk_dir}/temp_current_year.csv", index=True
    )
    shmap_y.drop(["lat", "lon"]).to_dataframe().round(1).to_csv(
        f"{cfg.wrk_dir}/hmap_current_year.csv", index=True
    )
    shwcs_y.drop(["lat", "lon"]).to_dataframe().round(1).to_csv(
        f"{cfg.wrk_dir}/hwcs_current_year.csv", index=True
    )
    sbounds_time = sbounds.assign_coords(
        doy=pd.date_range(
            dt.datetime(cfg.today.year, 1, 1),
            dt.datetime(cfg.today.year, 12, 31),
            freq="D",
        )
    )
    sbounds_time.drop(["lat", "lon"]).to_dataframe().round(1).to_csv(
        f"{cfg.wrk_dir}/bounds.csv", index=True
    )

    fcvars.to_dataframe().round(1).to_csv(f"{cfg.wrk_dir}/fcvars.csv", index=True)

    print(f"Elapsed time write csv files: {time.time()-this_time}s")
    this_time = time.time()

    # 生成统计结果JSON
    print(f"{bc.HEADER}Writing out json file with loc info{bc.ENDC}")
    loc_stats = {
        "tmax": round(fcvars.tmax.values.item(), 1),
        "tmin": round(fcvars.tmin.values.item(), 1),
        "tmed": round(fcvars.tmed.values.item(), 1),
        "tmax_ptile": int(doy_temp_ptile.tmax),
        "tmin_ptile": int(doy_temp_ptile.tmin),
        "tmed_ptile": int(doy_temp_ptile.tmed),
        "tmax_alltime_record_x": sdata_all.tmax.max().values.item(),
        "tmax_alltime_record_n": sdata_all.tmax.min().values.item(),
        "tmin_alltime_record_x": sdata_all.tmin.max().values.item(),
        "tmin_alltime_record_n": sdata_all.tmin.min().values.item(),
        "tmed_alltime_record_x": sdata_all.tmed.max().values.item(),
        "tmed_alltime_record_n": sdata_all.tmed.min().values.item(),
    }

    with open(f"{cfg.wrk_dir}/loc_stats.json", "w", encoding="utf-8") as f:
        f.write(json.dumps(loc_stats, indent=2))

    print(f"Elapsed time write json file: {time.time()-this_time}s")
    this_time = time.time()

    print(f"{bc.OKGREEN}Elapsed time TOTAL: {time.time()-start}s{bc.ENDC}")


if __name__ == "__main__":
    raise SystemExit(main())

整合步骤

1. 改造Python脚本,适配Flask调用

原脚本通过命令行参数接收坐标,需修改为支持函数传参,同时保留命令行运行能力:

# 修改main函数,支持直接传入坐标参数
def main(sel_lat=None, sel_lon=None):
    start = time.time()
    # 优先使用传入的参数,无参数时从命令行读取
    if sel_lat is None or sel_lon is None:
        args = parse_args()
        slat = args.sel_lat
        slon = args.sel_lon
    else:
        slat = sel_lat
        slon = sel_lon

    # 后续原有逻辑保持不变...

    # 函数末尾返回统计结果,供Flask接口直接返回
    return loc_stats

# 保留命令行运行逻辑
if __name__ == "__main__":
    args = parse_args()
    raise SystemExit(main(args.sel_lat, args.sel_lon))

2. 编写Flask后端接口

创建Flask应用,实现接收前端请求、调用Python脚本并返回结果的逻辑:

from flask import Flask, request, jsonify
from flask_cors import CORS  # 解决跨域问题
import your_script_name  # 替换为你的Python脚本文件名(不含.py)

app = Flask(__name__)
CORS(app)  # 开发阶段允许所有跨域请求,生产环境可配置指定域名

@app.route('/coordinates', methods=['GET'])
def process_coordinates():
    try:
        # 从请求参数中获取经纬度
        sel_lat = float(request.args.get('sel_lat'))
        sel_lon = float(request.args.get('sel_lon'))

        # 调用改造后的Python脚本
        loc_stats = your_script_name.main(sel_lat, sel_lon)

        # 返回JSON格式结果给前端
        return jsonify(loc_stats)
    except Exception as e:
        # 捕获错误并返回异常信息
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True, port=5000)  # 启动Flask服务,端口可自定义

3. 优化前端代码

修复异步处理和闭包问题,确保请求正常:

import { useMapEvents, Popup, Marker} from "react-leaflet";
import { useState } from "react";

const PopupInfo = () => {
    const [markers, setMarkers] = useState([]);

    const map = useMapEvents({
        async click(e) {
            try {
                const newMarker = e.latlng;
                // 使用函数式更新state,避免闭包导致的旧值问题
                setMarkers(prev => [...prev, newMarker]);
                console.log(e.latlng, "info");

                // 请求Flask后端接口(注意替换为实际后端地址)
                const response = await fetch(`http://localhost:5000/coordinates?sel_lat=${e.latlng.lat}&sel_lon=${e.latlng.lng}`, {
                    method: 'GET',
                    headers: {
                        Accept: 'application/json',
                    }
                }); 

                if (!response.ok) {
                    throw new Error(`请求失败: 状态码 ${response.status}`);
                }         
                // response.json()是异步操作,需添加await
                const result = await response.json();         
                console.log('接口返回结果: ', JSON.stringify(result, null, 4));
            } catch (error) {
                console.error('处理出错:', error);
                // 可在此添加用户友好的错误提示
            }
        }
    });

    return (
        <>
            {markers.map((marker, index) => 
                <Marker position={marker} key={index}>
                    <Popup>纬度: {marker.lat}<br/>经度: {marker.lng}</Popup>
                </Marker>
            )}
        </>
    );
};

export default PopupInfo;

4. 测试与部署注意事项

  • 安装依赖:执行pip install flask flask-cors pandas xarray(根据脚本实际依赖调整)。
  • 跨域配置:生产环境建议通过Nginx反向代理统一域名,避免跨域问题。
  • 文件权限:确保Python脚本有写入cfg.wrk_dir目录的权限,目录不存在需提前创建。
  • 启动顺序:先启动Flask后端,再启动React前端,测试点击地图是否能成功调用脚本并返回数据。

内容的提问来源于stack exchange,提问作者andy spears

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 16:15:38