湖南空气质量WebGIS可视化系统:从数据采集到地图渲染全解析
2026/9/20 19:30:46 网站建设 项目流程

简介:面向需要了解WebGIS与气象数据可视化结合的开发者,这份湖南空气质量WebGIS可视化源码包提供了一套可独立运行的完整示例。项目基于百度天气接口获取实时空气质量数据,借助Leaflet地图库完成前端渲染,清晰展示湖南各区县污染等级分布及中、重污染与优质区域对比,并附带污染严重和空气质量优的前10名区县排名逻辑,适合作为入门WebGIS数据可视化或区域环境监测Demo的参考。压缩包共3个文件,含可编辑的主页面HTML、轻量代码运行配置inscode以及忽略规则文件,整体仅7KB,结构精简、无多余依赖,方便快速下载与学习。已有46人浏览学习,适合具备基础前端知识、希望研究天气数据接入与地图展示的开发者。

1. 项目从哪来:为什么偏偏要给湖南空气质量做一张WebGIS地图

做这个项目的起因其实很朴素。我在研究全国城市空气质量数据的时候发现一个普遍问题——各种环境监测平台上的数据表都是清一色的数值堆叠,什么PM2.5浓度、AQI指数、污染等级,密密麻麻几十列。你盯着表格看了半天,可能还是说不清“湖南今天到底是哪里在污染、污染有多重、污染带往哪个方向扩散”。数据的空间特征被完全抹掉了。

于是我就萌生了一个想法:能不能把湖南全省空气质量数据叠加到一张真实的地图上,让每个城市的空气状况像热力图一样直观地铺开?不同污染级别用不同颜色标注,点击城市还能看到浓度变化曲线,最好再配上全国所有省份的对比视图。这就是湖南空气质量WebGIS可视化系统的来源。

这个项目做完之后,完整源码可以直接运行。它的核心价值在于:把WebGIS空间可视化空气质量数据实时采集图表联动分析三条线串在了一起。无论你是数据可视化方向的学生、做环境信息化的开发人员,还是单纯对GIS感兴趣想找个练手项目的读者,这套源码都有足够的参考价值。我不光是给你看最终效果,还会把数据的获取链路、地图渲染逻辑、前后端设计思路和部署运行方法全部拆开讲清楚。

先说明一下,这个项目里的空气质量数据源公开可获取,我在后文会给出具体的采集方式。如果你只是想看可视化效果,源码里也附带了一份本地历史数据,离线状态下同样能跑出完整的地图展示。

2. 数据链路搭建:从API采集到空间化处理

2.1 空气质量数据从哪来:公开接口的选取逻辑

做可视化项目,最容易被低估的就是数据获取这一步。很多人在前端的图表组件上花了大把时间,结果数据源一换,整个项目就瘫了。这个项目在选型时首要考虑的是:数据接口不需要繁琐的申请审批流程、返回格式要足够规范、更新频率能满足以小时为单位的监测需求。

我最终选了公开空气质量监测接口作为数据源。这类接口返回的是JSON格式数据,包含每个监测站点的空气质量指数(AQI)、首要污染物、PM2.5、PM10、二氧化硫、二氧化氮、一氧化碳、臭氧等逐项浓度。湖南全省的国控站点基本覆盖了十四个市州,这个粒度做城市级的地图可视化完全够用。

接口的调用方式很简单,本质就是一个带参数的HTTP GET请求。不过在实际开发中你会发现一个细节:如果直接在前端页面里请求第三方接口,会遇到跨域限制,而且直接把数据源地址暴露在浏览器端也不合适。所以我的做法是在后端做一层数据代理——由后端定时去请求数据接口,拿到数据后缓存在本地文件或内存中,前端只跟自己的后端服务通信。这样既规避了跨域问题,也为后续离线运行铺平了路。

2.2 后端代理与定时刷新:别让地图一直处于“加载中”

数据采集模块的代码结构大致是这样:

import requests import json import time from datetime import datetime def fetch_air_quality(): """从公开接口获取全国空气质量数据""" url = "https://example.com/api/air/city/all" # 实际项目中替换为真实数据地址 params = { "app_id": "your_app_id", "app_secret": "your_app_secret", "city": "湖南省" } try: resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() data = resp.json() return data.get("data", {}).get("cities", []) except Exception as e: print(f"[{datetime.now()}] 数据请求失败: {e}") return None def load_to_storage(cities_data): """将数据写入本地JSON,作为前端数据源""" if not cities_data: return payload = { "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "cities": cities_data } with open("air_data_cache.json", "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False)

这段代码的核心思想是:请求一次、落盘一次。前端地图组件不再关心具体的接口地址,它只需要定时读取这份本地JSON就够了。定时刷新我用的方案是APScheduler,设置每30分钟执行一次抓取任务。考虑到空气质量监测本身不是秒级变化的数值,30分钟的频率已经能很好地在数据时效性和接口稳定性之间取得平衡。

2.3 空间数据关联:城市名与经纬度的对齐是地图渲染的基石

拿到了城市的空气质量数值,还不能直接画地图。WebGIS地图上渲染城市点位,必须有每个城市的经纬度坐标。这一步加工看似简单,实际上非常容易出问题。

我在项目里维护了一张湖南各市州经纬度对照表,涵盖了长沙、株洲、湘潭、衡阳、岳阳、常德、张家界、益阳、郴州、永州、怀化、娄底、湘西州等主要城市。数据接口返回的城市名称,会与这张表做匹配,匹配成功后合并出含空间坐标的记录:

CITY_LOCATION = { "长沙": [112.9388, 28.2282], "株洲": [113.1337, 27.8274], "湘潭": [112.9441, 27.8297], "衡阳": [112.5720, 26.8934], "岳阳": [113.1289, 29.3568], "常德": [111.6985, 29.0317], "张家界": [110.4792, 29.1171], "益阳": [112.3551, 28.5541], "郴州": [113.0148, 25.7705], "永州": [111.6134, 26.4204], "怀化": [110.0016, 27.5698], "娄底": [112.0085, 27.7281], "湘西州": [109.7397, 28.3129], # 更多城市... }

这里要提醒一句:城市坐标的精度直接影响点位在地图上的落位效果。用市级行政中心坐标就够了,不需要精确到站点坐标——因为这个项目做的是城市粒度的对比,不是街道级别的空气质量监测。坐标差几公里在地图的全国或全省缩放下几乎看不出来,但坐标如果填错到别的城市,画出来的地图就会非常奇怪。

3. 地图主引擎:Leaflet图层设计与AQI分级着色

3.1 为什么选Leaflet而不是OpenLayers或Mapbox

可视化地图的技术选型,我当时认真对比过三个主流方案。OpenLayers功能最全,投影坐标系支持最丰富,适合做专业GIS分析系统,但它的API设计对新手不太友好,写起代码来总觉得在做几何运算而不是画地图。Mapbox的视觉效果确实顶尖,暗黑底图加高亮数据的组合非常出片,但它的个性化底图服务很多涉及商业授权问题,部署时容易踩合规坑。

Leaflet成了这个项目的最终选择。它的体积非常轻量,压缩后只有40KB左右;插件生态成熟,地图瓦片、标记点、弹窗、热力图都有现成方案;而且资源来自公共CDN,抄起代码就能直接跑。对于一个空气质量可视化项目来说,Leaflet的表达能力已经绰绰有余。

3.2 点位与弹窗:让地图“会说话”的基础结构

地图渲染的核心逻辑分成三步。第一步是初始化地图,指定中心点和缩放级别。湖南省的地图视角我设置为以长沙和株洲之间为中点,初始缩放级别6,这样全省各市州都能完整落入视窗。

第二步是在地图上添加城市的圆形标记点。这里用L.circleMarker而不是L.marker,原因有两个:circleMarker的样式完全由代码控制,不需要加载外部图标文件;它的半径可以动态绑定到AQI数值上,数值越大、圆圈越大,视觉上就能直接形成“污染扩散”的冲击感。

第三步是配置弹窗内容。点击任意一个城市标记点,弹窗里展示该城市当前的AQI数值、污染等级、首要污染物和更新时间和一小时的浓度变化摘要。这个弹窗里的数据是从后端JSON里取出来动态拼接成的HTML:

const popupContent = ` <strong>${city.name}</strong><br/> AQI:<span style="color:${aqiColor(city.aqi)};font-weight:bold;">${city.aqi}</span><br/> 等级:${city.level}<br/> 首要污染物:${city.primary}<br/> 数据时间:${city.time} `; L.circleMarker([city.lat, city.lng], { radius: Math.max(6, Math.sqrt(city.aqi) * 1.8), color: '#ffffff', weight: 1, fillColor: aqiColor(city.aqi), fillOpacity: 0.75 }) .addTo(map) .bindPopup(popupContent);

3.3 AQI分级配色的核心逻辑

空气质量AQI分级是国家标准给定的,我们不能自己瞎定义。按照HJ 633-2012技术规定,AQI数值区间与颜色的对应关系是这样的:

AQI区间空气质量类别配色
0-50#00E400
51-100#FFFF00
101-150轻度污染#FF7E00
151-200中度污染#FF0000
201-300重度污染#99004C
>300严重污染#7E0023

这里的配色是环保部门的统一标准色,可视化项目里必须严格遵守,不能自创一套颜色体系——否则做出来的图跟官方口径对不上,读者会对数据产生误解。

function aqiColor(aqi) { if (aqi <= 50) return '#00E400'; if (aqi <= 100) return '#FFFF00'; if (aqi <= 150) return '#FF7E00'; if (aqi <= 200) return '#FF0000'; if (aqi <= 300) return '#99004C'; return '#7E0023'; }

还有一个容易被忽略的细节:标记点不要全部叠在地图图层之上,最好把其中一个标记点的样式做成“当前选中高亮”——当用户从右侧城市列表点击某个城市时,对应标记点放大并置于顶层,其他点透明度降低。这种视觉上的主次区分能让地图在信息密度较高时保持可读性。

3.4 热力图图层:污染分布的另一种表达

城市点位图适合精确查询,但如果你想知道“湖南整体的污染空间分布趋势”,点状图就有点力不从心了。所以我在项目里额外加了热力图图层,使用Leaflet的leaflet.heat插件。

热力图的定位依据是每个城市的经纬度和AQI数值,数值高的地方热量强度就高。核心代码只有几行:

const heatData = cities.map(city => [city.lat, city.lng, city.aqi / 300]); const heatLayer = L.heatLayer(heatData, { radius: 40, blur: 30, maxZoom: 10 });

图层加好之后,我用一个右上角的图层切换控件让用户在城市点位图和热力图之间自由切换。两种视图各有优势:点位图精确,热力图直观。实际在项目演示中,热力图用于展示污染带走向和区域整体水平,效果尤其好——比如湖南东部和北部重工业城市密集的区域,在热力图上会自然形成一片红色聚集区。

4. 分析透视图:ECharts联动时序与城市对比

4.1 从地图到图表:两种可视化的互补关系

地图把空间维度讲清楚了,但时间维度的变化还是空白。某个城市过去24小时空气质量的趋势如何?全省哪个城市今天最干净、哪个又垫底?这些问题是纯粹的地图无法回答的,需要引入传统的数据图表来补位。

我选择ECharts作为图表库,理由很简单:它对时空数据可视化的支持成熟,折线图、柱状图、雷达图开箱即用,与Leaflet之间的数据联动也容易实现。更关键的是ECharts全面支持中文文档和社区讨论,遇到问题搜索解决方案的效率比一般图表库高得多。

4.2 全省排行榜:让数据立刻分出高下

排行榜的设计初衷是做“一目了然”的对比。我在地图右侧设置了一个垂直柱状图,展示湖南各市州的实时AQI排名,柱子的长度代表AQI数值,颜色沿用AQI分级配色。这个排行榜是自动排序的,污染最重或最轻的城市会自然排在顶部或底部,大大减少了用户在密密麻麻的地图标记点中寻找信息的认知负担。

const sortedCities = [...cities].sort((a, b) => b.aqi - a.aqi); const topList = sortedCities.slice(0, 10); option = { grid: { left: 90, right: 30, top: 20, bottom: 30 }, xAxis: { type: 'value', name: 'AQI' }, yAxis: { type: 'category', data: topList.map(c => c.name).reverse() }, series: [{ type: 'bar', data: topList.map(c => ({ value: c.aqi, itemStyle: { color: aqiColor(c.aqi) } })), barWidth: 14, label: { show: true, position: 'right' } }] };

我额外给这个排行榜做了一个联动功能:点击柱状图上的某个城市,地图上的对应标记点会让视野平移过去,并打开该城市的弹窗。图表组件之间的双向联动,是这个项目体验感提升最明显的一处设计。很多初级项目只会做地图单方向展示,加上双向交互之后,整个系统的“分析感”完全不同了。

4.3 历史趋势:把24小时浓缩成一条曲线

排行榜提供的是某一瞬间的面面观,但空气质量的短时波动才是很多使用者最关心的。我用ECharts折线图展示选中城市最近24小时的AQI变化轨迹,每条折线按小时打点,X轴是时间,Y轴是AQI值。曲线的颜色不再按单一AQI值分级,而是分成多段着色——在轻度污染区间和中度污染区间的曲线段颜色不同,这样曲线的升降过程本身就带上了健康提示的意味。

趋势图的数据来源是历史明细文件。数据抓取模块在每次定时刷新后,除了更新实时缓存,还会把每个城市的当前AQI追加到历史记录中。这样持续运行一天以后,24小时曲线自然成形。如果你想立即看到效果,源码里附带的历史数据文件已经预置了近一周的数据记录,开箱即用。

5. 完整运行清单:从下载源码到浏览器出图的每一步

5.1 环境准备与项目结构

这个项目的运行环境非常亲民,只要你电脑上装了Python和Node.js就能跑。我的开发环境是Python 3.9 + Node 16,不过实测Python 3.6以上都能正常运行,Node版本只要不低于12问题也不大。

下载源码并解压后,你会看到这样一个目录结构:

air-quality-webgis/ │ ├── backend/ # 后端服务 │ ├── app.py # Flask应用主入口 │ ├── data_fetcher.py # 空气质量数据采集模块 │ ├── scheduler.py # 定时任务模块 │ └── cache/ # 数据缓存目录 │ ├── air_data_cache.json # 实时数据缓存 │ └── history_data.json # 历史趋势数据 │ ├── frontend/ # 前端资源 │ ├── index.html # 主页面 │ ├── css/ │ │ └── style.css │ ├── js/ │ │ ├── map.js # Leaflet地图渲染 │ │ ├── chart.js # ECharts图表 │ │ └── main.js # 业务逻辑入口 │ └── lib/ # 第三方库 │ └── requirements.txt # Python依赖清单

5.2 三步启动流程

第一步:安装后端依赖

cd backend pip install -r requirements.txt

requirements.txt里包含flask、requests、apscheduler这几个核心库。如果你是用虚拟环境管理的项目,建议先把虚拟环境启用再执行安装。

第二步:启动后端服务

python app.py

后端默认在5000端口启动Flask服务。启动成功后会看到两行日志:一行表示HTTP服务已监听,另一行表示定时任务已挂载。这时候你可以先在浏览器访问http://localhost:5000/api/air测试一下接口能否正常返回JSON格式的空气数据。

第三步:打开前端页面

前端是一个纯静态页面,不需要额外的编译构建步骤。直接通过后端提供的页面对外服务,访问http://localhost:5000即可看到完整的地图可视化界面。

地图加载出来之后,如果一切正常,你应该能看到湖南地图背景上分布着几十个颜色各异的圆形标记点,点开某个城市能查看到实时AQI详情,右侧同步显示全省AQI排行榜,底部是城市的24小时趋势曲线。

5.3 常见启动失败场景及对策

我在把源码分享给朋友测试的时候,遇到过几个典型的启动报错。这里统一列出来,方便你排查:

  • 端口被占用:如果5000端口已被其他程序占用,启动会报Address already in use。解决方案是在app.py里把app.run(host='0.0.0.0', port=5000)改成其他端口号。
  • 请求依赖库超时pip install时如果不确定依赖安装是否完整,可以在Python交互环境里执行import flaskimport requests测试,缺哪个补装哪个。
  • 地图瓦片加载不出来:Leaflet默认使用的是OpenStreetMap的公共瓦片服务,偶尔会因为网络原因加载缓慢。如果遇到这种情况,可以在map.js里把瓦片地址换成国内可达性更好的底图服务,比如高德地图瓦片、天地图瓦片等,只需替换一行URL即可。
  • 数据接口连接失败:如果第三方空气质量接口近期调整或不可达,后端日志会打印请求失败的错误信息。源码里已经做了降级处理——当获取不到新数据时,会读取项目预置的缓存数据文件作为展示数据,保证地图和图表不会白屏。

6. 踩过的坑复盘:五个容易被忽略的技术细节

项目从开发到调试结束,前前后后踩了不少坑。挑选五个最有代表性的记录下来,如果你在做类似项目,应该能少走一些弯路。

第一个坑:缩放级别设置不当导致地图视觉失衡

设置CRS与缩放级别时,如果初始缩放级别太小,整个湖南省只在地图上占了巴掌大的区域,城市标记点挤成一团没法看。实际测试下来,缩放级别设置为6,配合setView的中心点坐标(112.5, 27.3),全省视角最舒服。

第二个坑:JSON字符串编码错误导致中文乱码

前后端数据传递过程中,最容易翻车的就是中文编码。解决方式很笨也很有效:字典在json.dumps时加上ensure_ascii=False,前端在fetch请求时指定response.json()解析,不乱码。

第三个坑:circleMarker的radius值不做非线性映射时会失真

有的城市AQI是40,有的是220,如果直接按AQI线性映射半径,AQI值大的城市标记点会大到遮挡周围所有信息。我的解决方案是用平方根映射——radius = Math.sqrt(aqi) * 1.8再取最小值6,这样视觉上大小差异仍然明显,但不会产生荒唐的巨型圆点。为什么用平方根?AQI从50涨到200是4倍的关系,但人眼感知的圆形面积是r的平方,取根号之后视觉上的面积增幅才与数值增幅基本匹配。

第四个坑:定时任务叠加导致数据文件反复写入

APScheduler默认是多线程执行,如果抓取接口响应慢,上一次任务还没跑完,下一次任务又启动了,两个任务同时写同一个缓存文件,最终结果很可能是旧数据覆盖新数据。解决方式是给调度器加max_instances=1参数,强制同一个任务执行完一轮之前不允许启动下一轮。

第五个坑:历史记录无限增长导致JSON文件爆炸

空气质量数据每30分钟追加一次,跑一个月就是1440条记录,如果一直累积不清理,JSON文件会越来越大,前端解析越来越慢。我在历史数据管理模块里加了滚动清理逻辑,只保留最近7天的数据,超出部分自动丢弃。对24小时趋势图来说,7天的历史数据已经足够支撑演示和分析了。

7. 扩展方向与二次开发建议

项目跑通之后,如果你打算把这个源码扩展到自己的场景里,有几个方向我觉得很值得做。

把城市粒度细化到监测站点级。目前项目展示的是城市平均空气质量,如果你能拿到各个国控监测站点的经纬度和AQI数据,就可以把渲染粒度从市级下沉到站点级。地图上的点位会从十几个变成几十个,污染源的辨识度会大幅提升。

接入更长周期的数据做季节分析。把定时抓取连续运行三个月,积累下来的历史数据就能支持做季度趋势分析和污染高发时段分析。配合ECharts的热力图日历组件,可以很直观地看到湖南哪个月份空气最差、哪个时段AQI最容易反弹。

换成其他省份或全国范围。代码里城市的坐标表和高德瓦片地址都是可以更换的。如果你要把它改成“广东空气质量可视化”或者“全国重点城市空气质量看板”,只需要修改城市坐标数据集和地图中心点即可,后端数据接口如果返回全国数据,前端会自动适配。

接入预测模型。数据积累到一定规模后,可以用历史AQI序列做简单的时间序列回归,比如用线性回归结合当天的气象参数、风速、湿度,预测未来几小时的污染趋势,然后把预测结果和实测结果画在同一条时间轴上对比。这会让项目的技术含量再上一个台阶。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询