简介:这份资源提供天津市地表水水质国控断面的坐标数据,面向环境科学研究者、GIS 分析人员、水资源管理者及环保政策制定者,用于定位监测点位、开展空间分析与水质变化趋势研究。数据涵盖所属省市、流域、河流、断面名称及经纬度等属性,可精确映射到地图,辅助判断污染来源与扩散方向。压缩包共 8 个文件,约 5KB,以 shapefile 地理空间数据为主:shp 存储断面几何形状,dbf 保存断面名称与流域等属性,prj 定义坐标投影,shx、sbn、sbx 提供索引加速访问,cpg 指定字符编码,shp.xml 记录元数据,整体可直接在 ArcGIS 或 QGIS 中加载处理。目前已有 616 人学习下载,适合需要快速获取权威国控断面底图、开展环境监测评估与报告制图的读者参考使用。
1. 23个国控断面坐标能拿来做什么:先搞清楚这份数据的分量
如果你正在做流域水质分析、断面周边污染源排查,或者要给一张天津地表水图配上准确的监测点位,手头缺的第一样东西往往不是模型,而是坐标。这份“天津市地表水水质国控断面坐标数据”一共23个断面,属于典型的轻量级空间基础数据——体量不大,但定位精准,直接对应国家地表水环境质量监测网在天津的国控点位。它解决的核心问题很具体:让你知道这23个断面究竟落在哪条河、哪个区、哪段河道上,从而把水质监测数值和真实地理空间挂上钩。适合环境监测、流域治理、GIS制图、水文分析方向的从业者,也适合需要做天津本地水环境可视化的开发者。别小看这23个点,国控断面的位置是经过国家层面布设和认定的,不是随便在河上戳一个坐标,用它做底图比自己在卫星图上目视描点靠谱得多。
2. 断面坐标数据的结构拆解:字段、坐标系与23个点的分布逻辑
拿到一份坐标数据,第一件事不是急着往地图上扔,而是先搞清楚它长什么样。这份数据围绕23个国控断面组织,每个断面至少包含断面名称和经纬度坐标,可能还带有所在河流、所属区域等辅助字段。不同来源的坐标数据在字段命名和坐标系上差异很大,直接混用会导致点位偏移几百米甚至更多,所以这一章先把数据结构、坐标系和分布逻辑讲透,再谈怎么用。
2.1 断面名称与经纬度字段的对应关系
国控断面的命名通常遵循“河流名+断面名”或“行政区+断面名”的规则,比如某河某桥、某区某闸。坐标字段一般以经度(longitude/lng/lon)和纬度(latitude/lat)成对出现,单位是十进制度数。你需要确认的是:经度在前还是纬度在前,小数点后保留几位。国内数据常见保留6位小数,对应约0.1米精度,对断面级别完全够用。如果数据里还有“河流名称”“所在区县”“断面属性”等列,建议保留,后续做分类统计和地图标注时能省不少事。
下面是一段读取和检查字段的示例代码,用Python的pandas快速摸清数据结构:
import pandas as pd # 读取断面坐标数据,注意编码,国内数据常见utf-8或gbk df = pd.read_csv("tianjin_sections.csv", encoding="utf-8") # 查看前5行,确认字段名和坐标格式 print(df.head()) # 检查列名,确认经纬度字段的实际命名 print(df.columns.tolist()) # 统计断面数量,核对是否为23个 print("断面总数:", len(df)) # 检查是否有缺失坐标 print(df[["经度", "纬度"]].isnull().sum())这段代码的逻辑很直接:先读进来,再看字段名和坐标格式,最后核对数量是否为23。参数上唯一需要注意的是编码,如果报UnicodeDecodeError,把encoding换成"gbk"再试。如果经纬度字段名不是“经度/纬度”,按实际列名替换即可。检查缺失值这一步别跳过,坐标缺失的断面在地图上会直接消失,后面做空间分析时才发现就晚了。
2.2 坐标系选择:WGS84、GCJ02与CGCS2000的差别
这是最容易翻车的地方。同一组经纬度数字,放在不同坐标系里,落点能差出几百米。国内常见三种:WGS84(GPS原始坐标)、GCJ02(国测局加密坐标,国内互联网地图常用)、CGCS2000(国家大地坐标系,与WGS84在米级上接近)。国控断面坐标如果是官方监测数据,通常基于CGCS2000或WGS84;如果是从某些在线地图上采集的,很可能是GCJ02。判断方法:把点叠加到天地图或卫星影像上,看是否落在河道中心。如果整体偏移且方向一致,基本就是坐标系不匹配。
常见做法是:先假定数据为WGS84,叠加到天地图影像上验证;若偏移,再尝试GCJ02转WGS84。转换可以用coord-convert这类库,也可以手写公式。下面是一个WGS84与GCJ02互转的简化示例:
import math # GCJ02转WGS84的简化实现,适用于点位精度要求不极端的场景 def gcj02_to_wgs84(lng, lat): a = 6378245.0 ee = 0.00669342162296594323 dlat = transform_lat(lng - 105.0, lat - 35.0) dlng = transform_lng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * math.pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) return lng - dlng, lat - dlat def transform_lat(x, y): ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x)) ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0 return ret def transform_lng(x, y): ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x)) ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0 return ret这段代码实现的是GCJ02到WGS84的粗略转换,参数a和ee是克拉索夫斯基椭球参数,转换精度在米级,对断面定位足够。注意:如果你只是做展示、不追求绝对精度,统一用GCJ02叠加到国内在线地图反而更省事;但如果要做空间统计、缓冲区分析,建议统一到CGCS2000或WGS84。别混着用,混用就是自找麻烦。
2.3 23个断面在海河流域的空间分布特征
天津地处海河流域下游,河流密集,国控断面覆盖了主要干流和重要支流。23个点从空间上看,大致分布在蓟运河、潮白河、北运河、永定河、大清河、子牙河、海河干流以及渤海湾入海口附近。做分布分析时,可以按行政区或流域分组统计,看看哪些区域断面密度高、哪些河段覆盖稀疏。这一步不需要复杂代码,用pandas分组计数加一个简单的地图可视化就能看出门道。
# 按所在区域统计断面数量 if "所在区县" in df.columns: print(df.groupby("所在区县").size().sort_values(ascending=False)) # 按河流统计 if "河流名称" in df.columns: print(df.groupby("河流名称").size().sort_values(ascending=False))分组统计的意义在于:如果某个区县断面数为零,要么是数据缺失,要么是该区确实没有国控点位,后续分析时心里有数。参数上没什么特别的,关键是确认分组字段存在且没有空值。如果字段名不同,按实际列名调整。
3. 把坐标落到地图上:从CSV到可视化与空间分析的完整链路
数据检查完、坐标系确认好,接下来就是让它“落地”——变成地图上的点、变成可查询的空间对象、变成能参与分析的图层。这一章按操作顺序走一遍:先做基础可视化验证点位是否正确,再转成GIS格式做空间查询,最后叠加河流水系做缓冲区分析。每一步都给出可复现的代码和参数说明。
3.1 用folium快速验证点位是否落在河道上
验证坐标最直观的办法就是叠到地图上。folium基于Leaflet,几行代码就能出交互地图,适合快速检查。注意:folium默认底图是OpenStreetMap,国内访问可能慢,可以换成天地图或高德底图,但高德底图是GCJ02坐标系,如果你的数据是WGS84,需要先转换,否则点位会偏移。
import folium import pandas as pd df = pd.read_csv("tianjin_sections.csv", encoding="utf-8") # 以第一个断面为中心创建地图,缩放级别12 m = folium.Map(location=[df["纬度"].mean(), df["经度"].mean()], zoom_start=10) # 逐个添加标记 for _, row in df.iterrows(): folium.Marker( location=[row["纬度"], row["经度"]], popup=row.get("断面名称", "未知断面"), tooltip=row.get("断面名称", "") ).add_to(m) # 保存为html,浏览器打开即可查看 m.save("tianjin_sections.html")这段代码的逻辑是:以所有点的平均位置为中心,逐个打标记,弹出框显示断面名称。参数上,zoom_start控制初始缩放,10大概能看到全市范围。保存成html后直接用浏览器打开,逐个点检查是否落在河道上。如果发现整体偏移,回到2.2节处理坐标系。这一步是“后悔药”,宁可现在多花五分钟检查,也别等分析做完了才发现点位全偏了。
3.2 转成GeoJSON与Shapefile:给GIS软件和空间数据库用
folium适合快速看,但要做正经的空间分析,得转成GIS标准格式。GeoJSON适合Web端和轻量分析,Shapefile适合ArcGIS、QGIS等桌面软件。用geopandas可以一次性搞定两种格式的导出。
import geopandas as gpd from shapely.geometry import Point # 假设df已经读取,且经纬度字段为"经度""纬度" geometry = [Point(xy) for xy in zip(df["经度"], df["纬度"])] gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326") # 导出GeoJSON gdf.to_file("tianjin_sections.geojson", driver="GeoJSON") # 导出Shapefile gdf.to_file("tianjin_sections.shp", encoding="utf-8") # 如果要做缓冲区分析,先投影到米制坐标系 gdf_utm = gdf.to_crs(epsg=32650) # 天津位于UTM 50N带 gdf_utm["buffer_1km"] = gdf_utm.geometry.buffer(1000) gdf_utm.to_file("tianjin_sections_buffer.shp", encoding="utf-8")这里的关键参数是crs。EPSG:4326是WGS84经纬度,适合存储和展示;EPSG:32650是UTM 50N投影坐标系,单位是米,适合做距离计算和缓冲区。buffer(1000)表示生成1公里缓冲区,用于排查断面周边1公里内的污染源或土地利用类型。注意:Shapefile对字段名长度有限制,中文编码也可能出问题,导出时加encoding="utf-8"能解决大部分乱码。如果字段名超过10个字符,geopandas会自动截断,导出后检查一下属性表。
3.3 叠加河流水系做缓冲区统计的实操
有了断面点和缓冲区,下一步就是叠加河流水系或土地利用数据做统计。比如:统计每个断面1公里缓冲区内有多少建设用地、多少耕地,或者距离最近排污口有多远。这一步需要额外的空间数据,常见来源是OpenStreetMap水系或本地国土空间规划数据。操作逻辑是空间连接(spatial join)。
# 假设已有土地利用面数据 landuse.shp landuse = gpd.read_file("landuse.shp") # 确保坐标系一致 landuse = landuse.to_crs(gdf_utm.crs) # 空间连接:找出每个缓冲区内包含的土地利用类型 joined = gpd.sjoin(gdf_utm, landuse, how="left", predicate="intersects") # 按断面统计各土地利用类型面积 area_stats = joined.groupby(["断面名称", "土地利用类型"]).size().unstack(fill_value=0) print(area_stats)这段代码的核心是sjoin,predicate="intersects"表示只要缓冲区与地块有交集就计入。参数上,how="left"保证即使缓冲区内没有地块,断面也会保留。统计结果可以导出成表格,用于后续相关性分析。常见坑是坐标系不一致导致空间连接结果为空,所以每次sjoin之前都确认一下两个图层的crs是否相同。
4. 避坑与排查:坐标数据落地时最容易翻车的五个地方
这一章集中说踩坑。坐标数据看着简单,但实际用起来,坐标系、字段、编码、精度、边界这五个地方最容易出问题。每条按“现象→原因→解决”写,都是血泪经验。
4.1 点位整体偏移几百米
现象:把断面点叠到地图上,所有点都偏离河道,方向一致。原因:坐标系不匹配,最常见的是数据为GCJ02却按WGS84展示,或反之。解决:按2.2节的方法判断并转换。快速判断技巧:如果偏移方向大致朝同一方向,基本就是加密偏移;如果偏移无规律,可能是数据本身有问题。
4.2 断面数量对不上,少了几个
现象:读取后只有20个或21个断面,不是23个。原因:CSV中存在空行、分隔符不一致、或坐标缺失导致被dropna过滤。解决:读取时加df = pd.read_csv(..., skip_blank_lines=True),然后检查df["经度"].isnull().sum(),定位缺失行。如果是分隔符问题,用sep="\t"或engine="python"尝试。
4.3 中文断面名称乱码
现象:断面名称显示为“æµ·æ²³”之类的乱码。原因:文件编码与读取编码不一致,常见于Windows下生成的CSV。解决:读取时尝试encoding="gbk"或encoding="gb18030",导出时统一用utf-8。如果已经乱码,可以用df["断面名称"] = df["断面名称"].str.encode("latin1").str.decode("utf-8")尝试修复,但不保证100%成功,最好从源头解决。
4.4 缓冲区分析结果为空
现象:sjoin之后没有任何匹配记录。原因:两个图层坐标系不一致,或者缓冲区单位错误(比如在经纬度坐标系下直接buffer(1000),实际缓冲了1000度)。解决:先to_crs到米制坐标系再做buffer,sjoin前打印两个图层的crs确认一致。
4.5 导出Shapefile后字段丢失
现象:导出的shp文件属性表里字段少了或名称被截断。原因:Shapefile格式限制字段名最长10个字符,且不支持某些数据类型。解决:导出前重命名字段为短英文名,或改用GeoPackage(.gpkg)格式,没有字段名长度限制,编码也更稳定。
5. 进阶用法:用23个断面做水质空间插值与趋势面分析
23个点不算多,但做空间插值够用了。如果你手头还有这些断面的水质监测数值(比如氨氮、总磷、高锰酸盐指数),可以把坐标和数值结合起来,做反距离加权(IDW)或克里金插值,生成天津地表水水质空间分布图。这一步的价值在于:把离散的监测点变成连续的面,直观看出污染空间趋势。
具体操作上,用pykrige或gstat(R)做克里金,用scipy.interpolate做IDW。下面是一个IDW的简化实现:
import numpy as np from scipy.spatial.distance import cdist def idw_interpolation(x, y, values, grid_x, grid_y, power=2): """ x, y: 已知点坐标数组 values: 已知点数值数组 grid_x, grid_y: 待插值网格 power: 距离幂次,常用2 """ grid_points = np.column_stack([grid_x.ravel(), grid_y.ravel()]) known_points = np.column_stack([x, y]) distances = cdist(grid_points, known_points) # 避免除零 distances[distances == 0] = 1e-10 weights = 1.0 / (distances ** power) interpolated = np.sum(weights * values, axis=1) / np.sum(weights, axis=1) return interpolated.reshape(grid_x.shape)参数power控制距离衰减速度,power越大,近处点影响越强。一般用2。注意:IDW在点稀疏区域会产生“牛眼”效应,克里金更平滑但计算量大。23个点做全市插值,边缘区域外推要谨慎,最好加一个研究区边界掩膜,只保留市域范围内的插值结果。
验证插值效果可以用留一交叉验证:每次去掉一个点,用其余22个点插值,比较预测值与实测值。如果误差太大,说明点太稀疏或空间自相关性弱,插值结果只能做趋势参考,不能当精确值用。
从那以后我每次拿到坐标数据,都强制先走一遍“读字段→查缺失→叠地图→转投影”的流程,确认无误再往下做分析。希望帮到你。
本文还有配套的精品资源,点击获取