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
相关产品推荐
相关产品推荐

