'火炉城市'这个说法最早是民国时期媒体对长江沿线重庆、武汉、南京的叫法,后来演变成'四大火炉'等不同版本。2017 年中国气象局国家气候中心发布过一份权威排名,夏季炎热程度前 10 的省会/直辖市依次是:重庆、福州、杭州、南昌、长沙、武汉、西安、南京、合肥、南宁。

这份名单和民间感受出入不大,但体感温度才是实际的'火力'指标——同一气温下湿度、风速、城市热岛效应会把体感拉开很大差距。我刚好手头在跑一个关联实时天气的 WebGIS 项目,顺手把 2025 年 8 月 30 日下午 1 点这 10 个城市的体感温度拉出来做了个可视化,看看谁还在蒸笼里、谁已经入秋。
为什么长江流域总出火炉
这 10 个城市有 8 个沿长江分布,只有西安在黄河流域,南宁在珠江水系。共同点大多是河谷、盆地地形,空气流通差。重庆四周被大巴山、巫山等山脉包住,夏季热量散不出去;武汉河湖密布,水汽蒸发把湿度推高;长沙则受湘江流域和焚风效应双重影响。再加上城市热岛效应——比如武汉城区夏季能比郊区高出 3-5℃——体感就更加难熬。
数据怎么来的
分析用的数据分两类:空间数据和天气数据。
| 序号 | 数据 | 说明 |
|---|---|---|
| 1 | 城市区域范围表 biz_city | 地级市 Polygon 面数据 |
| 2 | 城市点位信息表 biz_geographic_name | 地名 Point 数据 |
| 3 | 城市天气信息表 | 百度实时天气接口,抓取时刻:2025-08-30 13:00 左右 |
下面这张表是后续查询的基础,把城市名和行政区划代码对应起来:
| 序号 | 行政区划代码 | 城市名称 |
|---|---|---|
| 1 | 360100 | 南昌市 |
| 2 | 500000 | 重庆市 |
| 3 | 330100 | 杭州市 |
| 4 | 320100 | 南京市 |
| 5 | 420100 | 武汉市 |
| 6 | 340100 | 合肥市 |
| 7 | 350100 | 福州市 |
| 8 | 450100 | 南宁市 |
| 9 | 430100 | 长沙市 |
| 10 | 610100 | 西安市 |
技术路线不复杂:通过百度天气接口把实时数据缓存到本地,再用空间查询把城市区域、点位和天气关联起来,最后丢给 Leaflet 渲染。

后端:一个关联查询的 Mapper
核心 SQL 就是根据当天的日期(2025-08-30)和城市代码,从 biz_weather_now、biz_city、biz_geographic_name 三张表里捞出每个城市的体感温度(feels_like)以及坐标、GeoJSON。为了演示方便,我把 10 个城市代码直接写死在 SQL 的 IN 条件里了——实际项目里还是得传参数,扩展性会好很多。
package com.yelang.project.meteorology.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yelang.project.meteorology.domain.AreaWeatherVO;
import com.yelang.project.meteorology.domain.WeatherNow;
public interface WeatherNowMapper extends BaseMapper<WeatherNow>{
final static String GET_THREEFURNACES_INFO = "<script>" +
" SELECT t2.*,T.province_code,T.province_name,T.city_code,T.city_name, " +
" st_asgeojson ( T.geom ) geomJson,st_x ( t1.geom ) lon,st_y ( t1.geom ) lat " +
" FROM biz_weather_now t2,biz_city T,biz_geographic_name t1 " +
" WHERE to_char( t2.uptime, 'YYYY-MM-DD' ) = #{day} " +
" AND t2.location_code in ('350100','330100','360100','430100','420100','610100','320100','340100','450100','500000') " +
" AND T.city_name = t1.NAME AND T.city_code = t2.location_code ORDER BY t2.feels_like desc " +
"</script>";
@Select(GET_THREEFURNACES_INFO)
List<AreaWeatherVO> getThreeFurnacesInfo(@Param("day") String day);
}
Controller 很简单,直接返回 JSON 给前端:
package com.yelang.project.meteorology.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yelang.framework.web.controller.BaseController;
import com.yelang.framework.web.domain.AjaxResult;
import com.yelang.project.meteorology.domain.AreaWeatherVO;
import com.yelang.project.meteorology.service.IWeatherNowService;
@Controller
@RequestMapping("/met/threefurnaces/weather")
public class ThreeFurnacesController extends BaseController{
private String prefix = "meteorology/weather";
@Autowired
private IWeatherNowService weatherNowService;
@RequiresPermissions("met:threefurnaces:weather:map")
@GetMapping("/index")
public String index(){
return prefix + "/threefurnacesmap";
}
@RequiresPermissions("met:threefurnaces:weather:list")
@GetMapping("/list")
@ResponseBody
public AjaxResult list(){
String day = "2025-08-30";
List<AreaWeatherVO> dataList = weatherNowService.getThreeFurnacesInfo(day);
return AjaxResult.success().put("data", dataList);
}
}
前端:Leaflet 分层渲染
前端用 Leaflet。后端返回每个城市的 GeoJSON 范围、中心点坐标和体感温度,然后根据温度值从蓝绿黄红渐变中取一个颜色,分别填充区域和标记点。体感温度数值直接用 DivIcon 贴在中心点上。代码不长,核心就是遍历数据画多边形和标记:
function previewWeather(pid,provinceCode,name){
$.ajax({
type:"get",
url:ctx + "/met/threefurnaces/weather/list",
data:{},
dataType:"json",
cache:false,
processData:false,
success:function(result){
if(result.code == web_status.SUCCESS){
collisionLayer.clearLayers();
var dataArray = result.data;
if(dataArray != null && dataArray.length > 1){
var legendData = new Array();
for(var i =0;i< dataArray.length;i++){
var areaData = dataArray[i];
var color = makeColor(areaData.feelsLike,-20,45,DIY_BLUE_GREEN_YELLOW_RED_SCHEME);
var areaLayer = L.geoJSON(JSON.parse(areaData.geomJson),{style: {color:color,fillColor:color,weight:3,"opacity":0.65, fillOpacity: 0.65 }}).addTo(mymap);
var myIcon = L.divIcon({ iconSize: null, className: '', popupAnchor:[5,5], shadowAnchor:[5,5], html: buildShowInfo(i,color,areaData) });
showLayerGroup.addLayer(areaLayer);
L.marker([areaData.lat, areaData.lon], { icon: myIcon}).addTo(collisionLayer);
}
collisionLayer.addTo(showLayerGroup);
}
}
},
error:function(){
$.modal.alertWarning("获取空间信息失败");
}
});
}
结果:谁在蒸,谁已经凉了
所有城市展开看分布,大部分集中在东南部的长江中下游。体感温度前几名:杭州 38℃、南京 38℃、南昌 38℃,长沙 37℃;武汉和福州都是 35℃,南京实际测下来 32℃(当天短时回落)。西安和重庆反而舒服——西安体感才 21℃ 左右,有点秋天的意思了。

8 月底了,长三角依然火力十足。杭州、南京、南昌这三个同处 38℃ 的城市,加上 37℃ 的长沙,构成了'蒸烤核心区'。想起以前在武汉汉口火车站转车去上海的体验,盛夏候车厅确实像蒸笼。经济热度最高的长三角,天气也跟着高烧不退。

重庆和西安是意外。重庆作为传统火炉,当日体感温度降到 30℃ 以下,西安更是只有 21℃ 左右——可能是纬度偏高加上冷空气渗透,已经开始向秋天过渡。

这个项目就是把实时天气和空间数据绑在一起,用 WebGIS 可视化出来。硬编码城市代码的写法只适合演示,线上环境应该抽象成参数。数据只是 8 月 30 日这一个时间切片,不代表整个夏季的累积热度,有兴趣可以按天拉长对比,会更全面。

