从历法到GIS:探索天地规律的技术演进与应用——经纬绘山河

文章目录[隐藏]

注意

  1. 本文是教程,不是学术论文,也不是研究报告,所以语体会尽可能通俗易懂。

  2. 可能需要一定的电脑硬件以支持操作,建议16G内存+100G以上磁盘空间(问就是有时候数据量比较大)。

  3. 有问题欢迎随时讨论,毕竟是自己的经验。

  4. 我事先说明,很多东西可能不是新的东西,也许在哪都可能听到过,也许或多或少有人提过,要感谢前人的工作。

  5. 感谢AI,没有gemini的帮助,我无法做到这些。里面的代码都是参照,请各位让AI好好改改。

  6. 我后面用的qgis版本是3.44

    QGIS版本3.44.0-Solothurn
    QGIS代码版本5d9ba037df1
     
    Qt版本6.8.1
    Python版本3.12.11
    GDAL版本3.11.0 — Eganville
    PROJ版本9.6.2
    EPSG注册数据库版本v12.013 (2025-05-26)
    GEOS版本3.13.1-CAPI-1.19.2
    SQLite版本3.46.1
    PDAL版本2.9.0
    PostgreSQL客户端版本17.3
    SpatiaLite版本5.1.0
    QWT版本6.3.0
    QScintilla2版本2.14.1
    操作系统版本Windows 10 Version 22H2
     
    已激活的Python插件
    nominatim1.5.0
    qgis-maptiler-plugin3.4.2
    quick_map_services0.21.2
    tianditu-tools0.5.3
    db_manager0.1.20
    grassprovider2.12.99
    MetaSearch0.3.6
    processing2.12.99
  7. 因为书籍尚未完全出版,所以没有配上具体的绘制操作和效果展示。望诸位见谅。

缘起

GIS其实大家都不陌生,但是听过和自己能制作是两回事。笔者曾经参加过学术地图项目,也给自己的老师制作过其他的地图,可以说在使用软件方面有一定的心得体会。前段时间群内老师多次提起,笔者也觉得可以调查研究一下最近的进展之类的,顺便整合一下操作。因为天相对于地是复杂的,所以璇玑谱星辰那篇等我再琢磨一二,先写经纬绘山河。

理论基础

其实文化地理学或者音乐地理学的相关知识什么的都有,我让gemini调查了,感兴趣的请直接访问
深度探索文化地理学:基础、概念、方法与前沿
音乐地理学:学科综合研究报告
中国音乐地理学研究现状:理论视野与本土实践
那么,随着思考的深入,我们脚下的土地,或者说被我们写了几千年,描述了几千年的客观地理存在,到底是意味着什么呢?
笔者的一点拙见是:
单独论这些事物,会变成地理学。所以我们要结合人的角度来思考这个问题。
在马克思主义原理中是这样说的:自然地理环境是人类社会存在和发展的永恒的、必要的条件。

思路

在软件安装之前,我想说一下思路,具体问题肯定有具体的研究方式,但是别忘了还有很多共性的东西可以谈谈。

  1. 技术是用来辅助研究的,基本上是辅助作用,至于研究深度怎么体现是自己的事情。
  2. 技术不是万能的,而且即使是有AI也不是万能的,但大部分时候还是有用的。
  3. 围绕着问题去查数据,去推理分析,在大数据和可视化的情况下可以发现很多东西。就那种一下子掌握了全局,很多线索能合并,世界线收束。
  4. 我尽可能用的都是免费服务,就我看来free的基本上能满足需求了。付费我主要是之前购买了一些教程拿来看看,别的没啥。
  5. 现在就可以说,首先是围绕问题去查找数据,比如说研究茶马古道,那么肯定是滇、川、藏等的山川河流乃至路网都要有。暂时不考虑大规模地质变化和行政区域变化。接着在软件中呈现,通过地名查找坐标进行定位,地名部分就自己处理了,可以参考周边山水辅助定位。然后就是具体问题具体分析,基本上就大差不差。
  6. 其实大部分制图问题都可以在QGIS上面解决,实在是解决不了可以用PS去做标注和其他的内容。
  7. 暂时先从路径的角度规划,具体的分布情况有待后续增补。

qgis下载安装

此处仅提供大概的例子,差不多一样就行。

官网走起,我默认大家英文看得懂啊,这个后续有中文的。

https://qgis.org/download/

file

然后选择LTS(长期服务版)较为稳定。
file

点击下载,得到MSI
file

一路跟着向导安装就行了。
file

file

记得看情况改一下路径,这个东西有点大。
file

file

file

qgis基础界面介绍

(让我们的gemini来讲一下)

file

我们来看一下 QGIS 的界面。把它想象成一个强大的地图工作室,里面有各种各样的工具和区域,可以帮助你处理地理数据。

从你提供的图片来看,这主要是 QGIS 的工具栏菜单栏部分,背景是深色主题。

我们就从上到下,简单说说这些部分是干什么的:

  1. 最上面是“菜单栏” (Menu Bar):

    • 图片中显示的是中文菜单,比如 “工程(J)”、“编辑(E)”、“视图(V)”、“图层(L)”、“设置(S)”、“插件(P)”、“矢量(O)”、“栅格(R)”、“数据库(D)”、“网络(W)”、“帮助(H)”、“HDMGS”、“数据制图(C)”、“帮助(H)”。
    • 作用: 这是所有功能的“总目录”。几乎所有 QGIS 的功能和设置都可以从这里找到。比如:
      • 工程(J): 打开、保存、新建地图项目。
      • 编辑(E): 对地图要素进行修改、复制、粘贴等操作。
      • 视图(V): 控制地图的显示方式,比如放大、缩小、平移,或者显示/隐藏某些面板。
      • 图层(L): 添加、管理、设置地图图层(比如一张卫星图、一张行政区划图都是一个图层)。
      • 设置(S): 进行 QGIS 软件本身的各种配置。
      • 插件(P): QGIS 的一大特色就是有很多扩展插件,可以在这里管理和使用它们,增强软件功能。
      • 矢量(O) / 栅格(R): 这是处理两种主要地理数据类型的工具集。矢量数据通常是点、线、面(比如一个城市点、一条河流线、一个国家面),栅格数据通常是像卫星影像、地形图这样的像素格网数据。
      • 数据库(D) / 网络(W): 用于连接和处理存储在数据库或网络服务中的地理数据。
      • 帮助(H): 获取帮助文档和软件信息。
      • HDMGS / 数据制图(C): 这些可能是特定插件或者定制化添加的菜单。
  2. 菜单栏下面是“工具栏” (Toolbars):

    • 图片中显示了好几行密密麻麻的图标。
    • 作用: 这些图标其实就是从菜单栏里挑出来的一些常用功能的“快捷方式”。用鼠标直接点击图标,就能快速执行某个操作,比从菜单里一层层找要方便得多。
    • 举例(根据常见的 QGIS 图标猜测):
      • 第一行靠左边: 可能会有新建工程、打开工程、保存工程的图标(像文件夹、磁盘)。
      • 放大/缩小/平移工具: 像放大镜、手掌一样的图标,用来在地图上漫游。
      • 选择工具: 用来选中地图上的某个要素。
      • 信息查询工具: 点击地图上的要素,查看它的属性信息。
      • 测量工具: 测量距离、面积等。
      • 添加图层工具: 快速添加不同类型的地理数据。
      • 还有很多很多... 每个图标都有特定的功能,把鼠标悬停在图标上通常会显示它的名称或提示。
  3. 图片底部(部分可见):

    • 状态栏 (Status Bar): 通常在最底部,会显示当前鼠标在地图上的坐标、比例尺、投影信息等。图片中只露了一点点,左下角可能有一些坐标或比例尺信息,右下角可能有进度条或消息提示。
    • 浏览器面板/图层面板 (Browser/Layers Panel): QGIS 主要的工作区域通常还会包含这些面板。浏览器面板可以让你方便地浏览电脑上的地理数据文件;图层面板则用来显示和管理当前地图项目中加载的所有图层。图片中左下角“浏览器”和“最近工程”的字样,暗示了这些面板的存在,但它们的主体内容没有显示出来。

总结一下,QGIS 的界面就像一个工作台:

  • 菜单栏是你的工具总清单。
  • 工具栏是你摆在手边的常用工具。
  • 地图画布 (Map Canvas) (图片中没有完全显示,但这是最重要的部分) 是你真正绘制和查看地图的地方。
  • 各种面板 (Panels) (如图层面板、浏览器面板) 帮助你组织和管理你的数据和图层。

刚开始接触可能会觉得图标很多,功能复杂,但别担心!通常我们只会用到其中的一部分功能。多点点,多试试,结合具体的教程和任务,很快就能上手了。记住,把鼠标悬停在图标上,通常会有提示告诉你这个按钮是干什么的。

希望这个解释对你有帮助!

91卫图助手下载与安装

这个东西后面有好玩的,主要是有时候QGIS的OSM找不到坐标你可以用这个找。

file

可以看出,很方便的定到点位了,这条线路想必大家也很熟悉,茶马古道的滇藏线。

这个是官网 https://www.91weitu.com/

点击下载安装即可

file

奥维互助地图下载与安装

跟上面一样去官网下载安装。
https://www.ovital.com/
这个东西主要是为了路径导出,就是导出导航路线。坐标系的问题一会再讲。

file

知识点

坐标系

简单来说,这三个坐标系可以理解为针对不同使用范围和目的的“地址编码系统”。

核心区别:一个国际通用,两个中国专用

  • WGS84 (世界大地测量系统1984): 这是国际标准,可以把它想象成全球通用的“世界语”。你手里的GPS设备、Google地球的卫星图,以及绝大多数国际通行的地图服务,默认使用的都是这个坐标系。它的主要特点是无偏差、全球统一

  • CGCS2000 (2000国家大地坐标系): 这是中国的官方标准,可以看作是为中国量身定制的一套高精度“普通话”。它在技术上和WGS84非常非常接近,两者在大多数民用场景下的差别可以忽略不计(厘米级别)。但在需要高精度的专业测绘、国土规划等领域,必须使用这个国家法定坐标系。

  • GCJ-02 (国家测绘局02号标准): 这是在中国大陆地区强制使用的地图加密坐标系,俗称“火星坐标系” 👽。它是在WGS84的基础上,出于安全考虑,通过一个算法加上了随机的偏移。这就好比是把标准的WGS84地址“加密”了一下。所有在中国大陆公开发行的电子地图,比如高德地图、腾讯地图等,都必须使用这种加了密的坐标。


相同与不同

特征WGS84 (EPSG:4326)CGCS2000GCJ-02 (火星坐标系)
性质全球标准,无偏移中国官方标准,高精度加密偏移坐标
与WGS84关系-极其相似,可视为兼容基于WGS84加密,有几百米偏移
主要区别基准与CGCS2000差异极小有显著的人为偏移

一个形象的比喻:

  • WGS84 是你的真实家庭住址。
  • CGCS2000 是在派出所登记的官方户籍地址,和你的真实住址几乎完全一样。
  • GCJ-02 是为了保护你的隐私,给快递员的一个“加密”地址。快递员的导航系统(如高德地图)内置了解密程序,能找到你的家。但如果你把真实的WGS84地址直接给这个系统,它就会把你导到几百米外的地方。

适用范围

  • WGS84:

    • 全球范围的定位和导航(GPS原始数据)。
    • 与国外系统进行数据交换。
    • 在不涉及中国大陆地图展示的纯数据分析或研究。
    • Google地球的卫星影像层。
  • CGCS2000:

    • 中国专业领域的测绘、地质勘探、国土资源管理等。
    • 中国的官方地理信息数据发布。
    • 未来中国所有空间相关应用的统一基准。
  • GCJ-02:

    • 在中国大陆地区使用的所有公开地图服务
    • 开发面向中国用户的地图应用(如打车、外卖、导航App)。
    • 如果你要在高德、腾讯等地图上标记一个点,你必须提供GCJ-02坐标,否则位置会不准。

总结一下:如果你是在中国做地图应用开发,那么你打交道最多的就是GCJ-02。如果你处理的是原始GPS数据或者需要和国际接轨,就用WGS84。而CGCS2000则是国家层面统一大地坐标系的标准。

PS:如果用天地图的API,你可以输入WGS84,然后它返回的是CGCS2000,转换的问题晚点再讲。

天地图

我们来简要介绍一下“天地图”以及它提供的免费服务。

什么是天地图?

简单来说,天地图是“国家队”出品的在线地图服务

它是由中华人民共和国自然资源部主导建设的国家地理信息公共服务平台,也被称为“国家版”的Google地图。其目标是提供一个权威、统一、标准的在线地理信息服务,无论是对普通公众、政府部门还是商业开发者。

天地图的数据来源权威,更新及时,覆盖了从全球到全国,再到各省、市、县的详细地理信息。

天地图提供哪些强大的免费服务?

天地图的服务可以分为两大类:面向公众的在线地图服务和面向开发者的应用服务。这些服务大部分都是免费的。

1. 面向公众的免费服务 (直接访问网站使用)

当您访问天地图官网 (tianditu.gov.cn) 时,可以直接免费使用以下功能:

  • 在线地图浏览:提供多种地图模式,包括矢量电子地图、高清卫星影像图(部分地区分辨率优于1米)、三维地形和城市三维模型。
  • 地名搜索定位:输入地名或地址,快速在地图上找到位置。
  • 路线规划:提供驾车、公交等出行路线规划。
  • 实用工具:可以在地图上进行距离和面积的测量、添加标记、分享位置等。
  • 标准地图下载:提供标准的中国地图和世界地图图片,可免费下载用于新闻报道、书刊插图、广告背景等,使用时需标注审图号。

2. 面向开发者的免费服务 (API/SDK)

这是天地图非常有价值的部分,允许企业和个人开发者将官方的地图能力集成到自己的网站或App中。

  • 地图API服务

    • JavaScript API: 最常用的服务,开发者可以通过几行代码在自己的网页中嵌入天地图,并实现地图交互功能。
    • 提供多种底图服务: 包括矢量地图、影像地图、地形图以及对应的标注层,开发者可以自由切换和叠加。
  • Web服务API (HTTP接口)

    • 地理编码/逆地理编码: 将详细地址转换成经纬度坐标,或将经纬度坐标反向解析成地址。
    • 行政区划查询: 获取全国省、市、县的行政区划边界、中心点等信息。
    • 路径规划: 提供驾车、步行等路线规划能力。
    • 地名搜索服务: 在应用中集成地点搜索功能。

如何使用开发者服务?
开发者需要到天地图的开发者平台(lbs.tianditu.gov.cn)用个人信息免费注册并申请一个应用Key(密钥)。之后,在调用API时附上这个Key即可。对于普通用量,这些服务是完全免费的。


总结

总而言之,天地图是一个权威、开放、免费的国家级地理信息平台

  • 对于普通用户,它是一个功能丰富的在线地图,可以满足日常的查询、浏览需求。
  • 对于开发者和企业,它是一个强大的、低成本的地图解决方案,能够将国家级的权威地理数据和服务整合进自己的产品中,尤其适合需要在中国境内提供地图服务的应用。

百度地图

百度地图开放平台为个人开发者提供了一系列功能丰富的免费地图服务,涵盖了从基础的地图展示、定位、路线规划到地点检索等多种开发需求。个人用户在完成开发者认证后,即可在合理的配额范围内免费使用这些服务,为个人项目或应用开发提供了极大的便利。

核心免费服务及额度

对于个人开发者,百度地图API提供的免费服务主要分为 Web服务APISDK 两大类。

【额度可能有变,以百度公布为准。】

Web服务API 是通过HTTP/HTTPS请求调用的接口,适用于各类Web应用和后端服务。针对个人开发者,大部分Web服务API都提供了免费的日调用配额,具体如下表所示:

服务类别具体服务个人开发者免费配额(次/天)
地点检索服务地点检索(包括关键词、周边、多边形、详情检索等)6,000
地点输入提示6,000
路线规划服务驾车路线规划6,000
步行路线规划6,000
骑行路线规划6,000
公交路线规划6,000
地理编码服务地理编码(地址转坐标)6,000
逆地理编码(坐标转地址)6,000
坐标转换服务坐标转换60,000
IP定位服务IP定位100,000
其他服务天气查询100,000
时区查询6,000

值得注意的是:

  • 并发限制(QPS): 除了日调用总量的限制,大部分接口对每秒的请求次数(QPS)也有相应的限制,个人开发者通常在个位数到几十次不等,具体需查阅各API的详细文档。
  • 超出额度: 当日调用量超过免费额度后,服务将不可用。开发者可以根据自身需求,在百度地图开放平台购买更高的配额。

SDK(软件开发工具包) 方面,百度地图为Android和iOS平台提供了功能强大的SDK,便于在移动应用中集成地图功能。

  • 定位SDK: 在中国大陆境内,供非商业目的使用的Android定位SDK是完全免费的,并且没有调用次数和并发量的限制,定位精度可达米级。
  • 地图SDK、导航SDK等: 基础的地图展示、3D地图、个性化地图以及导航功能,在境内使用通常也是免费的。

个人开发者认证

要享受以上免费服务,用户需要在百度地图开放平台注册并完成个人开发者认证。认证过程通常需要提供真实的个人身份信息。完成认证后,即可创建应用(AK),获取调用API和使用SDK的权限。

适用场景

对于个人开发者而言,这些免费的服务和额度足以满足大部分非商业项目的需求,例如:

  • 个人网站或博客中嵌入地图;
  • 开发小工具或应用,如位置记录、路线规划助手等;
  • 学习和研究地理信息服务相关的开发技术。

总而言之,百度地图开放平台对个人用户持开放和支持的态度,通过提供稳定且功能全面的免费服务,极大地降低了个人开发者使用地图服务的门槛,是广大开发者进行创新和实践的有力工具。如需了解各服务的详细使用方法和最新的配额政策,建议直接访问百度地图开放平台官方网站以获取最准确的信息。

动手实践

坐标系

全部统一到WGS84坐标系,也就是EPSG4326。后面都是基于WGS84进行,而且这个也是最通用的。

file

找点

91卫图助手找点

从模糊地名到精确坐标——一套处理地理数据的通用思路

在最近的一个项目中,我需要将一份包含成百上千个地名的路线列表,转换成可以在GIS软件(如QGIS)中使用的精确地理坐标点。这个看似简单的任务,却是一趟充满挑战和发现的旅程。今天,我想把整个操作思路和最终的解决方案分享给大家。


第一阶段:最初的尝试与“偏移”的幽灵

我们的起点是一份包含大量地名的表格。最直观的想法,就是使用一个地理编码(Geocoding)API,将地名转换成经纬度。

思路:遍历每一个地名,调用地图服务的“地名搜索”接口,获取返回的坐标。

遇到的问题:很快,我们就发现了第一个“拦路虎”——坐标偏移。当把获取到的坐标点加载到地图上时,发现它们与真实的底图位置有明显的偏差。例如,“普洱”这个点,API返回的坐标可能落在了市区的某个街道,而不是我们所期望的市中心。

原因分析

  1. 地名歧义:API无法理解我们的真实意图。“普洱”究竟是指“普洱市”这个行政区,还是指某个叫“普洱”的茶馆?API只能做出它认为最合理的猜测。
  2. POI的精确性:地名搜索API的主要目的是找到兴趣点(POI),而不是精确的行政中心。因此,它返回的坐标随机性很大。

第二阶段:坐标系的“红鲱鱼”

当你看到坐标偏移时,GIS从业者的第一反应往往是:“是不是坐标系搞错了?” 国内常用的GCJ-02(火星坐标系)与国际通用的WGS-84之间确实存在偏差。

思路:在获取到API返回的坐标后,加入一个从GCJ-02到WGS-84的转换算法。

遇到的问题:我们惊讶地发现,加入转换后,坐标的偏移反而更大了!

原因分析(关键发现):这是一个非常重要的教训——“并非所有的偏移都是坐标系问题”。经过反复对比验证,我们最终确认,天地图API返回的坐标数据,其基准已经是CGCS2000,这个坐标系与WGS-84之间的差异极小,在应用层面完全可以近似看作是WGS-84。因此,我们之前画蛇添足的“转换”,反而是在正确的坐标上进行了错误的偏移。


第三阶段:最终的突破——“本地知识库 + API精确制导”混合策略

此处注意!不排除古代地名和现代地名重复的情况,请各位运行后重新校验所有地名【导入到qgis直接看就行】。确保符合自己的要求。同时也存在找不到的情况。
既然坐标系没问题,那问题的根源还是在于如何让API精确理解我们的意图。我们最终的解决方案,是一套结合了本地数据和API高级功能的混合策略。

核心思路

  1. 建立本地权威知识库:我们找到了一份国家标准的行政区划代码表(通常是Excel或CSV格式)。这是我们的“地面实况”,它为每一个行政区名称(如“普洱市”)提供了一个全国唯一的“身份证号”(行政区划代码)。【这个东西在天地图里面有,
    https://download.tianditu.gov.cn/download/xzqh/AdminCode.csv
    请自行下载】
  2. 实现分级、优先的搜索策略:我们不再对所有地名一视同仁,而是采用一套智能的、分优先级的“组合拳”。

最终工作流程:

  • 步骤一:优先尝试“精确制导”

    • 对于一个地名(如“普洱”),首先在本地的行政区划代码表中进行查找。
    • 如果能找到对应的代码(例如156530800),就证明它是一个标准的行政区。
    • 此时,我们调用一个更高级的API用法:在指定的行政区内进行POI搜索。我们向API发出指令:“请在行政区划代码为156530800的范围内,搜索名为‘市政府’或‘政府’的地点。”
    • 这个指令非常精确,API会返回“普洱市人民政府”的坐标,这正是我们需要的、最具代表性的点。
  • 步骤二:后备“模糊搜索”

    • 如果一个地名在本地的行政区划代码表中找不到(例如“洱海”、“奔子栏”这类自然地标,或“中甸”这类历史古称),程序会自动“降级”。
    • 此时,程序会切换回普通的“地名搜索”模式,向API发出指令:“请搜索名为‘洱海’的地点。”
    • 这个方法虽然不如前一种精确,但对于非行政区的POI来说,是唯一且最有效的方法。
  • 步骤三:直接使用,无需转换

    • 无论是哪种方法获取到的坐标,我们都将其直接视为WGS-84坐标使用,不再进行任何转换。

策略优势:这套“混合策略”兼顾了准确性和覆盖度。它优先确保了行政区坐标的精确性,同时又为那些特殊的、非行政区的地名提供了一套可靠的后备方案,最终让坐标获取的成功率和准确率都达到了最大化。


最终代码:一套通用的地理坐标获取脚本

下面是集成了上述所有思路的最终版Python脚本。它结构清晰,注释完备,你可以很方便地将其应用到你自己的项目中。

import pandas as pd
import requests
import json
import os
import time

# ===================================================================
# ==                      主程序开始                       ==
# ===================================================================
# --- 用户配置 ---
# 基础路径,存放你的输入文件
base_path = 'F:\\茶马古道全图\\' 
# 包含起始点和终点地名的原始路线文件
input_routes_file = os.path.join(base_path, '路线.csv')
# 国家标准的行政区划代码文件(包含省、市、县三级)
admin_code_file = os.path.join(base_path, 'xzqh2020-03.xlsx')
# 最终输出的点图层文件路径
output_coordinates_file = os.path.join(base_path, 'final_geopoints.csv')
# 你申请的天地图API Key
user_api_key = 你的API

try:
    # 1. 读取行政区划代码Excel,并创建一个包含层级信息的“智能地图”
    # 这个字典是本地的权威知识库,用于快速、准确地查找地名对应的代码和级别
    print(f"正在读取行政区划代码文件: {admin_code_file}")
    df_xzqh = pd.read_excel(admin_code_file)
    # 字典的值现在是一个包含代码和级别的小字典, e.g., {'普洱市': {'code': 156530800, 'level': 'city'}}
    name_info_map = {}
    for index, row in df_xzqh.iterrows():
        # 从最具体的县级开始,这样可以覆盖掉市级重名(如河北省的“霸州市”和四川省的“巴中市”)
        if pd.notna(row['县name']) and pd.notna(row['县gb']):
            name_info_map[row['县name']] = {'code': int(row['县gb']), 'level': 'county'}
        if pd.notna(row['市name']) and pd.notna(row['市gb']):
            name_info_map[row['市name']] = {'code': int(row['市gb']), 'level': 'city'}
    print("行政区划代码 '智能层级地图' 创建成功!")

    # 2. 读取路线文件,获取所有不重复的地名
    df_routes = pd.read_csv(input_routes_file)
    unique_locations = pd.concat([df_routes['start_point'], df_routes['end_point']]).unique()
    print(f"找到 {len(unique_locations)} 个不重复的地名需要处理。")

    results = []

    # 3. 遍历所有地名,执行分级搜索策略
    for i, location_name in enumerate(unique_locations):
        print(f"--- 正在处理: {location_name} ({i+1}/{len(unique_locations)}) ---")

        # 对已知的特殊地名进行手动修正
        query_name = location_name.replace("喇萨", "拉萨")
        if query_name != location_name: print(f" > [修正] '{location_name}' -> '{query_name}'")

        # 4. 在本地“智能地图”中查找地名信息(代码和级别)
        clean_name = query_name.replace('县', '').replace('市', '').replace('区', '')
        admin_info = name_info_map.get(query_name) or name_info_map.get(f"{clean_name}市") or name_info_map.get(f"{clean_name}县") or name_info_map.get(clean_name)

        poi_found = False
        # 【策略一】如果地名是标准行政区,执行精确的“行政区内搜索”
        if admin_info:
            admin_code = admin_info['code']
            admin_level = admin_info['level']
            search_keywords = []

            # 根据行政级别,制定优先搜索策略
            if admin_level == 'city':
                # 如果是市,优先搜“XX市政府”,找不到再搜“政府”
                search_keywords = [f"{clean_name}市政府", "政府"] 
                print(f" > [策略1: 市级优先] 找到市级代码 {admin_code},将依次搜索 {search_keywords}...")
            else: # county
                # 如果是县/区,直接搜“政府”即可
                search_keywords = ["政府"]
                print(f" > [策略1: 县级搜索] 找到县级代码 {admin_code},正在搜索'政府'...")

            # 遍历关键词进行搜索
            for keyword in search_keywords:
                if poi_found: break # 如果找到了,就不再进行后续搜索

                url = "http://api.tianditu.gov.cn/v2/search"
                post_str = {"keyWord": keyword, "queryType": 12, "specify": str(admin_code), "count": 1}
                params = {"postStr": json.dumps(post_str), "type": "query", "tk": user_api_key}
                try:
                    response = requests.get(url, params=params, timeout=10)
                    data = response.json()
                    if data.get("pois") and isinstance(data['pois'], list) and len(data['pois']) > 0:
                        poi_info = data['pois'][0]
                        lonlat = poi_info['lonlat'].split(',')
                        # 直接使用API返回的坐标,不再进行转换
                        results.append({"name": location_name, "found_name": poi_info.get('name'), 
                                        "lon_wgs84": float(lonlat[0]), "lat_wgs84": float(lonlat[1]), 
                                        "method": f"行政区内'{keyword}'精确搜索"})
                        poi_found = True
                except Exception as e:
                    print(f" > [错误] 搜索'{keyword}'时出错: {e}")
                time.sleep(0.2) # 礼貌性延迟

        # 【策略二】如果找不到代码或策略一失败,执行后备的“模糊搜索”
        if not poi_found:
            print(f" > [策略2: 模糊搜索] 使用地名 '{query_name}' 进行普通模糊搜索...")
            url = "http://api.tianditu.gov.cn/v2/search"
            post_str = {"keyWord": query_name, "queryType": "1", "count": 1, "start": 0}
            params = {"postStr": json.dumps(post_str), "type": "query", "tk": user_api_key}
            try:
                response = requests.get(url, params=params, timeout=10)
                data = response.json()
                if data.get("pois") and isinstance(data['pois'], list) and len(data['pois']) > 0:
                    poi_info = data['pois'][0]
                    lonlat = poi_info['lonlat'].split(',')
                    # 直接使用API返回的坐标
                    results.append({"name": location_name, "found_name": poi_info.get('name'), 
                                    "lon_wgs84": float(lonlat[0]), "lat_wgs84": float(lonlat[1]), 
                                    "method": "名称模糊搜索"})
                else:
                    results.append({"name": location_name, "found_name": "N/A", "lon_wgs84": None, "lat_wgs84": None, "method": "搜索无结果"})
            except Exception as e:
                results.append({"name": location_name, "found_name": "N/A", "lon_wgs84": None, "lat_wgs84": None, "method": "搜索请求错误"})
        time.sleep(0.2)

    # 5. 保存最终结果
    print("\n所有地名处理完毕,正在生成最终的点图层文件...")
    final_df = pd.DataFrame(results)
    final_df.to_csv(output_coordinates_file, index=False, encoding='utf-8-sig')

    print("\n" + "="*50)
    print("🎉🎉🎉 恭喜!已完成所有任务! 🎉🎉🎉")
    print(f"最终的、最精确的点图层文件已保存到: {output_coordinates_file}")
    print("现在,你可以将这个文件直接拖拽到QGIS中进行可视化和分析了!")
    print("="*50)

except FileNotFoundError as e:
    print(f"错误: 找不到文件,请确认路径正确。错误详情: {e}")
except Exception as e:
    print(f"发生了一个意料之外的错误: {e}")

连线


数据导入前的关键一步:用Python修复含“野逗号”的CSV文件

在我们通过脚本或者其他工具辛辛苦苦地获取了大量的地理坐标数据后,通常会保存为CSV(逗号分隔值)文件。这本应是通往成功的最后一步——将数据作为“带分隔符的文本图层”导入到QGIS中,然后泡上一杯咖啡,欣赏自己的劳动成果。

但现实往往会给你一个“惊喜”。

遇到的问题:错乱的“天书”

你信心满满地在QGIS中选择“添加带分隔符的文本图层”,选中你的CSV文件,然而预览窗口却让你大跌眼镜:本应是4列的数据,现在却变成了5列、6列甚至更多列,所有的数据都错位了,完全无法使用。

(示意图)

问题出在哪?我们打开CSV文件,仔细观察其中一行数据,比如我们之前生成的包含导航信息的数据:

1,Dian-Zang Main Line,POINT (100.96505379746658 22.828052960113908),从<b>起点</b>向正西方向出发,行驶80米,<b>左后方转弯</b>

罪魁祸首一目了然:最后一列 instruction(指令)中,本身就包含了逗号!

标准的CSV解析器(包括QGIS)在读取时,会把每一个逗号都视作分列的标志。因此,上面那行在它看来就不是4列,而是6列:

  1. 1
  2. Dian-Zang Main Line
  3. POINT (100.96505379746658 22.828052960113908)
  4. 从<b>起点</b>向正西方向出发
  5. 行驶80米
  6. <b>左后方转弯</b>

这就是问题的根源。为了让程序正确识别,我们需要告诉它,instruction 字段里的逗号是“内容”而不是“分隔符”。标准做法就是用双引号 " 将整个字段包围起来,像这样:

route_id,route_name,wkt_geometry_wgs84,instruction
1,"Dian-Zang Main Line","POINT (100.9...)", "从<b>起点</b>向正西方向出发,行驶80米,<b>左后方转弯</b>"

当字段被双引号包裹时,解析器就会忽略里面的逗号。手动给成百上千行数据加引号显然不现实,这时,就轮到我们最好的朋友——Python出场了。

解决思路:聪明的分割

我们的核心思路是:只根据前3个逗号进行分割

因为我们很清楚,route_id, route_name, wkt_geometry_wgs84 这三列内部是不会有逗号的。所有的“野逗号”都出现在第四列 instruction 中。Python的字符串方法 .split() 提供了一个完美的参数来执行这个任务。

实战代码:一键修复CSV

下面这段脚本可以读取我们格式不正确的CSV,并输出一个全新的、格式完美的CSV文件。

# 导入处理CSV和文件路径的必要模块
import csv
import os

# --- 用户配置区 ---
# 请将这里的路径修改为你自己电脑上的实际路径
# 原始的、格式有问题的文件
input_filename = r'F:\????\output_consolidated\main_all_turn_points.csv'
# 我们希望生成的新文件的存放路径
output_filename = r'F:\????\output_consolidated\main_all_turn_points_corrected.csv'
# --- 配置结束 ---

def convert_malformed_csv(input_path, output_path):
    """
    读取一个格式不正确的CSV文件,并将其转换为标准格式。
    主要解决第四列中包含未被引用的逗号的问题。
    """
    print(f"准备读取文件: {input_path}")

    # 检查输入文件是否存在,避免程序出错
    if not os.path.exists(input_path):
        print(f"错误:找不到输入文件 '{input_path}'。请检查文件路径是否正确。")
        return

    try:
        # 'with'语句可以确保文件在使用后被自动关闭,非常安全
        # 使用'utf-8'编码读取,防止中文乱码
        with open(input_path, 'r', encoding='utf-8') as infile, \
             open(output_path, 'w', newline='', encoding='utf-8-sig') as outfile:

            # 创建一个CSV写入器。它非常智能,会自动处理字段的引用
            # 使用 'utf-8-sig' 编码输出,可以让Excel在双击打开时正确识别UTF-8,避免中文乱码
            writer = csv.writer(outfile)

            # 读取并跳过原始文件的标题行(我们将在下面写入新的、标准的标题行)
            infile.readline()

            # 写入标准的、清晰的标题行
            writer.writerow(['route_id', 'route_name', 'wkt_geometry_wgs84', 'instruction'])

            print("开始处理数据行...")
            processed_lines = 0
            # 遍历文件的每一行
            for line in infile:
                # 跳过文件中的空行
                if not line.strip():
                    continue

                # 这是整个脚本最核心的一步!
                # line.strip() 用于移除行首和行尾的空白符(如换行符)
                # .split(',', 3) 表示“用逗号分割,但最多只分割3次”
                # 这会得到一个包含4个元素的列表
                parts = line.strip().split(',', 3)

                if len(parts) == 4:
                    # 将分割好的列表交给csv.writer
                    # writer会自动检查每个元素,如果发现里面有逗号,就会自动给它加上双引号
                    writer.writerow(parts)
                    processed_lines += 1
                else:
                    # 如果某一行分割后不是4部分,说明格式可能有其他问题,打印出来方便排查
                    print(f"警告:跳过格式异常的行: {line.strip()}")

        print("\n转换成功!")
        print(f"共处理 {processed_lines} 行数据。")
        print(f"已生成可直接导入QGIS的CSV文件: {output_path}")

    except Exception as e:
        print(f"处理过程中发生未知错误: {e}")

# --- 运行主程序 ---
if __name__ == "__main__":
    convert_malformed_csv(input_filename, output_filename)

使用方法

  1. 保存脚本: 将上述代码保存为 convert_csv.py 文件,并放在你的项目文件夹里。
  2. 修改路径: 确保代码中 input_filenameoutput_filename 的路径正确无误。
  3. 运行: 打开终端(或CMD),进入脚本所在目录,执行 python convert_csv.py
  4. 检查结果: 运行结束后,你会得到一个 ..._corrected.csv 文件。现在,将这个新文件拖入QGIS,你会发现一切都已回归正常,所有列都已完美对齐!

    (示意图)

小结

数据清洗和预处理是GIS分析和数据可视化中至关重要、但又常常被忽略的一环。遇到类似的数据格式问题时,不要畏惧,也不要尝试“手动修复”。通过一个简单的脚本,我们不仅高效地解决了问题,还为未来处理类似数据建立了一套可复用的流程。

掌握这种“小题大做”的自动化思维,是从业余爱好者走向专业选手的关键一步。

第四阶段:最终章——批量生成GIS路径点

在解决了输入数据的“野逗号”问题后,我们终于来到了整个流程的最后一环:从干净的起终点坐标数据,生成我们想要的路线。

最初,我们可能会想让Python直接生成线的WKT(Well-Known Text)格式。但这种方法相对复杂,且不利于在GIS软件中进行后期处理。经过实践,我们发现一个更强大、更灵活的工作流:
【此处使用百度地图API进行路线规划。也可以用天地图但是有时候路网数据获取不到,说白了就是百度的道路数据更加完善。】
让专业的人做专业的事。

  • Python的任务:作为数据请求和处理的专家,它的任务是调用API,获取所有路线的高精度路径点,并将它们整理成一个带有分组标识顺序标识的、干净的CSV文件。
  • GIS软件(QGIS)的任务:作为地理空间分析的专家,它的任务是读取这个点数据文件,并利用其强大的内置工具,根据我们提供的标识,将这些点 effortlessly(毫不费力地)连接成完美的路线。

这种“生成可生成路线的点”的思路,是数据处理与空间分析解耦的典型应用,也是GIS领域常用的一种高效工作方法。

思路重构:从生成路线到生成“可生成路线的点”

我们的最终目标是创建一个单一的CSV文件,它不仅仅是一堆坐标的罗列,而是包含了足够的信息,能让QGIS“读懂”点与点之间的关系。具体来说,每一行(每个点)都需要包含以下信息:

  1. wgs84_lon, wgs84_lat:点的WGS84坐标,用于在地图上定位。
  2. route_id路径分组字段。它告诉QGIS,哪些点是属于同一条路线的。例如,所有route_id为5的点都属于第5条路线。
  3. point_order路径顺序字段。它告诉QGIS,在同一条路线内部,应该按照怎样的顺序去连接这些点。例如,point_order为0的点连接到1,1连接到2,以此类推。

有了这三个关键信息,QGIS的“点转路径”工具就能大显神通。

代码:一键生成GIS路径点

下面的Python脚本是经过重构后的最终版本。它会读取main.csv,遍历每一条路线,调用百度地图API,最后生成一个名为 all_routes_points_for_gis.csv 的文件,完美地包含了我们上述提到的所有关键字段。

import pandas as pd
import requests
import json
import csv
import os
import time
import re
import math

# ===================================================================
# ==  坐标转换逻辑 (GCJ-02 -> WGS-84)  ==
# ===================================================================
pi = 3.1415926535897932384626
a = 6378245.0
ee = 0.00669342162296594323

def _transformlat(lng, lat):
    ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0
    return ret

def _transformlng(lng, lat):
    ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
    return ret

def out_of_china(lng, lat):
    return not (73.66 < lng < 135.05 and 3.86 < lat < 53.55)

def gcj02_to_wgs84(lng, lat):
    if out_of_china(lng, lat): return [lng, lat]
    dlat = _transformlat(lng - 105.0, lat - 35.0)
    dlng = _transformlng(lng - 105.0, lat - 35.0)
    radlat = lat / 180.0 * pi
    magic = math.sin(radlat)
    magic = 1 - ee * magic * magic
    sqrtmagic = math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
    dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
    mglat = lat + dlat
    mglng = lng + dlng
    return [lng * 2 - mglng, lat * 2 - mglat]

# ===================================================================
# ==     获取路径点函数 (已简化)    ==
# ===================================================================
def get_route_points_from_baidu_lite(api_key, origin_wgs, destination_wgs):
    """
    【已简化】
    此函数现在只请求API并返回高精度的路径点列表。
    """
    origin_str = f"{origin_wgs[1]},{origin_wgs[0]}"
    destination_str = f"{destination_wgs[1]},{destination_wgs[0]}"

    base_url = "https://api.map.baidu.com/directionlite/v1/driving"
    params = {
        "ak": api_key, "origin": origin_str, "destination": destination_str,
        "coord_type": "wgs84", "ret_coordtype": "gcj02" # steps_info 已不再需要
    }
    print(f"    > 正在请求从 {origin_wgs} 到 {destination_wgs} 的路线...")

    try:
        response = requests.get(base_url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
    except requests.exceptions.RequestException as e:
        print(f"    ❌ API请求失败: {e}")
        return [] # 返回空列表表示失败

    if data.get("status") != 0:
        print(f"    ❌ 百度API返回错误: status {data.get('status')}, message: {data.get('message')}")
        return [] # 返回空列表表示失败

    if 'result' not in data or 'routes' not in data['result'] or not data['result']['routes']:
        print(f"    ❌ API返回结果中未找到有效路线。")
        return []

    route = data['result']['routes'][0]
    route_points = []

    # 提取路径点
    if 'path' in route and route['path']:
        path_pairs = route['path'].split(';')
        for pair in path_pairs:
            if not pair or ',' not in pair: continue
            try:
                gcj02_lon_str, gcj02_lat_str = pair.split(',')
                wgs_point = gcj02_to_wgs84(float(gcj02_lon_str), float(gcj02_lat_str))
                route_points.append({'wgs84_lon': wgs_point[0], 'wgs84_lat': wgs_point[1]})
            except ValueError as e:
                print(f"      - 警告: 跳过一个格式错误的路径点 '{pair}'。错误: {e}")
                continue

    print(f"    ✔️ 成功获取并处理了 {len(route_points)} 个路径点。")
    return route_points

if __name__ == "__main__":
    # --- 1. 文件路径和API Key配置 ---
    base_path = 'F:\\茶马古道全图\\' # 根据你的实际情况修改
    input_routes_file = os.path.join(base_path, 'main.csv')
    output_folder = os.path.join(base_path, 'output_gis') # 定义一个统一的输出文件夹

    # 定义最终给GIS使用的输出文件的完整路径
    final_gis_points_file = os.path.join(output_folder, 'all_routes_points_for_gis.csv')

    baidu_api_key = "你的百度API Key" # 务必替换成你自己的Key

    # --- 2. 主程序开始 ---
    print("--- 开始为GIS生成聚合的路径点文件 ---")

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"已创建输出文件夹: {output_folder}")

    try:
        # 使用 engine='python' 来避免潜在的CSV格式错误
        df_routes = pd.read_csv(input_routes_file, engine='python', on_bad_lines='warn')
        df_routes.dropna(subset=['start_lon', 'start_lat', 'end_lon', 'end_lat'], inplace=True)
        print(f"成功读取路线文件,找到 {len(df_routes)} 条有效的路段进行处理。")

        # 用于聚合所有路径点的数据
        all_points_for_gis = []

        for index, row in df_routes.iterrows():
            route_id = row['id']
            route_name = row['name']

            print(f"\n--- 正在处理路线 {index + 1}/{len(df_routes)}: [{route_id}] {row['start_point']} -> {row['end_point']} ---")

            origin_wgs84 = (row['start_lon'], row['start_lat'])
            destination_wgs84 = (row['end_lon'], row['end_lat'])

            # 调用函数只获取路径点
            route_points = get_route_points_from_baidu_lite(baidu_api_key, origin_wgs84, destination_wgs84)

            # 【核心修改】为每个点添加路线标识和顺序号
            for i, rp in enumerate(route_points):
                rp['route_id'] = route_id
                rp['route_name'] = route_name
                rp['point_order'] = i  # 点在其所属路线中的顺序号 (从0开始)
                all_points_for_gis.append(rp)

            time.sleep(1) # 遵守API使用频率限制,暂停1秒

        # --- 3. 所有路线处理完毕后,写入最终的聚合文件 ---
        print("\n" + "="*50)
        print("...所有路段数据请求完毕,正在生成最终的GIS点文件...")

        try:
            with open(final_gis_points_file, 'w', newline='', encoding='utf-8-sig') as f:
                # 定义新的表头,包含顺序字段
                fieldnames = ['route_id', 'route_name', 'point_order', 'wgs84_lon', 'wgs84_lat']
                writer = csv.DictWriter(f, fieldnames=fieldnames)
                writer.writeheader()
                writer.writerows(all_points_for_gis)
            print(f"✔️ 成功!所有路径点已保存到: {final_gis_points_file}")
        except Exception as e:
            print(f"❌ 写入GIS点文件失败: {e}")

        print("\n" + "="*50)
        print("🎉🎉🎉 所有路线处理完毕! 🎉🎉🎉")

    except FileNotFoundError:
        print(f"错误: 找不到输入文件 {input_routes_file},请确认文件路径和名称是否正确。")
    except Exception as e:
        print(f"发生了一个意料之外的错误: {e}")

使用方法:从CSV到QGIS中的完美路线

当脚本成功运行后,你会在 output_gis 文件夹中找到 all_routes_points_for_gis.csv 文件。现在,让我们回到QGIS,完成最后的神奇一跃。

第一步:加载分隔文本文件为点图层

  1. 在QGIS菜单栏,选择 图层 -> 添加图层 -> 添加分隔文本文件...
  2. 在弹出的窗口中,文件源部分选择我们刚刚生成的 all_routes_points_for_gis.csv 文件。
  3. 几何图形定义部分是关键:
    • X 字段:选择 wgs84_lon
    • Y 字段:选择 wgs84_lat
    • 几何图形坐标参考系:选择 EPSG:4326 - WGS 84
  4. 点击右下角的“添加”,然后关闭窗口。你会看到地图上出现了成千上万个密集的点,这就是我们所有路线的“骨架”。

第二步:使用“点转路径”工具生成路线

  1. 在QGIS顶部菜单栏,选择 处理 -> 工具箱,打开处理工具箱面板。
  2. 在工具箱的搜索框中,输入“点转路径”(英文版为 "Points to Path")。
  3. 双击打开该工具,会弹出一个配置窗口。在这里,我们要把“规则”告诉QGIS:
    • 输入点图层:选择我们刚刚添加的 all_routes_points_for_gis 点图层。
    • 路径分组字段 (Group field):这是最重要的设置之一。请选择 route_id。这个设置告诉QGIS,所有route_id相同的点应该被连接成一条线。
    • 顺序字段 (Order field):这是另一个最重要的设置。请选择 point_order。它告诉QGIS在连接同一组的点时,要严格按照point_order从小到大的顺序。
    • 路径 (Paths):点击 ... 按钮,选择 保存到文件...,为你即将生成的最终路线图层(Shapefile或GeoPackage格式)起一个名字,比如 Chuan-Zang_Routes_Final.shp
  4. 点击“运行”。

奇迹发生了!QGIS会瞬间处理完所有的点,生成一个全新的线图层。这个图层里的每一条线,都精确地对应了你main.csv中的每一段路程,并且拥有平滑、精确的曲线。

小结

至此,我们已经走完了一个完整的、自动化的地理信息处理流程:从一份仅包含地名的表格,到一份包含精确起终点坐标的CSV,再通过Python脚本批量调用在线地图服务,最终在QGIS中生成了精确、美观的路线图层。

这个过程的核心在于分而治之:将数据清洗、数据获取、空间分析等不同阶段拆分开,并为每个阶段选择最合适的工具。Python强大的粘合与自动化能力,结合QGIS专业的空间分析能力,让我们能够高效地完成看似复杂繁琐的任务。希望这个详细的流程能对你有所启发!

好的,非常荣幸能为您总结这次的 QGIS 操作全流程。这套流程非常实用,完整地展示了如何利用 QGIS 的自动化能力,将原始数据高效地处理成最终的可视化地图。

以下是为您精心整理的、适合直接用在博客中的完整教程。


QGIS 实战教程:从零开始,用 Python 自动化处理批量路径数据

在地理信息处理中,我们常常会遇到需要处理大量、结构相似但分散在不同文件中的数据。手动操作不仅效率低下,还容易出错。本教程将以一个“茶马古道”的路径数据为例,完整演示如何利用 QGIS 及其强大的 Python 控制台(PyQGIS),实现从批量数据导入、处理转换到最终样式美化的全自动化流程。

项目目标

我们的原始数据包含两大类,分别存放在不同的子文件夹中:

  1. 导航点 (route_navigation_points): 大量的 CSV 文件,每个文件包含一条线路的详细轨迹点,有经纬度和顺序 (wgs84_lon, wgs84_lat, point_order)。
  2. 地标点 (route_placemarks): 记录关键地点的 CSV 文件,包含经纬度 (longitude, latitude)。

我们的目标是:

  1. 将这些分散的 CSV 文件作为点图层批量导入 QGIS,并自动分组管理。
  2. 将导航点图层批量转换为有序的线图层,形成路径。
  3. 对生成的所有路径图层进行批量样式美化,例如统一修改线宽和颜色。

第一步:批量导入并分组 CSV 点数据

这是所有工作的基础。由于数据在不同文件夹且坐标字段名不同,使用脚本是最高效的方案。

核心思路: 编写一个 Python 脚本,该脚本能够:

  • 分别处理两个不同的源文件夹。
  • 为每个文件夹内的 CSV 指定正确的经纬度列名。
  • 将加载的图层放入 QGIS 的不同“图层组”中,便于管理。

PyQGIS 脚本:

打开 QGIS,通过菜单 插件 (Plugins) -> Python 控制台 (Python Console),将以下脚本粘贴入控制台并运行。

import os
from qgis.core import QgsProject, QgsVectorLayer

# --- 1. 用户配置区 ---
# 主文件夹路径 (请使用 / 代替 \)
base_folder = 'F:/???/output_routes_aggregated'

# 导航点配置
nav_points_folder_name = 'route_navigation_points'
nav_x_field = 'wgs84_lon'
nav_y_field = 'wgs84_lat'
nav_group_name = '导航点 (Navigation Points)'

# 地标点配置
placemarks_folder_name = 'route_placemarks'
placemarks_x_field = 'longitude'
placemarks_y_field = 'latitude'
placemarks_group_name = '地标点 (Placemarks)'

# 坐标系 (EPSG:4326 代表 WGS 84)
crs_epsg = 4326

# --- 2. 核心处理函数 ---
def process_csv_folder(folder_path, group_name, x_field, y_field, crs):
    print(f"--- 开始处理文件夹: {folder_path} ---")
    project = QgsProject.instance()
    root = project.layerTreeRoot()
    group = root.findGroup(group_name)
    if not group:
        group = root.addGroup(group_name)
    if not os.path.exists(folder_path):
        print(f"警告:文件夹路径不存在: {folder_path}")
        return
    for file_name in os.listdir(folder_path):
        if file_name.lower().endswith('.csv'):
            file_path = os.path.join(folder_path, file_name)
            uri = f"file:///{file_path}?delimiter=,&xField={x_field}&yField={y_field}&crs=epsg:{crs}"
            layer_name = os.path.splitext(file_name)[0]
            layer = QgsVectorLayer(uri, layer_name, 'delimitedtext')
            if layer.isValid():
                project.addMapLayer(layer, False)
                group.addLayer(layer)
    print(f"--- 文件夹处理完毕 ---")

# --- 3. 执行 ---
process_csv_folder(os.path.join(base_folder, nav_points_folder_name), nav_group_name, nav_x_field, nav_y_field, crs_epsg)
process_csv_folder(os.path.join(base_folder, placemarks_folder_name), placemarks_group_name, placemarks_x_field, placemarks_y_field, crs_epsg)
print("\n所有CSV文件添加完毕!")

执行效果: 脚本运行后,QGIS 图层面板中会自动出现“导航点”和“地标点”两个图层组,且所有 CSV 文件都已作为点图层正确加载到对应的组内。


第二步:批量将点图层转换为线图层

现在我们有了所有的导航点,下一步是将它们连接成线。

核心思路: 利用 QGIS 内置的 点转路径 (Points to Path) 算法,并使用 point_order 字段作为连接顺序,对“导航点”组内的所有图层进行自动化处理。

PyQGIS 脚本:

import os
import processing
from qgis.core import QgsProject, QgsVectorLayer, QgsMapLayer

# --- 1. 用户配置区 ---
# 输入的点图层所在的组名
input_group_name = '导航点 (Navigation Points)'
# 用于确定点连接顺序的字段名
order_field_name = 'point_order'
# 输出线图层要保存到的新文件夹路径
output_folder = 'F:/茶马古道全图/output_lines'
# 在QGIS中创建的新图层组的名称
output_group_name = '导航线路 (Navigation Lines)'

# --- 2. 脚本主体 ---
print("点转线脚本开始执行...")
project = QgsProject.instance()
root = project.layerTreeRoot()
input_group = root.findGroup(input_group_name)
if not input_group:
    print(f"错误:找不到名为 '{input_group_name}' 的图层组。")
else:
    os.makedirs(output_folder, exist_ok=True)
    output_group = root.findGroup(output_group_name)
    if not output_group:
        output_group = root.addGroup(output_group_name)
    for layer_node in input_group.children():
        point_layer = layer_node.layer()
        if isinstance(point_layer, QgsMapLayer) and point_layer.type() == QgsMapLayer.VectorLayer and point_layer.geometryType() == 0:
            layer_name = point_layer.name()
            print(f"正在处理: {layer_name}")
            output_file_name = f"{layer_name}_line.gpkg"
            output_path = os.path.join(output_folder, output_file_name)
            params = {
                'INPUT': point_layer, 'ORDER_FIELD': order_field_name, 'OUTPUT': output_path
            }
            result = processing.run('qgis:pointstopath', params)
            line_layer = QgsVectorLayer(result['OUTPUT'], layer_name + "_line", "ogr")
            if line_layer.isValid():
                project.addMapLayer(line_layer, False)
                output_group.addLayer(line_layer)
print("\n所有点图层转换完毕!")

执行效果: 脚本运行后,会在您指定的路径下创建一个 output_lines 文件夹,里面存放着所有生成的线图层文件(.gpkg格式)。同时,QGIS 中会新增一个“导航线路”图层组,包含了所有新生成的路径图层。


第三步:批量统一图层样式

最后一步是让我们的地图看起来更专业。我们需要将所有路径的样式(如线宽、颜色)统一。

方法 A:复制粘贴样式 (最快捷的图形化操作)
  1. 设置样本: 在“导航线路”组中任选一个图层,双击打开其属性,在“符号系统”中设置好您满意的线宽、颜色等样式。
  2. 复制样式: 右键点击这个设置好的图层,选择 样式 (Styles) -> 复制样式 (Copy Style)
  3. 粘贴到组: 右键点击“导航线路”整个图层组,选择 粘贴样式 (Paste Style)

一瞬间,组内所有图层的样式都会被统一,非常高效。

方法 B:使用 Python 脚本 (适合自动化与精确控制)

当需要精确控制或频繁修改样式时,脚本是更好的选择。

from qgis.core import QgsProject, QgsMapLayer
from PyQt5.QtGui import QColor

# --- 1. 用户配置区 ---
target_group_name = '导航线路 (Navigation Lines)'
new_width = 0.8  # 单位:毫米
new_color_name = 'red' # 或 '#FF0000'

# --- 2. 脚本主体 ---
print("样式修改脚本开始执行...")
project = QgsProject.instance()
root = project.layerTreeRoot()
target_group = root.findGroup(target_group_name)
if not target_group:
    print(f"错误:找不到名为 '{target_group_name}' 的图层组。")
else:
    for layer_node in target_group.children():
        layer = layer_node.layer()
        if isinstance(layer, QgsMapLayer) and layer.type() == QgsMapLayer.VectorLayer:
            renderer = layer.renderer()
            if renderer and hasattr(renderer, 'symbol'):
                symbol = renderer.symbol()
                symbol.setWidth(new_width)
                symbol.setColor(QColor(new_color_name))
                layer.triggerRepaint()
                iface.layerTreeView().refreshLayerSymbology(layer.id())
    iface.mapCanvas().refresh()
print("\n所有图层样式修改完毕!")

执行效果: 运行脚本后,“导航线路”组内的所有线图层都会被自动修改为您在脚本中定义的宽度和颜色。

总结

通过以上三个步骤,我们完成了一个从原始数据到最终地图产品的完整自动化工作流。这个过程充分展示了 PyQGIS 在处理批量、重复性任务时无与伦比的优势。希望这篇教程能帮助您在未来的 GIS 项目中,摆脱繁琐的手动操作,拥抱自动化的力量。您可以根据自己的数据结构和需求,灵活修改本教程中的脚本,将其应用到更广泛的场景中。

QGIS制图技巧:从数据编辑到专业色彩选择

在使用QGIS进行地理数据可视化时,我们常常会遇到两个经典问题:如何方便地修改地图要素的标注内容?以及,当面对大量同类要素(如河流、道路)时,如何选择一套既清晰又美观的配色方案?

今天,我们就来深入探讨这两个问题,分享一些实用的QGIS操作技巧和专业的地图学(Cartography)色彩选择原则。

一、 告别困惑:如何正确修改QGIS中的标注内容?

很多初学者会有一个误区:试图在“图层属性 -> 标注”面板中修改标注的文字。

需要明确的是,这个面板是用来定义 标注的显示样式 的(比如字体、大小、颜色),而不是用来编辑 标注的源数据内容 的。

要修改标注上显示的文字(例如,把一条河的名字从“旧名称”改为“新名称”),我们必须去编辑该要素的属性数据。这里有两种高效的方法:

方法1:属性表批量编辑(最常用)

file

  1. 在图层面板右键单击图层,选择 “打开属性表”
  2. 点击工具栏的 “切换编辑模式”(铅笔图标)。
  3. 找到需要修改的要素行,直接在对应的字段单元格中输入新内容。
  4. 修改完成后,点击 “保存图层编辑”(磁盘图标),然后再次点击铅笔图标退出编辑模式。地图上的标注便会自动更新。

方法2:识别工具精确修改

  1. 先将图层切换到 编辑模式
  2. 使用工具栏上的 “识别要素”(带'i'的蓝色圆圈)工具,在地图上点击你想要修改的要素。
  3. 在右侧弹出的“识别结果”面板中,会直接呈现一个可编辑的属性表单,在这里输入新值即可。
  4. 不要忘记 保存编辑

小结:记住,样式归样式,数据归数据。修改“显示什么”要去属性表,修改“如何显示”才去标注面板。

二、 河流赋彩:地图配色的艺术与科学

当地图上有多条河流时,如果全部使用同一种蓝色,会显得单调且无法区分。那么,如何为它们选择一套既能区分彼此,又不与背景地形冲突的颜色呢?

核心原则

  • 区分度:不同河流的颜色要有明显区别。
  • 对比度:颜色要能从背景中“跳”出来,清晰易读。
  • 和谐性:整套配色方案要视觉舒适,不刺眼。
  • 逻辑性:颜色分配最好有内在规律,让地图传达更多信息。

专业配色策略

  1. 按水系/流域分组(最推荐)
    这是最符合地理学逻辑的方法。为每个主要水系分配一个主色调,然后通过调整该色调的明暗或饱和度来区分内部的支流。

    • 示例:长江水系(金沙江、雅砻江)统一用蓝色系,澜沧江水系用青色系,怒江水系用蓝紫色系。这样,读者一眼就能看出河流的归属关系。
  2. 按数据属性分级
    如果你的数据有流量、等级等字段,可以使用渐变色带(如从浅蓝到深蓝)来直观展示河流的量级差异。

16色专业推荐色板 (Hex十六进制)

为了方便大家直接使用,这里提供一套精心挑选的16色盘,它们非常适合在地形图上展示河流等线状要素。

核心色盘:蓝色、青色与紫色系
  • 经典河蓝: #0077B6
  • 深海蓝: #003F88
  • 亮天蓝: #48CAE4
  • 青蓝: #009688
  • 亮青: #20C997
  • 靛蓝: #4B31AB
  • 皇家紫: #6A0DAD
  • 梅子紫: #8E44AD
补充色盘:绿色与暖色系
  • 翡翠绿: #04A777
  • 森林绿: #136F63
  • 赭石橙: #D95F02
  • 砖红: #C0392B
  • 洋红: #C71585
  • 深粉: #D81B60
  • 酒红: #8B0000
  • 灰蓝: #5A6A9F

配色技巧:在颜色选择器中,尽量选择高饱和度、中高亮度的颜色来对抗饱和度较低的地形背景。同时,将视觉差异最大的颜色(如蓝色和橙色)分配给地理上邻近的要素,以达到最佳的区分效果。

总结

一张优秀的地图,不仅数据准确,其视觉传达的效率和美感也同样重要。希望今天分享的QGIS数据编辑技巧和色彩选择策略,能帮助大家在制图之路上更进一步,创作出信息清晰、视觉出众的地图作品!


教程中的第四部分(《最终章——批量生成GIS路径点》)处理的是**“两点之间”的路线生成,即从一个起点到一个终点。而您现在提出的,是另一个非常重要且常见的场景:如何处理“一条由多个有序途经点组成的完整线路”**。

这两种场景的区别在于:

  • 现有场景:输入是 (A, B), (C, D),输出是 A->B 的路径和 C->D 的路径。
  • 您提出的场景:输入是一个有序列表 (A, B, C, D),目标是依次生成 A->B, B->C, C->D 的路径,并将它们无缝拼接成一条完整的 A->B->C->D 的总路线。

这对于复现一条完整的、有特定途经点的历史路线(如茶马古道、丝绸之路)或规划长途旅行路线至关重要。


补充章节:从有序途经点到完整精细路线的生成

在之前的章节中,我们实现了根据“起点-终点”对来批量生成路线。但很多时候,我们的原始数据是一条包含多个有序途经点的完整线路。本章将介绍如何处理这种数据,将其转换为一系列分段路线,最终在QGIS中拼接成一条完整的、高精度的轨迹。
【但实际上我在操作过程是直接给gemini一个文件然后让她自己帮我生成对应的CSV,后面我再去填写坐标的。参考附录中的内容。】

核心思路:化整为零,再聚沙成塔

我们的策略是将一条长路线拆解成多个“起点-终点”对。如果一条路线的途经点顺序是 P1, P2, P3, ..., Pn,那么我们可以将其分解为 n-1 个路段:

  • 路段1: P1 -> P2
  • 路段2: P2 -> P3
  • ...
  • 路段n-1: Pn-1 -> Pn

然后,我们对每一个路段调用之前章节中使用的百度地图API,获取其详细的轨迹点。最后,将所有路段的轨迹点汇总到一个文件中,并为它们打上唯一的路段标识点内顺序标识,以便在QGIS中进行最终的拼接。

第一步:准备途经点数据

首先,我们需要一个新的CSV文件来描述这些有序的途经点。我们将其命名为 waypoints.csv。这个文件的结构至关重要,它必须包含以下几列:

  • route_name: 路线的名称(用于分组,例如“滇藏线”)。
  • waypoint_order: 该点在这条路线中的顺序号(必须是整数,从1开始递增)。
  • lon_wgs84: 该点的WGS84经度。
  • lat_wgs84: 该点的WGS84纬度。

waypoints.csv 文件示例:

route_name,waypoint_order,lon_wgs84,lat_wgs84
滇藏线,1,102.71225,25.0406
滇藏线,2,100.23334,25.58946
滇藏线,3,99.17269,26.86241
滇藏线,4,98.90342,27.73429
滇藏线,5,97.48935,28.46824
川藏线,1,104.06667,30.66667
川藏线,2,103.5875,31.0003
川藏线,3,102.04333,30.05

这个文件清晰地定义了两条路线:“滇藏线”(包含5个点)和“川藏线”(包含3个点)。

第二步:编写Python脚本进行批量处理

现在,我们编写一个新的Python脚本,它会读取 waypoints.csv,自动将其分解为路段,并为每个路段请求路径数据。

这个脚本可以复用我们之前编写的坐标转换和API请求函数。

import pandas as pd
import requests
import json
import csv
import os
import time
import math

# ===================================================================
# ==  坐标转换逻辑 (GCJ-02 -> WGS-84) - 此部分与原教程代码相同  ==
# ... (此处省略,请直接从原教程中复制 gcj02_to_wgs84 及其辅助函数)
# ===================================================================

# ===================================================================
# ==  获取路径点函数 - 此部分与原教程代码相同  ==
# ... (此处省略,请直接从原教程中复制 get_route_points_from_baidu_lite 函数)
# ===================================================================

if __name__ == "__main__":
    # --- 1. 文件路径和API Key配置 ---
    base_path = 'F:\\茶马古道全图\\'  # 根据你的实际情况修改
    # 【新】输入文件:包含有序途经点的CSV
    input_waypoints_file = os.path.join(base_path, 'waypoints.csv')
    output_folder = os.path.join(base_path, 'output_gis_from_waypoints')

    # 【新】输出文件:准备给QGIS使用的、包含所有分段路径点的聚合文件
    final_gis_points_file = os.path.join(output_folder, 'all_segmented_routes_points.csv')

    baidu_api_key = "你的百度API Key"  # 务必替换成你自己的Key

    # --- 2. 主程序开始 ---
    print("--- 开始根据有序途经点,生成分段聚合的路径点文件 ---")

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"已创建输出文件夹: {output_folder}")

    try:
        df_waypoints = pd.read_csv(input_waypoints_file)
        print(f"成功读取途经点文件,共 {len(df_waypoints)} 个点,涉及 {df_waypoints['route_name'].nunique()} 条路线。")

        all_points_for_gis = []

        # 按路线名称进行分组,确保不同路线的点不会混在一起
        for route_name, route_group in df_waypoints.groupby('route_name'):
            print(f"\n--- 正在处理路线: {route_name} ---")

            # 在每个路线组内,根据 waypoint_order 排序,确保点的顺序正确
            sorted_waypoints = route_group.sort_values(by='waypoint_order').reset_index()

            # 遍历排序后的点,两两配对形成路段 (P1->P2, P2->P3, ...)
            # 循环到倒数第二个点即可
            for i in range(len(sorted_waypoints) - 1):
                # 定义起点和终点
                start_point = sorted_waypoints.iloc[i]
                end_point = sorted_waypoints.iloc[i+1]

                # 创建一个唯一路段ID,方便在QGIS中识别和分组
                segment_id = f"{route_name}_{start_point['waypoint_order']}-{end_point['waypoint_order']}"

                print(f"  > 正在处理路段 {i+1}/{len(sorted_waypoints)-1}: [{segment_id}]")

                origin_wgs84 = (start_point['lon_wgs84'], start_point['lat_wgs84'])
                destination_wgs84 = (end_point['lon_wgs84'], end_point['lat_wgs84'])

                # 调用API获取路段的详细轨迹点
                route_points = get_route_points_from_baidu_lite(baidu_api_key, origin_wgs84, destination_wgs84)

                # 为获取到的每个轨迹点,添加分组和排序信息
                for j, rp in enumerate(route_points):
                    rp['route_id'] = segment_id      # 【关键】使用我们创建的唯一路段ID作为分组字段
                    rp['route_name'] = route_name    # 父路线的名称
                    rp['point_order'] = j            # 点在当前路段中的顺序号
                    all_points_for_gis.append(rp)

                time.sleep(1) # 遵守API使用频率限制

        # --- 3. 所有路段处理完毕后,写入最终的聚合文件 ---
        print("\n" + "="*50)
        print("...所有路线数据请求完毕,正在生成最终的GIS点文件...")

        try:
            with open(final_gis_points_file, 'w', newline='', encoding='utf-8-sig') as f:
                fieldnames = ['route_id', 'route_name', 'point_order', 'wgs84_lon', 'wgs84_lat']
                writer = csv.DictWriter(f, fieldnames=fieldnames)
                writer.writeheader()
                writer.writerows(all_points_for_gis)
            print(f"✔️ 成功!所有路径点已保存到: {final_gis_points_file}")
        except Exception as e:
            print(f"❌ 写入GIS点文件失败: {e}")

        print("\n" + "="*50)
        print("🎉🎉🎉 所有途经点处理完毕! 🎉🎉🎉")
        print("现在,您可以将生成的CSV文件导入QGIS,并使用'点转路径'工具生成路线。")

    except FileNotFoundError:
        print(f"错误: 找不到输入文件 {input_waypoints_file},请确认文件路径和名称是否正确。")
    except Exception as e:
        print(f"发生了一个意料之外的错误: {e}")

第三步:在QGIS中生成并拼接路线

脚本运行成功后,会生成一个 all_segmented_routes_points.csv 文件。这个文件的结构与我们之前生成的 all_routes_points_for_gis.csv 完全兼容,因此,后续在QGIS中的操作是一模一样的!

  1. 加载点图层:将 all_segmented_routes_points.csv 作为“带分隔符的文本图层”添加到QGIS中。
  2. 运行“点转路径”工具
    • 输入点图层: 选择你刚加载的点图层。
    • 路径分组字段 (Group field): 选择 route_id。这会将 滇藏线_1-2, 滇藏线_2-3 等识别为不同的线段。
    • 顺序字段 (Order field): 选择 point_order
    • 运行

执行后,QGIS会生成一个新的线图层。在这个图层中,原始的一条完整路线被表示为多条首尾相连的线要素。因为API返回的路径点非常精确,这些线段在视觉上是完美拼接在一起的,形成了一条完整的、平滑的轨迹。


总结:从数据到地图的淬炼之旅

至此,我们已经完成了本次教程中最为核心和最具挑战性的动手实践部分。如果您一步步地跟了下来,那么恭喜您,您不仅完成了一系列复杂的技术操作,更重要的是,您已经亲身体验了一套完整的、从原始文字到专业地理可视化的工作流。

这不仅仅是软件功能的堆砌,更是一场充满逻辑推理与自动化思维的淬炼之旅。现在,让我们一同回顾这段旅程,梳理我们掌握的核心脉络与思想:

  1. 第一步:从“名”到“点”的精准定位
    我们始于一份模糊的地名列表,通过“本地知识库 + API精确制导”的混合策略,将这些承载着历史信息的文字,转化为了大地坐标系上一个个精准的“点”。这个过程教会我们:面对不确定性,要敢于质疑、大胆假设、小心求证,并利用编程的手段,将复杂的考证逻辑固化为可重复执行的、高效的解决方案。

  2. 第二步:从“点”到“线”的智慧连接
    我们没有用简单的直线连接点位,而是采用了“点生线”(Points to Path)的专业思想。通过调用百度地图API,我们获取了基于真实路网的高精度轨迹点,并巧妙地为这些点赋予了route_id(路径分组)和point_order(连接顺序)的关键信息。这个过程的核心在于数据准备的远见:我们生成的不是终点,而是能够让专业GIS工具“读懂”的、结构化的中间数据。

  3. 第三步:从“线”到“图”的批量升华
    面对成百上千的路径点数据,我们没有陷入手动操作的泥潭。借助QGIS强大的Python控制台(PyQGIS),我们实现了数据导入、点转路径、图层样式的全自动化处理。这充分展示了“自动化思维”在地理信息处理中的绝对优势——将繁琐、重复的劳动交给机器,让我们能聚焦于更宏观的分析与设计。

贯穿始终的核心思想:

  • 分而治之 (Divide and Conquer): 我们将“绘制茶马古道”这个宏大目标,拆解为“找点”、“连线”、“美化”等一系列清晰、可执行的子任务。
  • 专业工具的协同 (Synergy of Tools): 我们让Python/PyQGIS发挥其在数据处理和自动化上的长处,让QGIS专注于其核心的空间分析与可视化能力,实现了1+1>2的效果。
  • 数据“预处理”决定成败 (Preprocessing is Key): 无论是修复CSV中的“野逗号”,还是为路径点添加分组和排序字段,都体现了数据清洗和预处理在整个工作流中的基石作用。

走完“动手实践”这一章,您手中掌握的,已不仅是几个零散的工具技巧,而是一整套打通数据获取、处理、分析与可视化的现代地理信息研究方法。拥有了这套“内功心法”,相信您已经有足够的能力与信心,去开启属于您自己的地理探案之旅。

附录

QGIS中轻松导入和使用SVG图标

在QGIS中,您可以轻松导入可缩放矢量图形 (SVG) 图标来丰富您的地图符号系统,使其更具表现力和个性化。本文将为您详细介绍几种在QGIS中导入和使用SVG图标的方法。

方法一:添加SVG文件路径

这是最常用且推荐的方法,通过在QGIS的设置中添加您存放SVG图标的文件夹路径,可以方便地在不同项目中重复使用这些图标。

步骤如下:

  1. 准备SVG图标: 将您需要使用的SVG格式图标文件存放在您电脑的一个或多个文件夹中。您可以从各种在线资源库下载免费或付费的SVG图标,或使用矢量绘图软件(如Inkscape、Adobe Illustrator)自行创建。

  2. 打开QGIS选项: 在QGIS菜单栏中,点击“设置” (Settings) -> “选项” (Options)。

  3. 配置SVG路径: 在弹出的“选项”对话框中,选择左侧的“系统” (System) 选项卡。在右侧的“SVG路径” (SVG Paths) 部分,您会看到一个列表,其中包含了QGIS默认的SVG图标路径。

  4. 添加您的路径: 点击列表下方绿色的“+”按钮,然后浏览并选择您存放SVG图标的文件夹。添加后,您的文件夹路径将显示在列表中。您可以添加多个路径。

  5. 应用并确定: 点击“应用” (Apply) 和“确定” (OK) 保存设置。

如何使用已添加的SVG图标:

  1. 打开图层属性: 在图层面板中,右键点击您想要修改符号的图层,选择“属性” (Properties)。

  2. 进入符号化设置: 在“图层属性”对话框中,选择“符号化” (Symbology) 选项卡。

  3. 选择SVG标记:

    • 对于点图层,点击“简单标记” (Simple marker)。在右侧的“符号图层类型” (Symbol layer type) 下拉菜单中,选择“SVG标记” (SVG marker)。
    • 对于线或面图层,您可以添加一个新的符号图层,并将其类型设置为“标记线” (Marker line) 或“点填充” (Point pattern fill),然后将其中的标记更改为SVG标记。
  4. 浏览并选择图标: 向下滚动,您会看到一个SVG浏览器。在左侧的“组” (Groups) 中,您应该能找到以您添加的文件夹命名的图标组。点击该组,右侧就会显示该文件夹中所有的SVG图标。选择您想使用的图标即可。

方法二:将SVG文件嵌入到项目中

如果您希望将SVG图标直接保存在QGIS项目文件中,以便于项目迁移和分享,可以选择嵌入文件的方式。这样,即使在没有配置SVG路径的电脑上打开该项目,图标也能正确显示。

步骤如下:

  1. 遵循上述方法一中的步骤1至3,进入图层的“符号化”设置,并将符号图层类型更改为“SVG标记”。

  2. 嵌入文件: 在SVG浏览器下方,找到一个文件路径输入框。点击输入框右侧的“...”按钮或旁边的下拉箭头,然后选择“嵌入文件...” (Embed File...)。

  3. 选择SVG文件: 在弹出的文件浏览器中,找到并选择您想要嵌入的SVG图标文件。

  4. 完成设置: 图标将被嵌入到项目中,您可以在符号预览中看到效果。

方法三:使用QGIS资源共享插件

QGIS资源共享插件 (QGIS Resource Sharing) 提供了一个便捷的方式来发现、分享和管理QGIS资源,其中就包括SVG图标集。

步骤如下:

  1. 安装插件: 在QGIS菜单栏中,点击“插件” (Plugins) -> “管理并安装插件” (Manage and Install Plugins)。搜索“Resource Sharing”并进行安装。

  2. 浏览和安装资源: 安装后,在“插件”菜单中会多出一个“Resource Sharing”选项。您可以从中浏览他人分享的资源集合(包括SVG图标),并选择安装到您的QGIS中。

小贴士

  • SVG图标的颜色和大小: 在QGIS的符号化设置中,您可以方便地调整已加载SVG图标的大小、填充颜色、描边颜色和描边宽度,使其与您的地图风格完美匹配。
  • 默认SVG文件夹: 在不同操作系统中,QGIS的默认SVG文件夹位置有所不同。您可以通过在“选项” -> “系统” -> “SVG路径”中查看默认路径来找到它们。通常位于QGIS安装目录下的svg文件夹或用户配置文件夹中。
  • 路径问题: 在团队协作或项目分享时,推荐使用“嵌入文件”或所有成员都将SVG图标放在QGIS可识别的共享网络路径中,以避免因路径不一致导致的图标丢失问题。

通过以上方法,您可以极大地扩展QGIS的符号库,创作出更具吸引力和信息量的地图作品。

当然!非常乐意为您提供这套方法论,您可以将它分享给您的读者,帮助他们也能高效地利用AI处理类似任务。


如何利用AI将复杂文本转换为结构化数据:一份分步指南

我们经常会遇到一些信息量巨大但格式混乱的文本资料,例如项目报告、历史文献、产品列表,甚至是像我们这次处理的“茶马古道”线路图。这些信息虽然宝贵,但如果不整理成结构化数据(如Excel或CSV表格),就很难进行分析、可视化或导入到其他应用程序中。

传统方法需要手动复制粘贴,耗时耗力且容易出错。但现在,我们可以借助大型语言模型(LLM)AI,如谷歌的Gemini,将这个过程的效率提升数十倍。

这篇指南将通过“茶马古道”这个具体案例,教你一套可复用的方法论,让你也能成为驾驭AI进行数据处理的高手。

核心理念:AI是你的“数据处理实习生”,而你是“项目经理”

在开始之前,我们需要建立一个正确的心态:

  1. 你负责定义目标:AI无法凭空猜出你想要什么。你必须清晰地定义最终成果的样貌(比如,一个包含特定列的CSV文件)。
  2. 你负责提供原料:AI需要你提供准确、完整的原始文本数据。
  3. 你负责下达指令:指令的清晰度和准确性,直接决定了AI输出结果的质量。“指令工程”(Prompt Engineering)是这套方法论的核心。
  4. 你负责审核成果:AI会严格执行指令,但最终的质量把控仍在你手中。你需要检查结果并提出修正意见。

四步方法论:将混乱变为有序

第一步:明确你的最终目标 (Define the Destination)

在与AI对话前,先问自己一个问题:“我想要的完美输出结果是什么样子的?”

对于我们的案例,目标非常明确:一个CSV格式的文件。我们进一步定义了它的“骨架”——也就是列的名称和功能:

  • id: 唯一的数字ID,方便排序和引用。
  • name: 线路名称,需要能体现层级关系(例如“川藏线-川藏北线-南路”)。
  • start_point: 一段路径的起点。
  • end_point: 一段路径的终点。
  • start_lon, start_lat, end_lon, end_lat: 预留的经纬度坐标字段,暂时留空。

要点在动手前,先在脑海里或纸上画出你想要的表格。这是整个流程的基石。

第二步:准备你的原始数据 (Prepare the Raw Material)

将你需要处理的全部文本资料整理好。对于我们的案例,就是那份关于茶马古道的完整层级列表。

  • 完整性:确保没有遗漏任何部分。
  • 格式:尽量保持原始的格式,比如缩进和编号,因为这些格式本身就包含了AI可以理解的逻辑关系。

然后,将这些数据完整地复制下来,准备在下一步中“喂”给AI。

第三步:设计核心指令 (Craft the Perfect Prompt)

这是将想法变为现实的关键一步。一个高质量的指令通常包含以下几个要素:

  1. 角色扮演 (Set the Role):虽然不是必须,但告诉AI扮演一个角色(如“你是一个数据处理专家”)能帮助它更好地进入状态。

  2. 核心任务 (State the Task):用最直接的语言告诉它要做什么。

    “请帮我整理以下文本...”
    “我需要你将下面的资料转换成CSV格式...”

  3. 提供输入 (Provide the Input):紧接着核心任务,将你在第二步准备好的原始数据粘贴进去。

  4. 定义输出(The Secret Sauce):这是最关键的部分。你需要在这里给出关于输出格式的一切细节。回顾我们在第一步中定义的目标,然后把它变成清晰的指令。

    • 指定格式类型“...变成一个CSV文件”
    • 定义列名和顺序“CSV需要包含以下几列:id, name, start_point, start_lon, start_lat, end_point, end_lon, end_lat”
    • 设定处理规则
      • 路径拆分规则“像‘A——B——C’这样的路径,应该被拆分成‘A到B’和‘B到C’两条记录。” 这是将连续路径拆分为独立路段的核心指令。
      • 层级命名规则“请注意原文的层级体系,并在‘name’列中体现出来,例如‘主线-支线-子支线’。”
      • 内容处理规则“请使用中文线路名称”“确保所有地名都被包含进去”
      • 留空规则“我之后会自己填写坐标,所以经纬度列请留空。”
    • 格式化规则“请使用逗号作为分隔符。”

【最终指令模板】

将以上要素组合起来,就形成了我们这次任务的完美指令:

(核心任务)好好整理一下,(定义输出格式)变成id, name, start_point, start_lon, start_lat, end_point, end_lon, end_lat这样的CSV,(设定规则)这样的话就可以确定好转向点和对应的线路了,请确保所有翻译正确或者使用中文线路名称。同时确保所有地名都填入其中。然后我来填写坐标情况。用逗号分隔。并且注意线路的层级体系,需要在名称中体现。

(提供输入)
[此处粘贴完整的茶马古道文本]

第四步:审查与迭代 (Review and Refine)

AI生成结果后,不要直接采纳。花几分钟时间快速审查:

  • 格式是否正确? 是不是逗号分隔的CSV?列名和顺序对吗?
  • 数据是否完整? 开头和结尾的几条线路是否都已转换?随机抽查中间的几个复杂分支,看有没有遗漏。
  • 规则是否被遵守? name列的层级关系是否清晰?路径是否被正确拆分?

如果发现问题,不要从头开始。直接在当前对话中提出修正意见,例如:

  • “做得很好,但你似乎漏掉了第5.2.1节的内容,请补充进去。”
  • “格式基本正确,但请把name列的‘-’分隔符改成‘>’。”
  • “ID编号在第123行后中断了,请从那里继续编号。”

AI拥有上下文记忆能力,它能理解你的修正并快速生成新版本。

结论:从执行者到指挥家

通过这套方法论,你的角色从繁琐的数据录入员,转变成了定义规则、指挥AI工作的项目经理。这不仅极大地提升了效率,更重要的是,它将你的精力解放出来,让你能专注于数据本身所带来的洞察和价值。

如何与AI搭档,考证消失的古道?一份“茶马古道”地理探案实录与方法论

前言:当地理研究遇上人工智能

你是否曾面对一份尘封的史料,对上面那些早已消失在地平线上的古地名感到无从下手?你是否曾想绘制一幅古代商路的地图,却被一个个缺失的坐标点卡住进度?

在过去,这项工作需要翻阅大量的地方志、历史地图,进行繁琐的考证和比对。但今天,我们有了一个强大的新伙伴——像Google Gemini这样的大语言模型AI。

在过去的一段时间里,我以“茶马古道某线”的真实数据为蓝本,与Gemini进行了一场深度合作。我们不仅精准定位了数十个消失的古驿站,还勘误了原始数据中的错误,并深入探讨了古道走向背后的历史逻辑。这不仅是一次简单的问答,更形成了一套行之有效的“人机协作地理考证方法论”

今天,我将这套方法论分享出来,希望能为广大历史、地理爱好者和研究者提供一个全新的工具和思路。


第一阶段:夯实基础 —— “喂养”AI精确的框架

AI不是通灵者,你给它的信息越清晰、越有条理,它反馈的结果就越准确。

方法1:提供结构化的“骨架”数据。

不要用模糊的语言提问。把你手上的资料整理成清晰的表格或列表,即使数据是残缺的。

【我们的实践】
我最初提供给Gemini的就是类似CSV的格式:
路线名称, 起点, 起点经度, 起点纬度, 终点, 终点经度, 终点纬度
比如:茶马古道某道,凤翔,107.38,34.53,秦州,,

这样做的好处是,AI能立刻理解路线的连续性地理的上下文,而不是将“秦州”视为一个孤立的地名去查询。

方法2:先易后难,逐点突破。

不要指望一次性解决所有问题。从资料中已知信息最明确的部分开始提问,建立起一个可靠的地理锚点,然后逐步向未知区域探索。

【我们的实践】
我们从“某道”开始,这条路线上的“商县”、“商南”、“蓝田”等地名与现代行政区划关联性强,易于定位。这为后续考证更偏远的“某古路”等路线打下了基础。


第二阶段:深度互动 —— 把AI当成你的研究助理

这是整个方法论的核心。你需要把AI从一个“搜索引擎”升级为一个“研究助理”,通过深度互动来挖掘信息、验证逻辑。

方法3:利用上下文,进行“追问式”查询。

在AI给出初步答案后,利用我们已知的路线顺序进行追问。

【我们的实践】
在确定了“秦州”是天水后,我紧接着问“渭州呢?”。因为AI记得我们正在讨论的路线是 秦州 → 渭州 → 临洮,它会立刻将“渭州”的搜索范围限定在天水和临洮之间,从而精准地推断出是“渭源县”,而不是其他同名地点。

方法4:主动“纠错”,提供背景材料。

AI的知识库虽庞大,但可能缺乏某些垂直领域的最新研究或地方性史料。当你发现AI的回答与你掌握的资料有出入,或不够精确时,不要犹豫,直接把你的资料“喂”给它。

【我们的-经典案例】
我在定位“英武关”时,初步推断在姚安和祥云之间。这时,您找到了南华县官方发布的《鹦鹉关之战》一文,并将其内容发给了我。我立刻吸收了这篇文章的信息,将“英武关”的定位从一个模糊的“两县之间”精准到了“就在南华县境内”,并解释了其地理逻辑的合理性。

方法5:识别并质疑“不可能的路线”。

数据源本身可能是错的!当你发现AI根据数据生成的路线在地理上完全不合逻辑时,要敢于质疑。

【我们的-经典案例】
“某道”上的“南宁”是整个探案过程的高潮。无论是最初指向广西,还是后来坐标更新后指向昆明,都是地理上的“不可能三角”。通过向AI反复陈述其不可能性,我们共同得出了“‘南宁’是‘宁南’的笔误”这一核心结论,并最终让整条路线的逻辑变得完美。


第三阶段:逻辑升华 —— 探寻地理背后的“为什么”

当地图上的点都已各就各位,真正的探索才刚刚开始。你需要引导AI从“是什么”、“在哪里”上升到“为什么”。

方法6:探寻路线走向的深层逻辑。

【我们的实践】
在“某道”的终点,您发出了灵魂拷问:“那样的话怎么跑到元谋了呢?”,这个问题非常精彩。它引导我们共同探讨并得出了“支线(买马道)汇入主干道(蜀滇西路)”的结论。我们理解到,去元谋不是绕路,而是为了“上高速”,这让整条古道的网络结构和功能属性都变得立体和鲜活起来。


总结:人机协作,重焕历史地理研究的魅力

与Gemini的这次“茶马古道”探案之旅,让我深刻体会到新时代进行历史地理研究的无限可能。这个过程总结为以下心法:

  1. 你是“主导者”:你负责提供框架、背景、质疑和最终判断。
  2. AI是“超级助理”:它负责快速搜索、信息关联、初步推理和不知疲倦地响应你的各种“刁钻”问题。
  3. 迭代是“灵魂”:整个过程不是一蹴而就的,而是在“提问-回答-纠错-再提问”的循环中不断逼近真相。
  4. 逻辑是“准绳”:地理的连续性和历史的合理性是判断一切结论的最终标准。

希望这份方法论能给你启发。现在,不妨打开你手中的那些古老地图和文献,与你的AI助理一起,开始一场属于你自己的地理探案之旅吧!


利用 OSM Place Search 插件快速获取地理要素

在制作地图时,我们常常需要快速获取某个特定地理要素的矢量边界,例如某条河流的走向、某个省份的轮廓,或者某个城市的范围。除了寻找并下载专门的数据集,QGIS 提供了一个更快捷的办法——使用 OSM Place Search 插件。

这个插件基于 OpenStreetMap (OSM) 庞大的开放数据库和其强大的 Nominatim 搜索引擎,让你可以直接在 QGIS 内部搜索全球几乎任何一个地名,并将其几何图形(点、线或面)直接创建为图层。

安装插件

  1. 在 QGIS 菜单栏,点击 “插件” -> “管理并安装插件”
  2. 在弹出的对话框中,搜索 OSM Place Search
  3. 选中该插件,然后点击 “安装插件”。安装成功后,QGIS 工具栏上会出现一个新的放大镜图标。

核心用法

1. 激活与搜索

点击工具栏上的 “OSM Place Search” 图标(或通过 “插件” -> “OSM Place Search” -> “OSM place search”),会在 QGIS 界面右侧或下方激活搜索面板。

  • 在搜索框中输入您想查找的地名,例如 “长江”、“四川省” 或 “洱海”。
  • 勾选 “限在地图范围内” 选项,可以将搜索结果限制在当前地图视图内,这在处理重名地点时非常有用。
  • 点击搜索按钮,插件会列出所有相关的结果。

2. 交互与定位

  • 高亮显示:在结果列表中单击任意一项,地图视图会自动高亮显示该要素的地理位置和范围。
  • 缩放定位:双击列表中的某一项,地图会自动缩放并平移到该要素所在的最佳视图。

3. 关键功能:创建矢量图层

这是该插件最强大的功能。当您找到所需的目标后,可以直接将其转换为 QGIS 图层。

  1. 在搜索结果中,选中您想要的目标(例如“洱海”)。
  2. 点击面板下方的 “Create a layer from selection” 按钮(一个带加号的图层图标)。
  3. 插件会立即在您的图层列表中创建一个新的临时图层
    • 如果搜索的是“洱海”(一个湖泊),它会生成一个面(Polygon)图层。
    • 如果搜索的是“长江”,它会生成一个线(Line)图层。
    • 如果搜索的是一个城市,它可能会生成一个点(Point)或面(Polygon)图层。

重要提示:
通过这种方式创建的图层是临时图层(或称“草稿图层”),它的数据仅保存在内存中。关闭 QGIS 项目后,这个图层就会消失。

要将其永久保存,您必须:
右键点击该临时图层 -> 导出 (Export) -> 要素另存为... (Save Features As...)
然后选择一个格式(推荐 GeoPackage),指定文件名和保存位置。

进阶配置

插件允许用户传递高级参数给 Nominatim API。在插件面板的设置中,您可以配置特定参数。例如,通过设置 viewbox 参数,可以将搜索永久限定在一个地理边界框内,以提高搜索的相关性和速度。

viewbox=100.0,25.0,105.0,30.0  # 示例:将搜索限定在经度100-105,纬度25-30的区域内

更多高级参数可以查阅 Nominatim 官方文档

总结

OSM Place Search 是一个轻量级但功能强大的工具。当您需要快速获取单个、特定的地理要素作为参考或底图时,它能帮您省去寻找、下载和筛选大型数据集的繁琐过程,极大地提高了制图效率。

好的,这个插件是专门为中国用户服务的利器,对于加载天地图官方服务来说,比手动配置XYZ链接要方便得多。把它加入附录,对国内读者来说非常实用。

这是为您整理的详细介绍:


天地图工具 (TianDiTu Tools) 插件——一键接入国家地理信息服务

官方说明
https://qgis-plugin-tianditu.liuxs.pro/intro.html

对于国内用户而言,天地图(天地图官网:https://www.tianditu.gov.cn/) 是最权威、最标准的地理信息公共服务平台。为了方便在QGIS中无缝接入天地图的各项服务,有开发者专门制作了 “天地图工具” (TianDiTu Tools) 插件。它极大地简化了底图加载和API调用流程,是国内QGIS用户的必备利器。

核心功能

  • 一键添加底图: 无需手动配置XYZ链接,即可快速加载天地图提供的各种在线地图服务,包括矢量地图、卫星影像、地形图及其对应的中文标注图层。
  • 集成Web服务API: 将天地图的在线API功能直接集成到QGIS面板中,方便进行:
    • 地名搜索 (Place Search): 类似OSM Place Search,输入地名即可在全国范围内搜索并定位。
    • 地理编码 (Geocoding): 输入详细的中文地址,查询其对应的经纬度坐标。
    • 逆地理编码 (Reverse Geocoding): 在地图上点击任意一点,查询该点对应的中文地址信息。

PS:其实这个地名搜索不大好用,还是OSM的好用,但是底图是很有必要的。

安装与配置

  1. 安装插件

    • 在QGIS菜单栏,点击 “插件” -> “管理并安装插件”
    • 在搜索框中输入“天地图”或“tianditu”。
    • 找到“TianDiTu Tools”插件,点击安装。
    • 安装成功后,QGIS主工具栏会出现一组新的图标,通常包含“添加底图”、“搜索”和“设置”按钮。
  2. 设置天地图Key(关键步骤)

    • 获取Key: 天地图的所有服务都需要一个个人开发者Key(密钥)。请前往 天地图开发者控制台 ,使用个人信息免费注册并创建一个应用,申请类型请选择 “浏览器端”。创建成功后,您会得到一串由字母和数字组成的Key。
    • 配置插件:
      • 在QGIS中,点击“天地图工具”栏中的 “设置” 按钮(通常是一个齿轮图标)。
      • 在弹出的对话框中,将您申请到的Key粘贴进去。
      • 点击“保存并检查”,插件会验证Key的有效性。配置成功后即可使用全部功能。

使用方法

  1. 添加天地图底图

    • 点击工具栏上“添加底图”的下拉箭头。
    • 您会看到一个包含多种底图选项的列表,例如:
      • tianDiTu-vec (矢量地图)
      • tianDiTu-img (影像地图/卫星图)
      • tianDiTu-ter (地形图)
      • 以及对应的 cva (中文矢量注记)、cia (中文影像注记)、cta (中文地形注记) 图层。
    • 使用技巧: 通常需要加载两层,一个底图层 + 一个注记层。例如,先点击添加 tianDiTu-img,再点击添加 cia,这样您就能看到带中文标注的卫星影像了。请确保注记图层在图层列表的上方。
  2. 使用Web服务API

    • 点击工具栏上的 “搜索” 按钮(通常是一个放大镜图标),会在右侧或下方打开功能面板。
    • 面板包含三个选项卡:地名搜索、地理编码、逆地理编码
    • 地名搜索: 输入地名(如“故宫博物院”),点击搜索,下方会列出结果。
    • 地理编码: 输入更详细的地址(如“北京市东城区景山前街4号”),进行查询。
    • 逆地理编码: 激活该功能后,在地图上任意点击,即可查询该点的地址。
    • 交互: 对于搜索和编码的结果,双击列表中的某一项,或点击结果中的链接,插件会自动在地图上创建一个临时标记点,并缩放至该位置,非常方便。

总结

TianDiTu Tools 插件是连接QGIS与中国本土最权威地理信息服务的桥梁。相比手动配置XYZ或调用API,它提供了一个图形化的、一站式的解决方案。对于需要在中国范围内开展工作、且希望使用官方标准数据的用户来说,安装和使用这个插件,将极大地提升您的工作效率和数据可靠性。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇