山东省17地市矢量图获取与处理:从坐标系转换到可视化落地

发布时间:2026/10/19 17:15:12
山东省17地市矢量图获取与处理:从坐标系转换到可视化落地
简介这份面向地理信息系统GIS学习者与科研人员的山东省17地市行政边界矢量数据包涵盖济南、青岛、淄博等全部17个地级市边界可用于地图制作、空间查询、区域规划与GIS教学实验。压缩包共12个文件包括.shp几何数据、.dbf属性表、.prj投影坐标、.shx索引、.sbn/.sbx空间索引等Shapefile必备组件另有.lyr图层样式和.mxd地图文档方便在ArcGIS中直接加载使用整体仅32KB结构清晰。已有1414人学习下载适合初步接触GIS空间操作或急需山东地市边界的用户。借助该数据可练习投影转换、叠加分析、缓冲区生成等核心方法配合QGIS或ArcGIS完成从数据清洗、坐标匹配到专题图输出的完整流程若结合人口、经济属性数据还可制作分级统计图用于城市规划、交通研究等实际场景。1. 山东省17地市矢量图做数据可视化前需要先搞清楚的底图问题很多开发者在做山东省相关的数据可视化时第一反应是上网搜“山东省地图shp”或“山东地市GeoJSON”下载下来却发现图和数据对不上或者地图在页面上偏移了几百米。这个标题里“山东省17地市”不是过时的表述而是指2019年莱芜并入济南之前山东省的行政区划口径也就是济南、青岛、淄博、枣庄、东营、烟台、潍坊、济宁、泰安、威海、日照、莱芜、临沂、德州、聊城、滨州、菏泽这17个地级市。直到今天不少统计年鉴、历史数据、存量业务系统仍然按17地市的口径出报表市面上的公开矢量图也有很多是17地市版本这就造成一个很现实的矛盾数据是17个地市地图却是16个地市图例和区域永远对不齐。这篇文章要解决的就是怎么拿到一份能直接用于生产环境的17地市矢量图把数据源选型、坐标系处理、边界修正到前端渲染这条路走通让读者不用再去试错。2. 矢量图从哪里来数据源选型与shp/GeoJSON/SVG的取舍2.1 先分清shp、GeoJSON、SVG三种格式背后的使用场景拿到“山东省17地市矢量图”这个需求时先别急着下载得想清楚这份数据最终要跑到哪里。如果是给GIS桌面软件用比如做空间分析、叠加统计那shapefileshp仍然是默认的交换格式几乎所有GIS工具都认如果是要嵌到Web前端做交互式地图GeoJSON是JSON的一种扩展浏览器原生就能解析配合Leaflet、MapLibre、ECharts都能直接渲染绝大部分前端问题都出在这里如果只是做一张静态的示意图插到PPT或者报告里SVG体积小、无限缩放不模糊是三个格式里最省心的。我一般会优先找shp或GeoJSON格式的原始数据而不是直接拿SVG原因是SVG丢掉了属性信息每个地市只剩一个路径和名字后续想做数据挂接、tooltip、统计着色都得重新加工。常见做法是到一些开放数据平台下载省级行政区划数据筛选出地市级边界再根据所需口径处理成17地市版本。下载之后第一件事不是打开看图形而是看属性表和坐标系这比图形本身更影响后续能不能用。2.2 用ogr2ogr把shapefile转成GeoJSON的最小命令如果你拿到的是一份shp格式的山东地市边界要转到Web前端用最直接的工具是GDAL自带的ogr2ogr命令行。它在QGIS安装目录里就有也可以单独安装GDAL环境。转换命令如下ogr2ogr -f GeoJSON -t_srs EPSG:4326 -lco COORDINATE_PRECISION6 \ shandong_17 cities.geojson shandong_cities_2020.shp这条命令做的事情是读取当前目录下的shandong_cities_2020.shp把坐标系转换为EPSG:4326WGS84经纬度输出成shandong_17cities.geojson坐标精度保留到小数点后6位。-lco COORDINATE_PRECISION6这个参数经常被忽略但非常关键默认GeoJSON输出坐标可能长达十几位小数文件体积随随便便翻一倍而6位小数已经足够表达米级以下精度体积却小得多前端解析快很多。2.3 数据质量初判属性字段、边界精度和更新时间的检查清单拿到一份来源不明的矢量图不要直接投入使用先做一个快速体检。我会依次检查四件事第一图层要素数量是不是17个地市多一个少一个都要弄清楚原因第二属性表里有没有地市名称字段名称是全称如“济南市”还是简称如“济南”这直接影响后面数据挂接的写法第三坐标系字段写在.prj文件里用文本编辑器打开就能看到常见的有GCS_WGS_1984、CGCS2000、GCS_Xian_1980这三种如果是后者一定要先转成WGS84或GCJ02再上线第四边界更新到哪一年2019年莱芜并入济南之后济南的边界已经变化如果拿的是2020年以后的数据17地市口径本身就不成立需要手动把莱芜的边界合并回济南或者反过来用2018年及以前的版本。检查要素数量和属性表可以用QGIS打开图层面板直接看也可以用下面这段Python脚本快速摸底import geopandas as gpd gdf gpd.read_file(shandong_cities.shp, encodingutf-8) print(要素数量, len(gdf)) print(字段列表, list(gdf.columns)) print(坐标系, gdf.crs) print(gdf[[name, adcode]].head(20))这段脚本用geopandas读取shp打印要素数量、字段名、坐标系和前20条记录的名称与行政区划代码。adcode是行政区划代码比如济南370100、青岛370200这是做数据挂接最可靠的关联键比名称字段稳定得多。如果数据里没有这个字段后续挂接只能用名称匹配而名称往往存在“济南”和“济南市”的差异处理起来很费劲。3. 坐标系与字段清洗让矢量图能直接对接业务数据的两个前置步骤3.1 识别坐标系先看.prj再用ogrinfo验证拿到矢量图后坐标系识别是第一步也是最容易翻车的一步。很多人下载数据后直接双击打开图形看起来正常就继续用了直到叠加到在线地图上才发现偏移几百米甚至几十公里。识别坐标系最靠谱的方式不是靠肉眼看也不是靠数据来源页面的描述而是看两个东西shp同级的.prj文件内容以及用ogrinfo输出的实际坐标系字符串。.prj文件在Windows上用记事本就能打开里面是一段WKT文本开头几个词已经说明问题。如果是PROJCS开头的说明是投影坐标系单位是米直接转WGS84时必须先做反投影如果是GEOGCS开头的说明是经纬度坐标系但要进一步看DATUM常见的有坐标系DATUM关键词适用范围叠加在线底图时的表现WGS84WGS_1984GPS、国际通用直接可用偏差极小GCJ02国测局火星坐标国内互联网地图高德、腾讯直接叠加会偏移约百米级BD09百度坐标百度地图直接叠加偏移更明显需二次转换CGCS2000China_2000国家测绘标准与WGS84差异很小可近似通用ogrinfo命令可以快速确认坐标系不用打开GIS软件ogrinfo -al -so shandong_cities.shp输出信息里有一个Coordinate System段落会明确显示坐标系的名字。如果这里写的是WGS 84说明数据本身已经是经纬度坐标如果是Pulkovo 1942这类冷门坐标系反而要小心说明数据有历史来源问题建议直接放弃这份数据另找来源。3.2 字段精简与地市名称规范化一条Python脚本搞定一份从公开渠道下载的行政区划数据字段往往很冗余常见的有objectid、shape_Leng、shape_Area、name、adcode、childrenNum等等真正用得到的其实就那么两三个。字段太多会造成两个问题一是GeoJSON文件体积变大二是前端代码写数据映射时容易把字段名搞错。我一般会做一个精简只保留地市名称和行政区划代码两个字段必要时加一个英文名或拼音字段用于样式类名。另一个必须处理的坑是名称口径。有些数据源里写“济南市”有些写“济南”有些甚至写“山东省济南市”如果业务数据里的城市名是“济南”而地图属性是“济南市”前端做数据匹配的时候就有两种情况对不上。统一的做法是在清洗阶段就规范成同一个口径我这里统一使用不带“市”的短名称因为业务数据表里短名称出现频率更高。import geopandas as gpd gdf gpd.read_file(shandong_17.shp, encodingutf-8) # 精简字段只保留名称和行政区划代码 keep_cols [col for col in [name, adcode] if col in gdf.columns] gdf gdf[keep_cols [geometry]] # 统一地市名称去掉“市”后缀保持业务表口径一致 name_col name if name in gdf.columns else None if name_col: gdf[city] gdf[name_col].astype(str).str.replace(市, , regexFalse) gdf.drop(columns[name_col], inplaceTrue) # 统一编码输出 gdf.to_file(shandong_17_clean.shp, encodingutf-8)这段代码做的事有三件筛选保留指定字段把名字里的“市”字去掉生成新的city字段最后用UTF-8编码输出成新shp。值得说明的是GDAL在写出shp时字符编码默认不是UTF-8如果不指定中文字段名在QGIS里显示正常但到了PostGIS或前端就可能乱码所以encodingutf-8一定要写。3.3 能多碎的边界就别开多高精度GCJ02偏移问题的取舍坐标系还有一层隐藏的问题要做取舍。如果你的矢量图最终要叠加到高德、腾讯这类互联网地图上直接用WGS84坐标会看到一个熟悉又恼人的现象地图上的边界线和卫星影像里的道路、河流对不上整体偏移几十到几百米这就是“火星坐标”偏移。GCJ02是国家测绘部门为了安全做过加密处理后的坐标系互联网地图服务商普遍采用而下载到的矢量图绝大多数是WGS84或CGCS2000这就造成了偏移。处理这个偏移有两条路线。第一条路线是找一份已经是GCJ02的山东边界数据但公开渠道里这类数据相对少见且来源不稳定第二条路线是自己在代码里做坐标纠偏把WGS84坐标偏移到GCJ02。用Python的话核心代码如下import math def wgs84_to_gcj02(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这个公式是业内公开的近似纠偏算法精度在亚米级到米级满足可视化用途。要注意的是纠偏不可逆直接反向计算会引入误差如果后续数据要上传到WGS84的服务端需要走GCJ02转WGS84的另一套补偿算法。另外如果项目不需要叠加在线底图只是自己用ECharts画静态区域图是否存在偏移无所谓不需要做这一步纠偏。4. 几何修复与边界简化交付前必须过的一道关4.1 用shp2pgsql与PostGIS做拓扑修复的流程从公开渠道下载的边界数据几何质量参差不齐常见问题包括自相交多边形、相邻面之间存在微小缝隙、重复顶点、多面体要素内部有悬空部分。这些几何问题在QGIS里肉眼很难发现但在PostGIS做空间运算或者前端渲染时就会冒出来面积计算不准、拓扑关系判断失败、地图上出现细线或缺口。目前比较可靠的修复流程是先导入PostGIS用ST_IsValid检测再用ST_MakeValid修复。具体做法先用shp2pgsql将shp导入数据库shp2pgsql -s 4326 -g geom -W utf-8 shandong_17_clean.shp shandong_17 \ | psql -U postgres -d gisdb这里的-s 4326指定源数据坐标系为WGS84-g geom指定几何列名为geom-W utf-8指定属性编码。导入后执行SQL检查无效几何SELECT city, ST_IsValidReason(geom) AS reason FROM shandong_17 WHERE NOT ST_IsValid(geom);如果查询有返回结果就说明这份数据存在拓扑问题需要用更新语句修复UPDATE shandong_17 SET geom ST_MakeValid(geom) WHERE NOT ST_IsValid(geom);ST_MakeValid会根据不同情况采取不同策略有些是拆分多部件有些是补洞修复后需要再跑一遍ST_IsValid确认全部通过。修复完的几何再导出成shp或GeoJSON才算是一份“干净”的底图。如果数据库条件不具备QGIS里也提供“修复几何”的处理工具但大批量处理时PostGIS的稳定性要好得多。4.2 用mapshaper简化边界体积下降与形状保持的平衡点边界简化是矢量图处理里最讲究手感的一步。原始行政边界数据往往非常精细一个地市的面可能有几十万个点整份17地市GeoJSON体积可能到几十MB浏览器渲染起来非常吃力而肉眼看不出差别。常见的简化工具是mapshaper它对大众用户最友好的地方是有一个在线版本直接把GeoJSON拖进去就能操作也提供了命令行。我常用的是命令行版本参数控制更精确npx mapshaper shandong_17_clean.geojson \ -simplify 15% keep-shapes \ -filter-fields city,adcode \ -o formatgeojson precision0.0001 shandong_17_simplified.geojson这条命令的核心是-simplify 15%意思是保留约15%的顶点数量keep-shapes参数保证简化后每个地市仍然保持原有的面状形态不会出现面积剧烈缩小甚至某个小多边形直接消失的情况。precision0.0001把坐标小数位限制到4位进一步压缩体积。简化之后务必做一次目视检查把简化前后的图叠在一起看重点关注烟台、威海这种海岸线曲折的地市以及临沂、潍坊这种边界犬牙交错的区域这些地方最容易因为简化过度而丢失视觉细节。4.3 修复后验证面积、周长和相邻关系的回归检查几何修复和简化做完不能直接拿去用需要做一轮回归验证。我一般看三个指标每个地市简化前后的面积偏差、周长偏差、以及相邻地市是否还共享边界。面积和周长可以用PostGIS直接算SELECT city, ST_Area(geom) / 1000000 AS area_km2, ST_Perimeter(geom) / 1000 AS perimeter_km FROM shandong_17 ORDER BY area_km2 DESC;简化前后各跑一遍对比每个地市面积变化比例建议控制在2%以内。面积差得太多说明简化参数太小顶点保留不够。相邻关系检查用QGIS打开简化后的图放大检查两个地市边界线之间是否出现白缝白缝意味着简化过程中相邻多边形的公共边界被各自独立处理后续做点击高亮或邻接分析时会出问题。遇到这种情况要么用拓扑简化模式要么换一份边界本身就很干净的数据源不要自己去修这种缝隙成本极高还不一定修得干净。5. 常见坑与排查从地图错位到属性对不上的五个实战问题5.1 地图整体偏移坐标系识别错导致的位置漂移现象地图上的地市边界与在线底图高德或天地图叠加时所有边界整体偏向东南方向几百米甚至数公里济南跑到章丘位置上了。原因绝大多数情况下是坐标系没有统一。下载的数据是CGCS2000坐标系而在线底图是GCJ02或WGS84直接叠用当然错位。有些时候数据本身标注是WGS84实际却是CGCS2000两者同为地心坐标系且框架接近一般人肉眼看不出来叠加在线图后问题才暴露。解决先用ogrinfo确认数据实际坐标系再确认目标底图坐标系如果是高德系底图必须做WGS84到GCJ02的偏移转换。如果偏移量在百米量级且方向稳定说明数据是WGS84但需要纠偏如果偏移量达到数公里往往说明数据是投影坐标系被人为改成了经纬度这种数据无法通过简单纠偏修复需要重新找到原坐标系做正确转换。5.2 边界出现锯齿或丢失岛屿简化参数过大的隐患现象mapshaper简化之后青岛的沿海边界出现明显锯齿状折线烟台部分近海岛屿从图上消失济南和德州之间原本清晰的边界线变得模糊。原因简化本质上是减少顶点数量参数设置过大时算法会把一些短边直接拉直曲折海岸线因此失真面积很小的岛屿因顶点太少被合并或过滤。解决回到-simplify参数把15%改成30%甚至50%重新生成对比体积和显示效果。要注意的是不要单独用一个参数处理全省精细做法是区分沿海地市和内陆城市分别设置不同简化率但这个操作复杂度高一般项目不值得我更推荐找一份原始精度本身就适中的边界数据避免初始顶点过多。5.3 属性表里“济南”和“济南市”对不上名称规范化的细节现象前端用业务数据里的“济南”字段去匹配地图数据的“city”字段控制台里大量undefined地图上十几个地市没有数据渲染。原因地图数据里地市名称带了“市”后缀或者用全称如“山东省济南市”而业务数据是简称字符串匹配失败。解决在数据清洗阶段就统一口径。我的习惯是统一去掉“市”字用“济南、青岛”这种短名称如果地图数据源已经无法改就在前端匹配时做一个兼容函数把两边同时去掉“市”和省份前缀再比较。更稳妥的是用adcode做关联键不使用名称匹配因为行政区划代码在国家和省市层面有统一标准不存在简称和全称的差异。5.4 图层面板里显示空白几何错误与数据范围的双重排查现象把GeoJSON加载到Leaflet或MapLibre里整个地图区域空白没有报错控制台也干净但就是什么都看不到。原因排除代码逻辑问题后最可能是两种。其一GeoJSON里几何坐标是投影坐标例如数字很大几百万量级而渲染引擎默认按经纬度处理导致图形画在很远的地方视野里看不到其二几何存在严重错误多边形坐标自相交或数据格式不规范渲染引擎无法生成有效路径。解决先打开GeoJSON文件看坐标范围如果坐标值超过180或小于-180基本可以断定没有正确转为WGS84。再用QGIS加载同一份文件如果QGIS正常显示说明问题出在前端渲染配置重点检查坐标系是否被前端重新投影如果QGIS也空白就要用ST_IsValid检查几何正确性逐项修复。5.5 预览正常但导出PDF缺块渲染引擎与图层裁剪的差异现象在QGIS里预览样式完全正常导出PDF后某些地市只显示一半另一半被裁掉或者整块变成空白。原因这往往不是数据问题而是规模与裁剪设置问题。QGIS的打印布局默认会按画布范围裁剪输出如果图层设置里的“绘制效果”启用了简化绘制导出时也沿用简化规则高DPI导出时边界复杂的地市会被错误简化到无法绘制。解决在打印布局中关闭“简化几何”选项同时打开“始终使用制图渲染器”设置重新导出。如果是命令行工具做批量出图调整渲染器的DPI阈值或把简化阈值设置为0。这个问题本身和数据关系不大但很多人排查了半天矢量图最后发现是渲染配置这里记录一笔。6. 进阶从静态文件到可交互的17地市地图服务静态的GeoJSON只能满足展示类需求如果要做县级下钻、点击查询地市详情、按指标动态着色建议直接切成矢量瓦片发布。常见的组合是tippecanoe切片加MapLibre GL渲染这个方案比一次性加载全量GeoJSON要成熟很多特别是17地市全图加几十个县的业务场景下加载速度和交互流畅度都有明显差距。用tippecanoe把GeoJSON切到Zoom 0到7级的矢量瓦片命令大致是这样tippecanoe -o shandong_17.mbtiles -Z 0 -z 7 \ --drop-densest-as-needed \ --named-layershandong_17 \ shandong_17_simplified.geojson-Z 0 -z 7控制最小和最大缩放级别地市级边界到7级足够匹配常见的Web地图比例尺--drop-densest-as-needed会在切片体积超限时优先丢弃最密集的点保证瓦片大小可控。切好的mbtiles可以用tileserver-gl发布成矢量瓦片服务前端用MapLibre GL的addSource加载绑定city字段做数据驱动的颜色映射这样数据更新时只需要替换GeoJSON重新切片前端代码不用动。最后说一个我自己的教训不要把坐标纠偏和边界简化的代码随手丢在临时脚本里这种数据处理的参数组合坐标系、简化率、字段口径非常容易忘记建议在项目仓库里单独建一个process目录把每一步的输入输出、命令参数、验证结果以README的形式固定下来。等三个月后领导让你换一套16地市口径时你会感谢自己当时多写了那几行注释。希望帮到你。本文还有配套的精品资源点击获取