当前位置: 首页 > news >正文

Pythoner 的Flask项目实践-添加Shapefile面数据并展示功能Mapboxgl底图

文章目录

    • 1,实现思路
    • 2,环境依赖
    • 3,实现步骤
      • 3.1. Flask 后端(app.py)
      • 3.2. 前端 templates/addshpfile.html
      • 3.3.属性表查看(点击面弹出属性字段 Popup) 的功能
    • 4,效果展示

在上一篇的项目中,我们添加手动选择本地shpfile文件并压缩上传展示在mapboxgl底图上。

1,实现思路

  • 后端 Flask:用 geopandas 读取 shp 文件,转成 GeoJSON。

  • 前端 HTML/JS:用Mapboxgl地图库把 GeoJSON 渲染出来。

  • 自动缩放到面数据范围。

  • 新建路由 /map:显示地图页面。

2,环境依赖

在 web_demo conda 环境里安装:

conda install geopandas shapely fiona pyproj -c conda-forge

在这里插入图片描述

3,实现步骤

因为Shapefile 必须包含 至少 3 个文件(.shp, .shx, .dbf),通常还可能有 .prj;所以上传时最好打包成 .zip,这样更方便后端解压读取。

具体实现功能:点击按钮 → 弹窗选择本地 .shp 文件 → 上传到 Flask → 在 MapboxGL 里加载显示。

3.1. Flask 后端(app.py)

from flask import Flask, render_template, request, redirect, url_for, jsonify
import geopandas as gpd
import os, zipfile, tempfile, shutilapp = Flask(__name__)# 内存中的签到列表
sign_in_list = []@app.route("/")
def home():"""首页"""return render_template("home.html")@app.route("/signwallet", methods=["GET", "POST"])
def signwallet():"""首页:签到表单 + 显示签到结果"""message = Noneif request.method == "POST":name = request.form.get("username", "").strip()if name:if name not in sign_in_list:sign_in_list.append(name)message = f"欢迎你,{name}!"else:message = "请输入名字再提交哦~"return render_template("signwallet.html", message=message, users=sign_in_list)@app.route("/about")
def about():"""关于页面"""return render_template("about.html")@app.route("/contact")
def contact():"""联系我们页面"""return render_template("contact.html")@app.route("/updatefeature")
def updatefeature():"""联系我们页面"""return render_template("updatefeature.html")@app.route("/terrain3D")
def terrain3D():"""联系我们页面"""return render_template("terrain3D.html")@app.route("/clear")
def clear():"""清空签到列表后跳回首页"""sign_in_list.clear()return redirect(url_for("home"))# 新增:地图页面
@app.route("/addshpfile")
def addshpfile():return render_template("addshpfile.html")# 提供 GeoJSON 数据接口
@app.route("/data/shapefile")
def shapefile_data():shp_path = "../web_demo_flask/data/project_boundary1.shp"   # 放在项目 data/ 目录下gdf = gpd.read_file(shp_path)gdf = gdf.to_crs(epsg=4326)  # 转WGS84经纬度,Mapbox才能正确显示# return jsonify(gdf.to_crs(epsg=4326).__geo_interface__)  # 转WGS84,前端能识别return jsonify(gdf.__geo_interface__)  # 转GeoJSON输出# 上传 shapefile (zip 格式)
@app.route("/upload_shp", methods=["POST"])
def upload_shp():if "file" not in request.files:return {"error": "没有检测到文件"}, 400file = request.files["file"]if not file.filename.endswith(".zip"):return {"error": "请上传包含 .shp/.dbf/.shx 的 ZIP 文件"}, 400# 保存临时文件tmp_dir = tempfile.mkdtemp()zip_path = os.path.join(tmp_dir, file.filename)file.save(zip_path)# 解压with zipfile.ZipFile(zip_path, "r") as zip_ref:zip_ref.extractall(tmp_dir)# 找到 .shp 文件shp_file = Nonefor f in os.listdir(tmp_dir):if f.endswith(".shp"):shp_file = os.path.join(tmp_dir, f)breakif not shp_file:shutil.rmtree(tmp_dir)return {"error": "ZIP 文件里没有找到 .shp"}, 400# 读取并转成 GeoJSONtry:gdf = gpd.read_file(shp_file).to_crs(epsg=4326)geojson = gdf.__geo_interface__except Exception as e:shutil.rmtree(tmp_dir)return {"error": str(e)}, 500# 清理临时目录shutil.rmtree(tmp_dir)return jsonify(geojson)if __name__ == "__main__":app.run(debug=True, port=5000)

3.2. 前端 templates/addshpfile.html

{% extends "home.html" %}
{% block title %}地图展示{% endblock %}{% block content %}
<!-- Mapbox GL JS -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Shapefile 面数据展示 (Mapbox GL)</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 100px; bottom: 0; width: 100%; }
</style>
</head>
<body>
<h3>上传 Shapefile (ZIP)</h3>
<form id="uploadForm"><input type="file" id="shpFile" accept=".zip" /><button type="submit">上传并显示</button>
</form>
<div id="map" style="height: 500px; width: 100%; margin-top:10px;"></div><script>// 替换成你自己的 Mapbox Access Tokenmapboxgl.accessToken = 'pk.eyJ1IjoidGlnZXJiZ3AyMDIwIiwiYSI6ImNsaGhpb3Q0ZTBvMWEzcW1xcXd4aTk5bzIifQ.4mA7mUrhK09N4vrrQfZA_Q';var map = new mapboxgl.Map({container: 'map',style: 'mapbox://styles/mapbox/streets-v12',center: [54.5, 24.0], // 阿联酋大致中心zoom: 6});document.getElementById("uploadForm").addEventListener("submit", function(e){e.preventDefault();var fileInput = document.getElementById("shpFile");if(fileInput.files.length === 0){alert("请选择一个 zip 文件");return;}var formData = new FormData();formData.append("file", fileInput.files[0]);fetch("/upload_shp", {method: "POST",body: formData}).then(res => res.json()).then(data => {if(data.error){alert("错误: " + data.error);return;}// 如果已有图层,先移除if(map.getSource("uploadedShp")){map.removeLayer("uploadedShp-fill");map.removeLayer("uploadedShp-line");map.removeSource("uploadedShp");}map.addSource("uploadedShp", {"type": "geojson","data": data});map.addLayer({"id": "uploadedShp-fill","type": "fill","source": "uploadedShp","paint": {"fill-color": "#088", "fill-opacity": 0.5}});map.addLayer({"id": "uploadedShp-line","type": "line","source": "uploadedShp","paint": {"line-color": "#000", "line-width": 2}});// 自动缩放var bbox = turf.bbox(data);map.fitBounds(bbox, {padding: 20});// 点击显示属性表(Popup)map.on("click", "uploadedShp-fill", function(e){var props = e.features[0].properties;var html = "<b>属性信息:</b><br>";for(var key in props){html += key + ": " + props[key] + "<br>";}new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(html).addTo(map);});// 鼠标悬停时变成小手map.on("mouseenter", "uploadedShp-fill", function () {map.getCanvas().style.cursor = "pointer";});map.on("mouseleave", "uploadedShp-fill", function () {map.getCanvas().style.cursor = "";});}).catch(err => alert("上传失败: " + err));
});
</script><!-- Turf.js 用于计算 bbox -->
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
{% endblock %}

使用方法:

  1. 把 .shp/.shx/.dbf/.prj 打包成一个 yourdata.zip

  2. 在页面点击 选择文件 → 选 yourdata.zip → 点击上传

  3. Flask 解压并读取 shapefile → 返回 GeoJSON → MapboxGL 渲染

3.3.属性表查看(点击面弹出属性字段 Popup) 的功能

  1. Flask 后端(无需改动)

之前 /upload_shp 返回的 geojson 已经包含了属性字段(properties),直接在前端用就行。

  1. 前端html添加以下代码
        // ✅ 点击显示属性表(Popup)map.on("click", "uploadedShp-fill", function(e){var props = e.features[0].properties;var html = "<b>属性信息:</b><br>";for(var key in props){html += key + ": " + props[key] + "<br>";}new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(html).addTo(map);});// 鼠标悬停时变成小手map.on("mouseenter", "uploadedShp-fill", function () {map.getCanvas().style.cursor = "pointer";});map.on("mouseleave", "uploadedShp-fill", function () {map.getCanvas().style.cursor = "";});

4,效果展示

  1. 上传 .shp + .shx + .dbf 文件的压缩zip文件 → MapboxGL 显示面数据

  2. 鼠标点击某个面 → 弹窗显示该要素的所有属性字段和值

  3. 鼠标悬停时变成小手,提示可点击

在这里插入图片描述

在这里插入图片描述


“人的一生会经历很多痛苦,但回头想想,都是传奇”。


http://www.dtcms.com/a/398018.html

相关文章:

  • Flutter混合Android开发Release 打包失败GeneratedPluginRegistrant.java,Plugin不存在
  • docker 安装TDengine 并创建新用户
  • 网站推广实施方案珠海网站制作软件
  • 为世界添彩 - WebGL 中的颜色与着色器变量
  • 初识MYSQL —— mysql的安装
  • c回顾 01
  • 【LeetCode 每日一题】3484. 设计电子表格——(解法一)二维数组
  • python+django/flask+springboot实践性教学系统 实训任务发布 学生作业提交 教师评阅管理系统
  • 洞悉未来,智驭不确定性:蒙特卡洛模拟决策模型实践
  • 长宁哪里有做网站优化比较好利润在100万到300万之间税率2021
  • 沈阳网站设计外包广西建设网官网桂建云
  • vscode 插件怎么实现编辑器行号处添加图标标记
  • Git 从零到一:以 Gitee 为例的实战与可视化指南
  • React 标准 SPA 项目 入门学习记录
  • HAProxy 完整指南:简介、负载均衡原理与安装配置
  • 领码课堂 | React 核心组件与高级性能优化全实战指南
  • 涡轮丝杆升降机的丝杆材质有哪些?
  • 前端笔记:vue中 Map、Set之间的使用和区别
  • 中美关系最新消息视频重庆seo优化公司
  • 【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制
  • 英雄联盟视频网站源码做产品设计之前怎么查资料国外网站
  • Vue3-接入飞书H5应用
  • 四川省建设厅网站川北医学院广告网站怎么建设
  • 七彩喜智慧养老:科技向善,让晚年生活绽放“喜”悦之光
  • 模型驱动的 AI Agent架构:亚马逊云科技的Strands框架技术深度解析
  • 【数据结构】——外部排序(K路归并)
  • 【观成科技】活跃黑产团伙“黑猫”攻击武器加密通信分析
  • 高斯过程(Gaussian Process)回归:一种贝叶斯非参数方法
  • 微算法科技(NASDAQ MLGO)创新基于账户加权图与后量子密码学的区块链
  • 中国银行信息科技岗位笔试