├── tianditu_tools ├── widgets │ ├── __init__.py │ ├── AddMap │ │ ├── __init__.py │ │ ├── utils.py │ │ ├── extra_map.py │ │ ├── main.py │ │ └── sd.py │ ├── FitZoom │ │ ├── __init__.py │ │ └── main.py │ ├── Search │ │ ├── __init__.py │ │ └── main.py │ ├── Setting │ │ ├── __init__.py │ │ ├── main.py │ │ ├── mapmanager.py │ │ └── dialog.py │ ├── icons.py │ └── toolbar.py ├── images │ ├── icon.png │ ├── map_icons │ │ ├── ESRI.webp │ │ ├── osm.webp │ │ ├── stamen_toner.webp │ │ ├── stamen_terrain.webp │ │ ├── googlemap_default.png │ │ ├── googlemap_terrain.png │ │ ├── stamen_watercolor.webp │ │ ├── googlemap_satellite.png │ │ ├── amap.svg │ │ └── default.svg │ ├── fitzoom.svg │ ├── search.svg │ ├── add_map.svg │ ├── setting.svg │ ├── earth.svg │ └── map_tianditu.svg ├── __init__.py ├── maps │ ├── summary.yml │ └── extra.yml ├── plugin.py ├── qgis_utils.py ├── Styles │ ├── terrain.qml │ └── PointStyle_316.qml ├── metadata.txt ├── compat.py ├── utils.py └── ui │ ├── sd.ui │ ├── sd.py │ ├── search.ui │ ├── sd_6.py │ ├── search.py │ ├── search_6.py │ ├── setting.py │ ├── setting.ui │ └── setting_6.py ├── docs ├── notes.md ├── images │ ├── 菜单.png │ ├── 设置.png │ ├── 地名搜索.webp │ ├── 地点标记.jpg │ ├── 地理编码查询.webp │ ├── 逆地理编码查询.webp │ └── PixPin_2024-01-10_16-13-41.png ├── public │ ├── logo.png │ ├── favicon.ico │ └── logo.svg ├── .vitepress │ ├── theme │ │ ├── index.js │ │ └── custom.css │ └── config.mjs ├── 天地图Web服务API.md ├── 通过 XYZ Tiles 添加天地图.md ├── index.md ├── intro.md └── 天地图省级节点加载.md ├── .pylintrc ├── pyproject.toml ├── package.json ├── .github ├── ISSUE_TEMPLATE │ ├── 02_feature_request.yml │ └── 01_report_bugs.yml └── workflows │ ├── commit.yml │ ├── pylint.yml │ └── release.yml ├── update_ui.py ├── README.md ├── pack.py ├── .gitignore └── uv.lock /tianditu_tools/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Notes 6 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/AddMap/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import AddMapBtn 2 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/FitZoom/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import FitZoomAction 2 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Search/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import SearchAction 2 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Setting/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import SettingAction 2 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | disable = E0401,C0103,C0114,C0115,C0116,R0903,R0914 3 | -------------------------------------------------------------------------------- /docs/images/菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/菜单.png -------------------------------------------------------------------------------- /docs/images/设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/设置.png -------------------------------------------------------------------------------- /docs/images/地名搜索.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/地名搜索.webp -------------------------------------------------------------------------------- /docs/images/地点标记.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/地点标记.jpg -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/public/logo.png -------------------------------------------------------------------------------- /docs/images/地理编码查询.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/地理编码查询.webp -------------------------------------------------------------------------------- /docs/images/逆地理编码查询.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/逆地理编码查询.webp -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /tianditu_tools/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/icon.png -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme"; 2 | import "./custom.css"; 3 | 4 | export default DefaultTheme; 5 | -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/ESRI.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/ESRI.webp -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/osm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/osm.webp -------------------------------------------------------------------------------- /docs/images/PixPin_2024-01-10_16-13-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/docs/images/PixPin_2024-01-10_16-13-41.png -------------------------------------------------------------------------------- /tianditu_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .plugin import TianDiTu 2 | 3 | 4 | def classFactory(iface): 5 | """QGIS Plugin""" 6 | return TianDiTu(iface) 7 | -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/stamen_toner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/stamen_toner.webp -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/stamen_terrain.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/stamen_terrain.webp -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/googlemap_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/googlemap_default.png -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/googlemap_terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/googlemap_terrain.png -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/stamen_watercolor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/stamen_watercolor.webp -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/googlemap_satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuxspro/qgis-plugin-tianditu/HEAD/tianditu_tools/images/map_icons/googlemap_satellite.png -------------------------------------------------------------------------------- /tianditu_tools/maps/summary.yml: -------------------------------------------------------------------------------- 1 | extra: 2 | id: extra 3 | name: 其他地图 4 | lastUpdated: '2025-06-07 10:14:11' 5 | tianditu_province: 6 | id: tianditu_province 7 | name: 天地图省级节点 8 | lastUpdated: '2025-06-09 21:49:24' 9 | -------------------------------------------------------------------------------- /docs/天地图Web服务API.md: -------------------------------------------------------------------------------- 1 | # 天地图 Web 服务 API 2 | 3 | 4 | 5 | 6 | ## 地名搜索 7 | 8 | 9 | 10 | ### 例外 11 | 12 | 搜索`eqwewq12321` 13 | 返回 14 | ```json 15 | {"count": "0", "resultType": 1, "status": {"cndesc": "服务正常", "infocode": 1000}, "keyWord": "eqwewq12321"} 16 | ``` -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-home-hero-name-color: transparent; 3 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff); 4 | --vp-home-hero-image-background-image: linear-gradient(-45deg, #dd95ffc9 50%, #47caff 50%); 5 | --vp-home-hero-image-filter: blur(78px); 6 | } 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "qgis-plugin-tianditu" 3 | version = "0.5.4" 4 | description = "QGIS 天地图工具,方便进行天地图瓦片底图的添加以及简单实现了部分天地图 Web 服务 API(地名搜索、地理编码查询、逆地理编码查询) " 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = ["pyqt5>=5.15.11", "pyqt6>=6.8.1"] 8 | 9 | 10 | [tool.uv] 11 | # 为项目的依赖项添加额外的约束条件 12 | constraint-dependencies = ["pyqt5-qt5 <=5.15.2; sys_platform == 'win32'"] 13 | # PyQt5-Qt5 这个包只有 5.15.2 还支持 Windows 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qgis-plugin-tianditu-docs", 3 | "version": "0.5.5", 4 | "description": "documents of tianditu-tools plugin", 5 | "main": "index.js", 6 | "scripts": { 7 | "docs:dev": "vitepress dev docs", 8 | "docs:build": "vitepress build docs", 9 | "docs:preview": "vitepress preview docs" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "GPL-3.0", 14 | "devDependencies": { 15 | "vitepress": "1.0.0-rc.36" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tianditu_tools/plugin.py: -------------------------------------------------------------------------------- 1 | from .widgets.toolbar import TiandituToolbar 2 | 3 | 4 | class TianDiTu: 5 | def __init__(self, iface): 6 | self.iface = iface 7 | self.toolbar = TiandituToolbar(self.iface) 8 | 9 | def initGui(self): 10 | self.iface.addToolBar(self.toolbar) 11 | 12 | def unload(self): 13 | """Unload from the QGIS interface""" 14 | self.toolbar.remove_dock() 15 | mw = self.iface.mainWindow() 16 | mw.removeToolBar(self.toolbar) 17 | self.toolbar.deleteLater() 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 功能请求🙋‍♂️ 2 | description: 功能请求 3 | labels: 4 | - 'Feature Request' 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您抽出时间正确填写这份功能请求报告。 10 | 11 | - type: textarea 12 | id: what 13 | attributes: 14 | label: 功能描述 15 | description: | 16 | 描述所要实现的功能 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | id: additional-context 22 | attributes: 23 | label: 其他补充描述 24 | description: | 25 | 其他补充描述 -------------------------------------------------------------------------------- /.github/workflows/commit.yml: -------------------------------------------------------------------------------- 1 | name: 'Commit CI' 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'dev' 7 | 8 | jobs: 9 | build: 10 | name: 'build' 11 | runs-on: 'ubuntu-latest' 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install uv 16 | uses: astral-sh/setup-uv@v5 17 | with: 18 | version: "0.6.0" 19 | 20 | - name: Set up Python 21 | run: uv python install 22 | 23 | - name: 'Build and Pack' 24 | run: | 25 | chmod +x ./build.sh 26 | ./build.sh 27 | 28 | - name: Upload Artifact 29 | uses: actions/upload-artifact@v4 30 | with: 31 | path: ./dist/*.zip -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | Pylint: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ['3.9', '3.12' ] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pylint 21 | - name: Analysing the code with pylint 22 | run: | 23 | pylint $(git ls-files '*.py' ':(exclude)tianditu_tools/ui/') 24 | -------------------------------------------------------------------------------- /docs/通过 XYZ Tiles 添加天地图.md: -------------------------------------------------------------------------------- 1 | # 通过 XYZ Tiles 添加天地图 2 | 3 | 天地图的 XYZ 链接为: 4 | 5 | `http://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={z}&TileRow={y}&TileCol={x}&style=default&format=tiles&tk=<自己申请的key>` 6 | 7 | 以往, 直接填入这一串 URL 就可以正常访问天地图了. 但不知从什么时候开始,加载底图就不行了,原因天地图是启用了防盗链, 需要验证 Referer 后才能正常返回瓦片, 否则就是返回疑似攻击. 8 | 9 | 所以, 在填天地图 XYZ Tiles 的时候, 需要将 Referer 栏(被翻译成“引用”)填入天地图的官网 `https://www.tianditu.gov.cn` 即可正常加载天地图. 10 | 11 | ![天地图 XYZ Tiles](./images/PixPin_2024-01-10_16-13-41.png) 12 | 13 | --- 14 | 15 | 参考资料: 16 | 17 | 1. [通过QGIS XYZ Tiles访问国内四大图商地图服务](https://mp.weixin.qq.com/s/V4yI1yqzGSR1M8oqEn0wbA) 18 | 2. [QGIS文章一 —— 实现天地图加载](https://mp.weixin.qq.com/s/6ZUnTNYftIkPDGpV7UxXgA) 19 | -------------------------------------------------------------------------------- /tianditu_tools/images/fitzoom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Setting/main.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtWidgets import QAction 2 | 3 | from .dialog import SettingDialog 4 | from ..icons import icons 5 | from ...compat import IS_QT5, IS_QT6 6 | 7 | 8 | class SettingAction(QAction): 9 | def __init__( 10 | self, 11 | toolbar, 12 | parent=None, 13 | ): 14 | super().__init__(parent) 15 | self.setIcon(icons["setting"]) 16 | self.setText("设置") 17 | self.triggered.connect(self.show_setting_dialog) 18 | self.toolbar = toolbar 19 | 20 | def show_setting_dialog(self): 21 | dlg = SettingDialog(toolbar=self.toolbar) 22 | if IS_QT5: 23 | dlg.exec_() 24 | if IS_QT6: 25 | dlg.exec() 26 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/icons.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtGui import QIcon 2 | 3 | from ..utils import PluginDir 4 | 5 | icon_dict = { 6 | "setting": "./images/setting.svg", 7 | "add": "./images/add_map.svg", 8 | "map": "./images/map_tianditu.svg", 9 | "other": "./images/earth.svg", 10 | "search": "./images/search.svg", 11 | "fitzoom": "./images/fitzoom.svg", 12 | } 13 | 14 | icons = {key: QIcon(str(PluginDir.joinpath(value))) for key, value in icon_dict.items()} 15 | 16 | map_icon_folder = PluginDir.joinpath("./images/map_icons") 17 | 18 | 19 | def get_extra_map_icon(name: str): 20 | if map_icon_folder.joinpath(name).exists(): 21 | return QIcon(str(map_icon_folder.joinpath(name))) 22 | return QIcon(str(map_icon_folder.joinpath("default.svg"))) 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'Release' 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: 'Release' 11 | runs-on: 'ubuntu-latest' 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install uv 16 | uses: astral-sh/setup-uv@v5 17 | with: 18 | version: "0.6.0" 19 | 20 | - name: Set up Python 21 | run: uv python install 22 | 23 | - name: 'Build and Pack' 24 | run: | 25 | chmod +x ./build.sh 26 | ./build.sh 27 | 28 | - name: Create GitHub release 29 | id: release 30 | env: 31 | GH_TOKEN: ${{ github.token }} 32 | run: gh release create ${{ github.ref_name }} ./dist/*.zip -t "Release ${{ github.ref_name }}" --generate-notes -d -------------------------------------------------------------------------------- /tianditu_tools/images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /update_ui.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | cwd = Path.cwd() 4 | ui_dir = cwd.joinpath("tianditu_tools/ui") 5 | 6 | 7 | def update_ui_genate_py(file_path: Path): 8 | assert file_path.suffix == ".py" 9 | content = file_path.read_text(encoding="utf-8") 10 | content = content.replace("from PyQt5", "from qgis.PyQt") 11 | content = content.replace("from PyQt6", "from qgis.PyQt") 12 | file_path.write_text(content, encoding="utf-8") 13 | 14 | 15 | def update_file(): 16 | # 遍历目录下的所有 .py 文件 17 | for file_path in ui_dir.glob("**/*.py"): 18 | print(f"正在处理文件: {file_path}") 19 | try: 20 | update_ui_genate_py(file_path) 21 | except Exception as e: # pylint: disable=broad-exception-caught 22 | print(f"处理文件 {file_path} 时出错: {e}") 23 | 24 | 25 | update_file() 26 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: "Tianditu Tools", 6 | description: "QGIS Plugin", 7 | head: [["link", { rel: "icon", href: "/favicon.ico" }]], 8 | themeConfig: { 9 | // https://vitepress.dev/reference/default-theme-config 10 | logo: "/logo.svg", 11 | nav: [ 12 | { text: "主页", link: "/" }, 13 | // { text: "开发笔记", link: "https://liuxs.pro" }, 14 | ], 15 | 16 | sidebar: [ 17 | { 18 | text: "使用说明", 19 | items: [ 20 | { text: "简介", link: "/intro" }, 21 | { text: "通过 XYZ Tiles 添加天地图", link: "/通过 XYZ Tiles 添加天地图" }, 22 | { text: "天地图省级节点加载", link: "/天地图省级节点加载" }, 23 | ], 24 | }, 25 | ], 26 | 27 | socialLinks: [{ icon: "github", link: "https://github.com/liuxsdev/qgis-plugin-tianditu" }], 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/amap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Tianditu Tools" 7 | text: "QGIS Plugin" 8 | tagline: 快速添加天地图地图至 QGIS 9 | image: 10 | src: /logo.svg 11 | width: 100% 12 | alt: Tianditu Tools 13 | actions: 14 | - theme: brand 15 | text: 使用说明 16 | link: /intro 17 | - theme: alt 18 | text: View on GitHub 19 | link: https://github.com/liuxsdev/qgis-plugin-tianditu 20 | - theme: alt 21 | text: View on QGIS Plugins Repository 22 | link: https://plugins.qgis.org/plugins/tianditu-tools/ 23 | 24 | features: 25 | - title: 天地图底图 26 | icon: 🗺️ 27 | details: 只需要填入天地图 Key,无需自己拼接天地图 URL 28 | - title: 天地图 API 29 | icon: 🔍 30 | details: 在QGIS中进行天地图地名搜索、地理编码查询、逆地理编码查询 31 | - title: 其他图源 32 | icon: 🌍️ 33 | details: 收集了部分 QGIS 能加载的天地图省级节点图源以及常见的第三方图源(谷歌、高德、ESRI等) 34 | - title: 完全开源 35 | icon: ☕ 36 | details: 代码已经在 Github 开源,喜欢的话,可以点个 Star ✨ 37 | --- 38 | 39 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | # TianDiTu Tools 2 | 3 | QGIS 天地图工具,方便进行天地图瓦片底图的添加以及简单实现了部分[天地图 Web 服务 API](http://lbs.tianditu.gov.cn/server/guide.html)(地名搜索、地理编码查询、逆地理编码查询) 4 | 5 | ## 安装 6 | 7 | 从 QGIS 插件中,搜索 “天地图” 或者 “tianditu” , 找到插件安装即可. 8 | 9 | 安装插件后,可在 QGIS 工具栏中看到工具按钮,按钮功能分别为添加底图、搜索、以及设置. 10 | 11 | ## 设置天地图 Key 12 | 13 | 使用前需要设置天地图 Key,点击设置按钮,输入 key,保存并检查 14 | 15 | ![设置](./images/设置.png) 16 | 17 | ::: tip 18 | 天地图 key 需要到 [天地图控制台](https://console.tianditu.gov.cn/api/key) 去申请,申请的类型为 “浏览器端” 或者 “Android平台” 19 | ::: 20 | 21 | ## 天地图底图添加 22 | 23 | 在工具栏下拉菜单中选择底图,点击即可添加到当前工程中。 24 | 25 | 26 | 27 | ## 天地图 Web 服务 API 28 | 29 | | 地名搜索 | 地理编码 | 逆地理编码 | 30 | | :----------------------------: | :------------------------------------: | :----------------------------------------: | 31 | | ![地名搜索](./images/地名搜索.webp) | ![地理编码查询](./images/地理编码查询.webp) | ![逆地理编码查询](./images/逆地理编码查询.webp) | 32 | 33 | 双击结果项(或点击链接)可添加至当前地图中: 34 | 35 | ![地点标记](./images/地点标记.jpg) 36 | -------------------------------------------------------------------------------- /tianditu_tools/images/add_map.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_report_bugs.yml: -------------------------------------------------------------------------------- 1 | name: 报告插件错误❌ 2 | description: 报告你使用插件过程中遇到的异常 3 | labels: 4 | - 'Bug' 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您抽出时间正确填写这份错误报告。 10 | 11 | - type: textarea 12 | id: what 13 | attributes: 14 | label: 请描述您遇到的问题? 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | id: steps 20 | attributes: 21 | label: 如何重现您的问题 22 | description: | 23 | 重现问题的步骤 24 | 1. 25 | 2. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: version-info 31 | attributes: 32 | label: 您使用的QGIS版本以及电脑系统版本 33 | description: | 34 | 您使用的QGIS版本以及电脑系统版本, 如 Windows 11 23H2, QGIS 3.34.2 35 | validations: 36 | required: true 37 | 38 | - type: checkboxes 39 | id: self-check 40 | attributes: 41 | label: 自查清单 42 | description: | 43 | 请您自查以下问题 (是则勾选) 44 | options: 45 | - label: 通过XYZ Tiles的方式能否能正常加载天地图. 46 | - label: 访问[天地图官网地图](https://www.tianditu.gov.cn/)是否正常 47 | 48 | - type: textarea 49 | id: additional-context 50 | attributes: 51 | label: 其他补充描述 52 | description: | 53 | 其他补充描述 -------------------------------------------------------------------------------- /tianditu_tools/qgis_utils.py: -------------------------------------------------------------------------------- 1 | from qgis.core import QgsProject, QgsRasterLayer, QgsMessageLog, Qgis 2 | 3 | 4 | def push_message(iface, title: str, message: str): 5 | """ 6 | 将具有默认超时时间的信息推送到消息栏。 7 | https://qgis.org/pyqgis/3.44/gui/QgsMessageBar.html#qgis.gui.QgsMessageBar.pushInfo 8 | Args: 9 | iface : iface 10 | title (str): 消息标题 11 | message (str): 消息 12 | """ 13 | iface.messageBar().pushInfo( 14 | title, 15 | message, 16 | ) 17 | 18 | 19 | def push_warning(iface, title: str, message: str): 20 | iface.messageBar().pushWarning( 21 | title, 22 | message, 23 | ) 24 | 25 | 26 | def add_raster_layer(uri: str, name: str, provider_type: str = "wms"): 27 | """QGIS 添加栅格图层 28 | 29 | Args: 30 | uri (str): 栅格图层 uri 31 | name (str): 栅格图层名称 32 | provider_type(str): 栅格图层类型(wms,arcgismapserver) 33 | Reference: https://qgis.org/pyqgis/3.32/core/QgsRasterLayer.html 34 | """ 35 | raster_layer = QgsRasterLayer(uri, name, provider_type) 36 | if raster_layer.isValid(): 37 | QgsProject.instance().addMapLayer(raster_layer) 38 | return raster_layer 39 | log_message(f"无效的图层 Invalid Layer: \n{uri}") 40 | return None 41 | 42 | 43 | def log_message(message: str): 44 | QgsMessageLog.logMessage(message, "Tianditu-Tools", Qgis.Info) 45 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/toolbar.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtWidgets import QToolBar 2 | 3 | from .AddMap import AddMapBtn 4 | from .FitZoom import FitZoomAction 5 | from .Search import SearchAction 6 | from .Setting import SettingAction 7 | from .icons import icons 8 | from ..utils import PluginConfig 9 | 10 | 11 | class TiandituToolbar(QToolBar): 12 | def __init__(self, iface, parent=None) -> None: 13 | super().__init__("Tianditu Tools 工具栏", parent) 14 | self.iface = iface 15 | self.icons = icons 16 | self.conf = PluginConfig() 17 | self.setToolTip("天地图 Tools 工具栏") 18 | self.add_button = None 19 | self.actions = [] 20 | 21 | self.init_config() 22 | self.setup_action() 23 | 24 | def setup_action(self): 25 | self.add_button = AddMapBtn(iface=self.iface, parent=self) 26 | self.addWidget(self.add_button) 27 | # 添加 Action 28 | action_setting = SettingAction(self) 29 | action_search = SearchAction(iface=self.iface, parent=self) 30 | action_fitzoom = FitZoomAction(iface=self.iface, parent=self) 31 | 32 | self.actions.extend([action_setting, action_search, action_fitzoom]) 33 | self.addActions(self.actions) 34 | 35 | def init_config(self): 36 | self.conf.init_config() 37 | 38 | def remove_dock(self): 39 | dock = self.actions[1] 40 | dock.unload() 41 | -------------------------------------------------------------------------------- /tianditu_tools/Styles/terrain.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | None 18 | WholeRaster 19 | Estimated 20 | 0.02 21 | 0.98 22 | 2 23 | 24 | 25 | 26 | 27 | 28 | resamplingFilter 29 | 30 | 6 31 | 32 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/AddMap/utils.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote, urlencode 2 | 3 | from qgis.core import Qgis 4 | 5 | 6 | def parse_referer(referer): 7 | if not referer: 8 | return "" 9 | # Determine the correct parameter name based on the QGIS version 10 | param_name = "http-header:referer" if Qgis.QGIS_VERSION_INT >= 32600 else "referer" 11 | return f"&{param_name}={referer}" 12 | 13 | 14 | def get_xyz_uri(url: str, zmin: int = 0, zmax: int = 18, referer: str = "") -> str: 15 | """返回 XYZ Tile 地图 uri 16 | 17 | Args: 18 | url (str): 瓦片地图 url 19 | zmin (int, optional): z 最小值. Defaults to 0. 20 | zmax (int, optional): z 最大值 Defaults to 18. 21 | referer (str, optional): Referer. Defaults to "". 22 | 23 | Returns: 24 | str: 瓦片地图uri 25 | """ 26 | # "?" 进行 URL 编码后, 在 3.34 版本上无法加载地图 27 | # "&" 是必须要进行 url 编码的 28 | url = quote(url, safe=":/?=") 29 | parsed_referer = parse_referer(referer) 30 | uri = f"type=xyz&url={url}&zmin={zmin}&zmax={zmax}{parsed_referer}" 31 | return uri 32 | 33 | 34 | def get_wmts_uri(uri, referer=""): 35 | parsed_referer = parse_referer(referer) 36 | return uri + parsed_referer 37 | 38 | 39 | def _gen_wmts_uri(crs, img_format, layers, matrix, url): 40 | params = { 41 | "crs": crs, 42 | "format": img_format, 43 | "layers": layers, 44 | "styles": "default", 45 | "tileMatrixSet": matrix, 46 | "tilePixelRatio": 0, 47 | "url": url, 48 | } 49 | query_string = urlencode(params, safe=":/") 50 | return query_string 51 | -------------------------------------------------------------------------------- /tianditu_tools/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=TianDiTu Tools 3 | qgisMinimumVersion=3.4 4 | qgisMaximumVersion=4.99 5 | description=天地图底图加载 6 | about=QGIS 天地图工具,方便进行天地图瓦片底图的添加以及简单实现了部分天地图 Web 服务 API(地名搜索、地理编码查询、逆地理编码查询) 7 | version=0.5.5 8 | tags=basemap, xyz, tianditu, geocoder, 天地图 9 | icon=images/icon.png 10 | experimental=False 11 | deprecated=False 12 | supportsQt6=yes 13 | author=liuxspro 14 | email=liuxspro@gmail.com 15 | homepage=https://qgis-plugin-tianditu.liuxs.pro/ 16 | tracker=https://github.com/liuxspro/qgis-plugin-tianditu/issues 17 | repository=https://github.com/liuxspro/qgis-plugin-tianditu 18 | changelog= 19 | Version 0.5.5 2025/8/28: 20 | * Fix: macOS上初始化插件的错误 21 | Version 0.5.4 2025/8/16: 22 | * QGIS 4 Ready 23 | * ✨ 新增天地图山体阴影 24 | * 🐛 Fix:山东天地图影像注记 25 | Version 0.5.3 2025/6/9: 26 | * 🐛 修复地图加载问题 27 | Version 0.5.2 2025/5/9: 28 | * ✨ 逆地理编码增加坐标拾取 29 | * 🐛 修复山东历史影像 30 | Version 0.5.1 2025/2/18: 31 | * ✨ QT6 Support 32 | Version 0.5.0 2024/12/18: 33 | * ✨ 新增功能: 支持山东天地图历史影像 34 | Version 0.4.2 2024/08/17: 35 | * 🐛 BUG修复及搜索功能优化 36 | Version 0.4.1 2024/04/15: 37 | * 🐛 修复3.18及以下版本上无法加载插件 38 | Version 0.4.0 2024/01/16: 39 | * ✨ 新增功能: 支持多个天地图key 40 | * ✨ 新增功能: 地图管理和更新 41 | Version 0.3.1 2023/11/17: 42 | * 🐛 修复3.34版本上无法加载地图 43 | Version 0.3.0 2023/7/1: 44 | * ✨ 新增功能:调整缩放比例至正确的 Zoom Level 45 | * 💄 更换图标 46 | Version 0.2.1 2023/5/25: 47 | * 🐛 修复地名搜索不正常/添加地名点时图标丢失 48 | Version 0.2.0 2023/5/5: 49 | * 💥 更改配置文件到 QGIS Setting中 50 | * ✨ 新增天地图·江苏 51 | * ✨ 新增底图(stamen/esri) 52 | * 🐛 修复一些问题 53 | Version 0.1.1 2023/4/29: 54 | * ✨ 增加子域选择 55 | * 🐛 修复一些问题 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TianDiTu Tools 2 | 3 | [![Version](https://img.shields.io/badge/dynamic/xml?color=blue&label=Version&query=%2F%2Fpyqgis_plugin%5B%40name%3D%27TianDiTu%20Tools%27%5D%2Fversion%2Ftext%28%29&url=https%3A%2F%2Fplugins.qgis.org%2Fplugins%2Fplugins.xml%3Fqgis%3D3.30)](https://plugins.qgis.org/plugins/tianditu-tools/) 4 | [![Downloads](https://img.shields.io/badge/dynamic/xml?color=success&label=Downloads&query=%2F%2Fpyqgis_plugin%5B%40name%3D%27TianDiTu%20Tools%27%5D%2Fdownloads%2Ftext%28%29&url=https%3A%2F%2Fplugins.qgis.org%2Fplugins%2Fplugins.xml%3Fqgis%3D3.30)](https://plugins.qgis.org/plugins/tianditu-tools/) 5 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 6 | 7 | QGIS 天地图工具,方便进行天地图瓦片底图的添加以及简单实现了部分[天地图 Web 服务 API](http://lbs.tianditu.gov.cn/server/guide.html)(地名搜索、地理编码查询、逆地理编码查询) 8 | 9 | ## 使用说明 10 | 11 | 安装插件后,可在 QGIS 工具栏中看到工具按钮,按钮功能分别为添加底图、搜索、以及设置 12 | 13 | ### 设置天地图 Key 14 | 15 | 使用前需要设置天地图 Key,输入 key,点击保存按钮 16 | 17 | 可以保存多个key,选择使用或者随机选用key. 18 | 19 | ![设置](docs/images/设置.png) 20 | 21 | > 天地图 key 需要到 [天地图控制台](https://console.tianditu.gov.cn/api/key) 去申请,申请的类型为“浏览器端” 或者 “Android平台” 22 | 23 | ### 天地图底图添加 24 | 25 | 在工具栏下拉菜单中选择底图,点击即可添加到当前工程中。 26 | 27 | 28 | 29 | ### 天地图 Web 服务 API 30 | 31 | | 地名搜索 | 地理编码 | 逆地理编码 | 32 | | :----------------------------: | :------------------------------------: | :----------------------------------------: | 33 | | ![地名搜索](docs/images/地名搜索.webp) | ![地理编码查询](docs/images/地理编码查询.webp) | ![逆地理编码查询](docs/images/逆地理编码查询.webp) | 34 | 35 | 双击结果项(或点击链接)可添加至当前地图中: 36 | 37 | ![地点标记](docs/images/地点标记.jpg) 38 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Search/main.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtWidgets import QAction, QMessageBox 2 | 3 | from .searchDock import SearchDockWidget 4 | from ..icons import icons 5 | from ...compat import LeftDockWidgetArea 6 | from ...utils import PluginConfig 7 | 8 | conf = PluginConfig() 9 | 10 | 11 | class SearchAction(QAction): 12 | def __init__( 13 | self, 14 | iface, 15 | parent=None, 16 | ): 17 | super().__init__(parent) 18 | self.parent = parent 19 | self.iface = iface 20 | self.setIcon(icons["search"]) 21 | self.setText("搜索") 22 | self.searchdockwidget = SearchDockWidget(self.iface) 23 | self.searchdockwidget.visibilityChanged.connect(self.onDockVisibilityChanged) 24 | self.iface.addDockWidget(LeftDockWidgetArea, self.searchdockwidget) 25 | self.searchdockwidget.hide() 26 | self.setCheckable(True) 27 | self.triggered.connect(self.openSearch) 28 | 29 | def openSearch(self): 30 | key = conf.get_key() 31 | if key == "": 32 | QMessageBox.warning( 33 | self.parent, 34 | "错误", 35 | "天地图Key未设置或Key无效", 36 | QMessageBox.Yes, 37 | QMessageBox.Yes, 38 | ) 39 | self.setChecked(False) 40 | else: 41 | if self.searchdockwidget.isHidden(): 42 | self.searchdockwidget.show() 43 | else: 44 | self.searchdockwidget.hide() 45 | 46 | def onDockVisibilityChanged(self, is_visible): 47 | if not is_visible: 48 | self.setChecked(False) 49 | else: 50 | self.setChecked(True) 51 | 52 | def unload(self): 53 | self.iface.removeDockWidget(self.searchdockwidget) 54 | -------------------------------------------------------------------------------- /pack.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | 6 | # Define the source and destination directories 7 | cwd = Path.cwd() 8 | source_dir = cwd.joinpath("tianditu_tools") 9 | dist_dir = cwd.joinpath("dist") 10 | # same as original plugin package name (tianditu-tools). 11 | dist_source_dir = dist_dir.joinpath("tianditu-tools") 12 | 13 | # Other necessary files 14 | other_files = ["README.md", "LICENSE"] 15 | 16 | 17 | def delete_pycache(directory): 18 | pycache_dir = directory.joinpath("__pycache__") 19 | 20 | if pycache_dir.exists() and pycache_dir.is_dir(): 21 | shutil.rmtree(pycache_dir) 22 | 23 | for subdirectory in directory.iterdir(): 24 | if subdirectory.is_dir(): 25 | delete_pycache(subdirectory) 26 | 27 | 28 | # 删除dist文件夹 29 | if dist_dir.exists(): 30 | shutil.rmtree(dist_dir) 31 | # 递归删除 __pycache__ 文件夹 32 | delete_pycache(source_dir) 33 | # 复制源文件夹到 dist 目录下的 ianditu-tools 文件夹 34 | shutil.copytree(source_dir, dist_source_dir) 35 | # 删除ui文件夹的 ui 文件,只保留 python 文件 36 | for file_path in dist_source_dir.joinpath("ui").glob("*.ui"): 37 | file_path.unlink() 38 | # 复制 README 和 LICENSE 文件 39 | for file in other_files: 40 | shutil.copy(cwd.joinpath(file), dist_source_dir) 41 | # Read the metadata.txt file using configparser 42 | config = configparser.ConfigParser() 43 | config.read(source_dir.joinpath("metadata.txt"), encoding="utf-8") 44 | # Get the version under the [general] section 45 | version = config.get("general", "version") 46 | filename = f"tianditu_tools-{version}.zip" 47 | # Zip the destination directory 48 | with zipfile.ZipFile(dist_dir.joinpath(filename), "w", zipfile.ZIP_DEFLATED) as zipf: 49 | for file in dist_source_dir.glob("**/*"): 50 | zipf.write(file, file.relative_to(dist_source_dir.parent)) 51 | # 删除dist下的打包目录 tianditu_tools 52 | shutil.rmtree(dist_source_dir) 53 | print(f"完成打包 {filename}") 54 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/FitZoom/main.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtWidgets import QAction 2 | from qgis.core import QgsCoordinateReferenceSystem 3 | 4 | from ..icons import icons 5 | 6 | 7 | class FitZoomAction(QAction): 8 | def __init__(self, iface, parent=None): 9 | self.iface = iface 10 | super().__init__(parent) 11 | self.setIcon(icons["fitzoom"]) 12 | self.setText("调整缩放比例") 13 | self.triggered.connect(self.fit_zoom_level) 14 | self.iface.mapCanvas().destinationCrsChanged.connect(self.check_crs) 15 | self.iface.mapCanvas().layersChanged.connect(self.check_crs) 16 | self.check_crs() 17 | 18 | def fit_zoom_level(self): 19 | crs = self.iface.mapCanvas().mapSettings().destinationCrs() 20 | if crs == QgsCoordinateReferenceSystem("EPSG:3857"): 21 | max_zoom_level = 23 22 | mpp_3857 = [40075016.685 / (2**i * 256) for i in range(max_zoom_level)] 23 | current_mpp = self.iface.mapCanvas().mapUnitsPerPixel() 24 | nearest_level = self.find_nearest_number_index(mpp_3857, current_mpp) 25 | zoom_factor = mpp_3857[nearest_level] / current_mpp 26 | if not abs(1 - zoom_factor) < 1e-5: 27 | self.iface.mapCanvas().zoomByFactor(zoom_factor) 28 | 29 | @staticmethod 30 | def find_nearest_number_index(numbers_list, target): 31 | min_difference = float("inf") 32 | nearest_index = None 33 | for i, number in enumerate(numbers_list): 34 | difference = abs(number - target) 35 | if difference < min_difference: 36 | min_difference = difference 37 | nearest_index = i 38 | return nearest_index 39 | 40 | def check_crs(self): 41 | crs = self.iface.mapCanvas().mapSettings().destinationCrs() 42 | layers_number = self.iface.mapCanvas().layerCount() 43 | if crs == QgsCoordinateReferenceSystem("EPSG:3857") and layers_number > 0: 44 | self.setText("调整缩放比例") 45 | self.setEnabled(True) 46 | else: 47 | self.setText("调整缩放比例不可用") 48 | self.setEnabled(False) 49 | -------------------------------------------------------------------------------- /tianditu_tools/compat.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtCore import Qt, QT_VERSION_STR 2 | from qgis.PyQt.QtGui import QClipboard 3 | from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest 4 | from qgis.PyQt.QtWidgets import QToolButton 5 | 6 | QT_MAJOR_VERSION = int(QT_VERSION_STR.split(".")[0]) 7 | IS_QT5 = QT_MAJOR_VERSION == 5 8 | IS_QT6 = QT_MAJOR_VERSION == 6 9 | 10 | if IS_QT5: 11 | # Qt 5 版本 12 | from .ui.sd import Ui_SdDockWidget # noqa # pylint: disable=unused-import 13 | from .ui.search import Ui_SearchDockWidget # noqa # pylint: disable=unused-import 14 | from .ui.setting import Ui_SettingDialog # noqa # pylint: disable=unused-import 15 | 16 | # 枚举值改变 17 | LeftDockWidgetArea = Qt.LeftDockWidgetArea 18 | RightDockWidgetArea = Qt.RightDockWidgetArea 19 | AlignCenter = Qt.AlignCenter 20 | DescendingOrder = Qt.DescendingOrder 21 | Checked = Qt.Checked 22 | Unchecked = Qt.Unchecked 23 | MatchExactly = Qt.MatchExactly 24 | MenuButtonPopup = QToolButton.MenuButtonPopup 25 | NoError = QNetworkReply.NoError 26 | HttpStatusCodeAttribute = QNetworkRequest.HttpStatusCodeAttribute 27 | ModeClipboard = QClipboard.Clipboard 28 | 29 | if IS_QT6: 30 | # QT6 版本 31 | 32 | from .ui.sd_6 import Ui_SdDockWidget # noqa # pylint: disable=unused-import 33 | from .ui.setting_6 import Ui_SettingDialog # noqa # pylint: disable=unused-import 34 | from .ui.search_6 import ( # noqa # pylint: disable=unused-import 35 | Ui_SearchDockWidget, # noqa # pylint: disable=unused-import 36 | ) # noqa # pylint: disable=unused-import 37 | 38 | # 枚举值改变 39 | LeftDockWidgetArea = Qt.DockWidgetArea.LeftDockWidgetArea 40 | RightDockWidgetArea = Qt.DockWidgetArea.RightDockWidgetArea 41 | AlignCenter = Qt.AlignmentFlag.AlignCenter 42 | DescendingOrder = Qt.SortOrder.DescendingOrder 43 | Checked = Qt.CheckState.Checked 44 | Unchecked = Qt.CheckState.Unchecked 45 | MatchExactly = Qt.MatchFlag.MatchExactly 46 | MenuButtonPopup = QToolButton.ToolButtonPopupMode.MenuButtonPopup 47 | NoError = QNetworkReply.NetworkError.NoError 48 | HttpStatusCodeAttribute = QNetworkRequest.Attribute.HttpStatusCodeAttribute 49 | ModeClipboard = QClipboard.Mode.Clipboard 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea/ 6 | # C extensions 7 | *.so 8 | config.ini 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # node modules 133 | node_modules/ 134 | cache/ -------------------------------------------------------------------------------- /tianditu_tools/images/setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/AddMap/extra_map.py: -------------------------------------------------------------------------------- 1 | from qgis.PyQt.QtWidgets import QMenu 2 | 3 | from .sd import SdAction 4 | from .utils import get_xyz_uri 5 | from ..icons import icons, get_extra_map_icon 6 | from ...qgis_utils import add_raster_layer 7 | from ...utils import PluginDir, load_yaml, PluginConfig 8 | 9 | conf = PluginConfig() 10 | 11 | 12 | def add_map(data): 13 | name = data["name"] 14 | map_type = data.get("type") 15 | add_map_type = "wms" 16 | uri = data.get("uri", "") 17 | referer = data.get("referer", "") 18 | if uri == "": 19 | uri = get_xyz_uri(data["url"], data["zmin"], data["zmax"], referer) 20 | 21 | if map_type == "arcgismapserver": 22 | add_map_type = "arcgismapserver" 23 | 24 | add_raster_layer(uri, name, add_map_type) 25 | 26 | 27 | def add_tianditu_province_menu(parent_menu: QMenu, iface): 28 | # 增加山东天地图 29 | sd = SdAction(iface, parent=parent_menu) 30 | parent_menu.addAction(sd) 31 | parent_menu.addSeparator() 32 | # 其他省份 33 | tianditu_province_path = PluginDir.joinpath("maps/tianditu_province.yml") 34 | tianditu_province = load_yaml(tianditu_province_path)["maps"] 35 | maps = tianditu_province.keys() 36 | extra_maps_status = conf.get_extra_maps_status() 37 | for map_name in maps: 38 | # 一级菜单 省份名称 39 | if map_name in extra_maps_status["tianditu_province"]: 40 | add_map_action = parent_menu.addAction(icons["map"], map_name) 41 | map_data = tianditu_province[map_name] 42 | sub_menu = QMenu(parent_menu) 43 | for m in map_data: 44 | sub_menu.addAction( 45 | icons["map"], 46 | m["name"], 47 | lambda m_=m: add_map(m_), 48 | ) 49 | add_map_action.setMenu(sub_menu) 50 | parent_menu.addSeparator() 51 | 52 | 53 | def add_extra_map_menu(parent_menu: QMenu): 54 | extra = load_yaml(PluginDir.joinpath("maps/extra.yml")) 55 | extra_maps = extra["maps"] 56 | extra_root = parent_menu.addAction(icons["other"], "其他地图") 57 | extra_root_menu = QMenu(parent_menu) 58 | maps = extra_maps.keys() 59 | extra_maps_status = conf.get_extra_maps_status() 60 | for map_name in maps: 61 | if ( 62 | map_name in extra_maps_status["tianditu_province"] 63 | or map_name in extra_maps_status["extra"] 64 | ): 65 | map_data = extra_maps[map_name] 66 | sub_menu = extra_root_menu.addAction(icons["other"], map_name) 67 | sub_sub_menu = QMenu(parent_menu) 68 | for sub_map in map_data: 69 | sub_sub_menu.addAction( 70 | get_extra_map_icon(sub_map.get("icon", "default.svg")), 71 | sub_map["name"], 72 | lambda m_=sub_map: add_map(m_), 73 | ) 74 | sub_menu.setMenu(sub_sub_menu) 75 | extra_root.setMenu(extra_root_menu) 76 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/AddMap/main.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from qgis.PyQt.QtWidgets import QToolButton, QMenu, QMessageBox 4 | 5 | from .extra_map import add_tianditu_province_menu, add_extra_map_menu 6 | from .utils import get_xyz_uri 7 | from ..icons import icons 8 | from ...compat import MenuButtonPopup 9 | from ...qgis_utils import add_raster_layer 10 | from ...utils import TIANDITU_HOME_URL, PluginConfig, tianditu_map_url, PluginDir 11 | 12 | tianditu_map_info = { 13 | "vec": "天地图-矢量地图", 14 | "cva": "天地图-矢量注记", 15 | "img": "天地图-影像地图", 16 | "cia": "天地图-影像注记", 17 | "ter": "天地图-地形晕染", 18 | "cta": "天地图-地形注记", 19 | "ibo": "天地图-全球境界", 20 | } 21 | 22 | conf = PluginConfig() 23 | 24 | 25 | class AddMapBtn(QToolButton): 26 | def __init__(self, iface, parent=None): 27 | super().__init__(parent) 28 | self.iface = iface 29 | self.icons = icons 30 | self.setToolTip("添加地图") 31 | self.setup_action() 32 | 33 | def setup_action(self): 34 | menu = QMenu(self) 35 | menu.setObjectName("TianDiTuAddMap") 36 | for map_type, map_name in tianditu_map_info.items(): 37 | menu.addAction( 38 | self.icons["map"], 39 | map_name, 40 | lambda maptype_=map_type: self.add_tianditu_basemap(maptype_), 41 | ) 42 | # 山体阴影 43 | menu.addAction(self.icons["map"], "天地图-山体阴影", self.add_terrain_rgb) 44 | menu.addSeparator() 45 | # 天地图省级节点 46 | add_tianditu_province_menu(menu, self.iface) 47 | # 其他图源 48 | add_extra_map_menu(menu) 49 | self.setMenu(menu) 50 | self.setPopupMode(MenuButtonPopup) 51 | self.setIcon(self.icons["add"]) 52 | 53 | def add_terrain_rgb(self): 54 | key = conf.get_key() 55 | if key == "": 56 | QMessageBox.warning( 57 | self, 58 | "错误", 59 | "天地图Key未设置或Key无效", 60 | QMessageBox.Yes, 61 | QMessageBox.Yes, 62 | ) 63 | return 64 | map_url = tianditu_map_url("terrain-rgb", key, "") 65 | uri = get_xyz_uri(map_url, 1, 12, TIANDITU_HOME_URL) 66 | terrain_uri = "interpretation=maptilerterrain&" + uri 67 | terrain_layer = add_raster_layer(terrain_uri, "天地图-山体阴影") 68 | terrain_layer.loadNamedStyle(str(PluginDir.joinpath("./Styles/terrain.qml"))) 69 | 70 | def add_tianditu_basemap(self, maptype): 71 | key = conf.get_key() 72 | if key == "": 73 | QMessageBox.warning( 74 | self, 75 | "错误", 76 | "天地图Key未设置或Key无效", 77 | QMessageBox.Yes, 78 | QMessageBox.Yes, 79 | ) 80 | else: 81 | random_enabled = conf.get_bool_value("Tianditu/random") 82 | key_random_enabled = conf.get_bool_value("Tianditu/random_key") 83 | if random_enabled: 84 | subdomain = f"t{random.randint(0, 7)}" 85 | else: 86 | subdomain = conf.get_value("Tianditu/subdomain") 87 | if key_random_enabled: 88 | key = conf.get_random_key() 89 | map_url = tianditu_map_url(maptype, key, subdomain) 90 | uri = get_xyz_uri(map_url, 1, 18, TIANDITU_HOME_URL) 91 | add_raster_layer(uri, tianditu_map_info[maptype]) 92 | -------------------------------------------------------------------------------- /tianditu_tools/images/earth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tianditu_tools/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from random import choice 4 | 5 | import yaml 6 | from qgis.PyQt.QtCore import QUrl, QUrlQuery 7 | from qgis.PyQt.QtNetwork import QNetworkRequest 8 | from qgis.core import QgsSettings 9 | 10 | TIANDITU_HOME_URL = "https://www.tianditu.gov.cn/" 11 | PLUGIN_NAME = "tianditu-tools" 12 | 13 | PluginDir = Path(__file__).parent 14 | 15 | HEADER = { 16 | "User-Agent": "Mozilla/5.0 QGIS/32400/Windows 10 Version 2009", 17 | "Referer": "https://www.tianditu.gov.cn/", 18 | } 19 | 20 | APP_FONT = QgsSettings().value("app/fontFamily") 21 | 22 | 23 | def get_extramap_status(): 24 | summary = load_yaml(PluginDir.joinpath("maps/summary.yml")) 25 | default_status = {} 26 | for section in summary: 27 | section_data = load_yaml(PluginDir.joinpath(f"maps/{section}.yml")) 28 | default_status[section] = list(section_data["maps"].keys()) 29 | return default_status 30 | 31 | 32 | class PluginConfig: 33 | def __init__(self): 34 | self.conf = QgsSettings() 35 | self.conf_name = "tianditu-tools" 36 | self.section_tianditu = f"{self.conf_name}/Tianditu" 37 | 38 | def init_config(self): 39 | # 初始化配置文件 40 | if not self.conf.contains("tianditu-tools/Tianditu/key"): 41 | print("初始化配置文件") 42 | # 初始化 43 | self.conf.setValue(f"{self.section_tianditu}/key", "") 44 | self.conf.setValue(f"{self.section_tianditu}/keyList", "") 45 | self.conf.setValue(f"{self.section_tianditu}/random", True) 46 | self.conf.setValue(f"{self.section_tianditu}/random_key", False) 47 | self.conf.setValue(f"{self.section_tianditu}/subdomain", "t0") 48 | if not self.conf.contains("tianditu-tools/Other/extramap_status"): 49 | print("初始化 extra map 文件") 50 | self.conf.setValue( 51 | f"{self.conf_name}/Other/extramap_status", str(get_extramap_status()) 52 | ) 53 | # 升级到保存多个key的版本 54 | if not self.conf.contains(f"{self.section_tianditu}/keyList"): 55 | self.conf.setValue(f"{self.section_tianditu}/random_key", False) 56 | # 保存原来的key 57 | if self.get_key() != "": 58 | self.conf.setValue(f"{self.section_tianditu}/keyList", self.get_key()) 59 | else: 60 | self.conf.setValue(f"{self.section_tianditu}/keyList", "") 61 | 62 | def get_key_list(self): 63 | data_str = self.get_value("/Tianditu/keyList") 64 | if data_str == "": 65 | return [] 66 | return data_str.split(",") 67 | 68 | def save_key_list(self, data_list): 69 | self.conf.setValue(f"{self.section_tianditu}/keyList", ",".join(data_list)) 70 | 71 | def get_extra_maps_status(self): 72 | data = self.get_value("Other/extramap_status") 73 | return json.loads(data.replace("'", '"')) 74 | 75 | def set_extra_maps_status(self, data): 76 | self.conf.setValue(f"{self.conf_name}/Other/extramap_status", str(data)) 77 | 78 | def get_value(self, name): 79 | return self.conf.value(f"{self.conf_name}/{name}") 80 | 81 | def get_bool_value(self, name): 82 | return self.conf.value(f"{self.conf_name}/{name}", type=bool) 83 | 84 | def set_value(self, name, value): 85 | self.conf.setValue(f"{self.conf_name}/{name}", value) 86 | 87 | def get_key(self): 88 | return self.get_value("Tianditu/key") 89 | 90 | def get_random_key(self): 91 | key_list = self.get_key_list() 92 | return choice(key_list) 93 | 94 | def set_key(self, key): 95 | key_to_set = "" 96 | if key is not None: 97 | key_to_set = key 98 | self.conf.setValue(f"{self.section_tianditu}/key", key_to_set) 99 | 100 | 101 | def make_request(url: str, referer: str = None, params=None): 102 | u = QUrl(url) 103 | query = QUrlQuery() 104 | if params: 105 | for key, value in params.items(): 106 | query.addQueryItem(key, value) 107 | u.setQuery(query) 108 | request = QNetworkRequest(u) 109 | # request.setHeader(QNetworkRequest.UserAgentHeader, headers["User-Agent"]) # 设置 User-Agent 无效 110 | if referer: 111 | request.setRawHeader(b"Referer", referer.encode("utf-8")) 112 | 113 | return request 114 | 115 | 116 | def tianditu_map_url(maptype: str, token: str, subdomain: str) -> str: 117 | """ 118 | 返回天地图url 119 | 120 | Args: 121 | maptype (str): 类型 122 | token (str): 天地图key 123 | subdomain (str): 使用的子域名 124 | 125 | Returns: 126 | str: 返回天地图XYZ瓦片地址 127 | """ 128 | domain = f"https://{subdomain}.tianditu.gov.cn" 129 | if maptype == "terrain-rgb": 130 | domain = "https://lcdata.tianditu.gov.cn" 131 | url = f"{domain}/{maptype}_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER={maptype}" 132 | url += "&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileCol={x}&TileRow={y}&TileMatrix={z}" 133 | url += f"&tk={token}" 134 | return url 135 | 136 | 137 | def load_yaml(file_path: Path): 138 | """ 139 | 读取YAML文件 140 | """ 141 | with open(file_path, "r", encoding="utf-8") as f: 142 | return yaml.safe_load(f) 143 | -------------------------------------------------------------------------------- /tianditu_tools/images/map_icons/default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tianditu_tools/ui/sd.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SdDockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 376 10 | 442 11 | 12 | 13 | 14 | 天地图·山东 15 | 16 | 17 | 18 | 19 | 20 | 21 | Token 设置 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Consolas 31 | 8 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 8 41 | 42 | 43 | 44 | 保存 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 8 55 | 56 | 57 | 58 | <html><head/><body><p><a href="https://mp.weixin.qq.com/s/CcdjjjsBk7rNlxalBjUXIg"><span style=" text-decoration: underline; color:#0000ff;">Token 申请方法及使用说明</span></a></p></body></html> 59 | 60 | 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 0 73 | 0 74 | 75 | 76 | 77 | 服务资源 78 | 79 | 80 | 81 | 82 | 83 | 84 | 0 85 | 0 86 | 87 | 88 | 89 | 90 | 16777215 91 | 58 92 | 93 | 94 | 95 | 96 | 9 97 | 98 | 99 | 100 | true 101 | 102 | 103 | 80 104 | 105 | 106 | QListView::Adjust 107 | 108 | 109 | 2 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 历史影像 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 根据当前画布范围 128 | 129 | 130 | 131 | 132 | 133 | 134 | 查询历史影像 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 名称 145 | 146 | 147 | AlignCenter 148 | 149 | 150 | 151 | 152 | 时间 153 | 154 | 155 | AlignCenter 156 | 157 | 158 | 159 | 160 | 分辨率(m) 161 | 162 | 163 | AlignCenter 164 | 165 | 166 | 167 | 168 | URL 169 | 170 | 171 | AlignCenter 172 | 173 | 174 | 175 | 176 | el 177 | 178 | 179 | AlignCenter 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | QgsPasswordLineEdit 193 | QLineEdit 194 |
qgspasswordlineedit.h
195 |
196 |
197 | 198 | 199 |
200 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/天地图省级节点加载.md: -------------------------------------------------------------------------------- 1 | # 天地图省级节点加载 2 | 3 | ## 天地图·江西 4 | 5 | ### 添加资源 6 | 7 | 在其[资源中心](https://jiangxi.tianditu.gov.cn/ServiceMarket/index.html)页面中,有许多服务, 部分能预览并且可以直接加载到QGIS中去. 8 | 9 | 以`2021年江西省7-18级影像`为例, 服务地址为: ` 从该网页可以得到 KVP 风格的[能力文档地址:](https://jiangxi.tianditu.gov.cn/geostar/JX_IMG_2021/wmts/1.0.0/WMTSCapabilities.xml) 10 | 11 | ``` 12 | https://jiangxi.tianditu.gov.cn/geostar/JX_IMG_2021/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities 13 | ``` 14 | 15 | :::tip 16 | RSET 风格的地址 , QGIS可以识别图层, 但是无法加载地图 17 | ::: 18 | 19 | 添加到 QGIS 中后, `JX_IMG_2021_7_18` 图层有 3 个瓦片矩阵集`Matrix_0`、`Matrix_1`、`Matrix_2`, 其中`Matrix_0`无法加载、`Matrix_2`有较大的偏移、`Matrix_1`有微小的偏移. 20 | 21 | 在其能力文档的描述中, 对瓦片矩阵集的描述如下: 22 | 23 | ```xml 24 | 25 | 26 | 7.6.21214 27 | 28 | 29 | JX_IMG_2021_7_18_Matrix_0 30 | 31 | 32 | JX_IMG_2021_7_18_Matrix_1 33 | 34 | 35 | JX_IMG_2021_7_18_Matrix_2 36 | 37 | 38 | ``` 39 | 40 | 其中 OGCPyramidType 是最适合 QGIS 加载的, 但是仍然会有一点点的偏移. 41 | 42 | ### 修正偏移 43 | 44 | 观察 OGCPyramidType 的矩阵集, 7级的`ScaleDenominator`为`4367821.452062846`, QGIS无偏正确的`ScaleDenominator`为`4367830.269199427`, 因此, 修改一下`ScaleDenominator`就可以修改地图的偏移. 45 | 46 | 删除`Matrix_0`、`Matrix_2`瓦片矩阵集,修改`Matrix_1`瓦片矩阵集中的`ScaleDenominator`值. 47 | 48 | :::tip 49 | ``和``的值不需要动,否则可能出现瓦片缺失 50 | ::: 51 | 52 | 修改后的 TileMatrixSet: 53 | 54 | ```xml 55 | 56 | JX_IMG_2021_7_18_Matrix_1 57 | urn:ogc:def:crs:EPSG::4490 58 | 59 | 7 60 | 4367830.269199427 61 | 90.0 -180.0 62 | 256 63 | 256 64 | 107 65 | 24 66 | 67 | 68 | 8 69 | 2183915.1345997173 70 | 90.0 -180.0 71 | 256 72 | 256 73 | 213 74 | 47 75 | 76 | 77 | 9 78 | 1091957.5672998542 79 | 90.0 -180.0 80 | 256 81 | 256 82 | 425 83 | 94 84 | 85 | 86 | 10 87 | 545978.783649929 88 | 90.0 -180.0 89 | 256 90 | 256 91 | 850 92 | 187 93 | 94 | 95 | 11 96 | 272989.3918249645 97 | 90.0 -180.0 98 | 256 99 | 256 100 | 1699 101 | 373 102 | 103 | 104 | 12 105 | 136494.69591248178 106 | 90.0 -180.0 107 | 256 108 | 256 109 | 3397 110 | 746 111 | 112 | 113 | 13 114 | 68247.34795624108 115 | 90.0 -180.0 116 | 256 117 | 256 118 | 6793 119 | 1491 120 | 121 | 122 | 14 123 | 34123.67397812058 124 | 90.0 -180.0 125 | 256 126 | 256 127 | 13585 128 | 2982 129 | 130 | 131 | 15 132 | 17061.83698906029 133 | 90.0 -180.0 134 | 256 135 | 256 136 | 27169 137 | 5964 138 | 139 | 140 | 16 141 | 8530.918494530137 142 | 90.0 -180.0 143 | 256 144 | 256 145 | 54338 146 | 11927 147 | 148 | 149 | 17 150 | 4265.459247265068 151 | 90.0 -180.0 152 | 256 153 | 256 154 | 108675 155 | 23853 156 | 157 | 158 | 18 159 | 2132.729623632539 160 | 90.0 -180.0 161 | 256 162 | 256 163 | 217349 164 | 47705 165 | 166 | 167 | ``` 168 | 169 | 江西多时相地址: 170 | 171 | QGIS 似乎不能加载多时相的图层 172 | -------------------------------------------------------------------------------- /tianditu_tools/images/map_tianditu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tianditu_tools/ui/sd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './tianditu_tools/ui/sd.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.11 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from qgis.PyQt import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_SdDockWidget(object): 15 | def setupUi(self, SdDockWidget): 16 | SdDockWidget.setObjectName("SdDockWidget") 17 | SdDockWidget.resize(376, 442) 18 | self.dockWidgetContents = QtWidgets.QWidget() 19 | self.dockWidgetContents.setObjectName("dockWidgetContents") 20 | self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) 21 | self.verticalLayout.setObjectName("verticalLayout") 22 | self.groupBox = QtWidgets.QGroupBox(self.dockWidgetContents) 23 | self.groupBox.setObjectName("groupBox") 24 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox) 25 | self.verticalLayout_3.setObjectName("verticalLayout_3") 26 | self.horizontalLayout = QtWidgets.QHBoxLayout() 27 | self.horizontalLayout.setObjectName("horizontalLayout") 28 | self.mLineEdit_sdtk = QgsPasswordLineEdit(self.groupBox) 29 | font = QtGui.QFont() 30 | font.setFamily("Consolas") 31 | font.setPointSize(8) 32 | self.mLineEdit_sdtk.setFont(font) 33 | self.mLineEdit_sdtk.setObjectName("mLineEdit_sdtk") 34 | self.horizontalLayout.addWidget(self.mLineEdit_sdtk) 35 | self.pushButton_save = QtWidgets.QPushButton(self.groupBox) 36 | font = QtGui.QFont() 37 | font.setPointSize(8) 38 | self.pushButton_save.setFont(font) 39 | self.pushButton_save.setObjectName("pushButton_save") 40 | self.horizontalLayout.addWidget(self.pushButton_save) 41 | self.verticalLayout_3.addLayout(self.horizontalLayout) 42 | self.label = QtWidgets.QLabel(self.groupBox) 43 | font = QtGui.QFont() 44 | font.setPointSize(8) 45 | self.label.setFont(font) 46 | self.label.setOpenExternalLinks(True) 47 | self.label.setObjectName("label") 48 | self.verticalLayout_3.addWidget(self.label) 49 | self.verticalLayout.addWidget(self.groupBox) 50 | self.groupBox_2 = QtWidgets.QGroupBox(self.dockWidgetContents) 51 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) 52 | sizePolicy.setHorizontalStretch(0) 53 | sizePolicy.setVerticalStretch(0) 54 | sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) 55 | self.groupBox_2.setSizePolicy(sizePolicy) 56 | self.groupBox_2.setObjectName("groupBox_2") 57 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_2) 58 | self.verticalLayout_4.setObjectName("verticalLayout_4") 59 | self.listWidget = QtWidgets.QListWidget(self.groupBox_2) 60 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 61 | sizePolicy.setHorizontalStretch(0) 62 | sizePolicy.setVerticalStretch(0) 63 | sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) 64 | self.listWidget.setSizePolicy(sizePolicy) 65 | self.listWidget.setMaximumSize(QtCore.QSize(16777215, 58)) 66 | font = QtGui.QFont() 67 | font.setPointSize(9) 68 | self.listWidget.setFont(font) 69 | self.listWidget.setAutoScroll(True) 70 | self.listWidget.setAutoScrollMargin(80) 71 | self.listWidget.setResizeMode(QtWidgets.QListView.Adjust) 72 | self.listWidget.setObjectName("listWidget") 73 | self.verticalLayout_4.addWidget(self.listWidget) 74 | self.verticalLayout.addWidget(self.groupBox_2) 75 | self.groupBox_3 = QtWidgets.QGroupBox(self.dockWidgetContents) 76 | self.groupBox_3.setObjectName("groupBox_3") 77 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_3) 78 | self.verticalLayout_2.setObjectName("verticalLayout_2") 79 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 80 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 81 | self.checkBox = QtWidgets.QCheckBox(self.groupBox_3) 82 | self.checkBox.setObjectName("checkBox") 83 | self.horizontalLayout_2.addWidget(self.checkBox) 84 | self.pushButton_search = QtWidgets.QPushButton(self.groupBox_3) 85 | self.pushButton_search.setObjectName("pushButton_search") 86 | self.horizontalLayout_2.addWidget(self.pushButton_search) 87 | self.horizontalLayout_2.setStretch(0, 1) 88 | self.horizontalLayout_2.setStretch(1, 4) 89 | self.verticalLayout_2.addLayout(self.horizontalLayout_2) 90 | self.treeWidget = QtWidgets.QTreeWidget(self.groupBox_3) 91 | self.treeWidget.setObjectName("treeWidget") 92 | self.treeWidget.headerItem().setTextAlignment(0, QtCore.Qt.AlignCenter) 93 | self.treeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignCenter) 94 | self.treeWidget.headerItem().setTextAlignment(2, QtCore.Qt.AlignCenter) 95 | self.treeWidget.headerItem().setTextAlignment(3, QtCore.Qt.AlignCenter) 96 | self.treeWidget.headerItem().setTextAlignment(4, QtCore.Qt.AlignCenter) 97 | self.verticalLayout_2.addWidget(self.treeWidget) 98 | self.verticalLayout.addWidget(self.groupBox_3) 99 | SdDockWidget.setWidget(self.dockWidgetContents) 100 | 101 | self.retranslateUi(SdDockWidget) 102 | QtCore.QMetaObject.connectSlotsByName(SdDockWidget) 103 | 104 | def retranslateUi(self, SdDockWidget): 105 | _translate = QtCore.QCoreApplication.translate 106 | SdDockWidget.setWindowTitle(_translate("SdDockWidget", "天地图·山东")) 107 | self.groupBox.setTitle(_translate("SdDockWidget", "Token 设置")) 108 | self.pushButton_save.setText(_translate("SdDockWidget", "保存")) 109 | self.label.setText(_translate("SdDockWidget", "

Token 申请方法及使用说明

")) 110 | self.groupBox_2.setTitle(_translate("SdDockWidget", "服务资源")) 111 | self.groupBox_3.setTitle(_translate("SdDockWidget", "历史影像")) 112 | self.checkBox.setText(_translate("SdDockWidget", "根据当前画布范围")) 113 | self.pushButton_search.setText(_translate("SdDockWidget", "查询历史影像")) 114 | self.treeWidget.headerItem().setText(0, _translate("SdDockWidget", "名称")) 115 | self.treeWidget.headerItem().setText(1, _translate("SdDockWidget", "时间")) 116 | self.treeWidget.headerItem().setText(2, _translate("SdDockWidget", "分辨率(m)")) 117 | self.treeWidget.headerItem().setText(3, _translate("SdDockWidget", "URL")) 118 | self.treeWidget.headerItem().setText(4, _translate("SdDockWidget", "el")) 119 | from qgspasswordlineedit import QgsPasswordLineEdit 120 | -------------------------------------------------------------------------------- /tianditu_tools/maps/extra.yml: -------------------------------------------------------------------------------- 1 | info: 2 | id: extra 3 | name: 其他地图 4 | lastUpdated: '2025-06-07 10:14:11' 5 | maps: 6 | 谷歌地图: 7 | - name: Google Map - Satellite 8 | icon: googlemap_satellite.png 9 | url: https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z} 10 | zmin: 0 11 | zmax: 21 12 | referer: '' 13 | - name: Google Map - Roadmap 14 | icon: googlemap_default.png 15 | url: https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z} 16 | zmin: 0 17 | zmax: 21 18 | referer: '' 19 | - name: Google Map - Terrain Background 20 | icon: googlemap_terrain.png 21 | url: http://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}&apistyle=s.e:l|p.v:off,s.t:1|s.e.g|p.v:off,s.t:3|s.e.g|p.v:off,s.t:2|s.e.g|p.v:off,s.t:4|s.e.g|p.v:off 22 | zmin: 0 23 | zmax: 21 24 | referer: '' 25 | 谷谷GIS: 26 | - name: Google Map - Satellite 27 | icon: googlemap_satellite.png 28 | url: https://mt3v.gggis.com/maps/vt?lyrs=s&x={x}&y={y}&z={z} 29 | zmin: 0 30 | zmax: 21 31 | referer: '' 32 | - name: Google Map - Roadmap (GCJ02) 33 | icon: googlemap_default.png 34 | url: https://mt3v.gggis.com/maps/vt?lyrs=r&x={x}&y={y}&z={z} 35 | zmin: 0 36 | zmax: 21 37 | referer: '' 38 | - name: Google Map - Terrain Background 39 | icon: googlemap_terrain.png 40 | url: https://mt3v.gggis.com/maps/vt?lyrs=p&x={x}&y={y}&z={z}&apistyle=s.e:l|p.v:off,s.t:1|s.e.g|p.v:off,s.t:3|s.e.g|p.v:off,s.t:2|s.e.g|p.v:off,s.t:4|s.e.g|p.v:off 41 | zmin: 0 42 | zmax: 21 43 | referer: '' 44 | - name: Google Earth (无水印高清) 45 | uri: crs=EPSG:4326&dpiMode=7&featureCount=10&format=image/jpeg&layers=gggis_google_new&styles=default&tileMatrixSet=WorldCRS84QuadLess&tilePixelRatio=0&url=https://wmts.liuxs.pro/collection 46 | - name: Google Earth (最新影像) 47 | uri: crs=EPSG:4326&dpiMode=7&featureCount=10&format=image/jpeg&layers=gggis_google_time&styles=default&tileMatrixSet=WorldCRS84QuadLess&tilePixelRatio=0&url=https://wmts.liuxs.pro/collection 48 | ESRI: 49 | - name: ESRI - World Imagery 50 | icon: ESRI.webp 51 | url: https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x} 52 | zmin: 0 53 | zmax: 18 54 | referer: '' 55 | - name: ESRI - World Ocean Base 56 | icon: ESRI.webp 57 | url: https://server.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x} 58 | zmin: 0 59 | zmax: 10 60 | referer: '' 61 | - name: ESRI - World Terrain Base 62 | icon: ESRI.webp 63 | url: https://server.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x} 64 | zmin: 0 65 | zmax: 9 66 | referer: '' 67 | - name: ESRI - World Physical Map 68 | icon: ESRI.webp 69 | url: https://server.arcgisonline.com/arcgis/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x} 70 | zmin: 0 71 | zmax: 8 72 | referer: '' 73 | 高德地图: 74 | - name: 高德地图 - 影像地图 75 | icon: amap.svg 76 | url: http://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z} 77 | zmin: 0 78 | zmax: 18 79 | referer: '' 80 | - name: 高德地图 - 矢量地图 81 | icon: amap.svg 82 | url: http://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z} 83 | zmin: 0 84 | zmax: 18 85 | referer: '' 86 | - name: 高德地图 - 矢量注记 87 | icon: amap.svg 88 | url: http://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z} 89 | zmin: 0 90 | zmax: 18 91 | referer: '' 92 | - name: 高德地图 - 矢量地图2 93 | icon: amap.svg 94 | url: http://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=9&x={x}&y={y}&z={z} 95 | zmin: 0 96 | zmax: 18 97 | referer: '' 98 | - name: 高德地图 - 矢量地图3 99 | icon: amap.svg 100 | url: http://wprd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=10&x={x}&y={y}&z={z} 101 | zmin: 0 102 | zmax: 18 103 | referer: '' 104 | Stamen Maps: 105 | - name: Alidade Smooth 106 | icon: default.svg 107 | url: https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}.png 108 | zmin: 0 109 | zmax: 20 110 | referer: https://maps.stamen.com/ 111 | - name: Alidade Smooth Dark 112 | icon: default.svg 113 | url: https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}.png 114 | zmin: 0 115 | zmax: 20 116 | referer: https://maps.stamen.com/ 117 | - name: Alidade Satellite 118 | icon: default.svg 119 | url: https://tiles.stadiamaps.com/tiles/alidade_satellite/{z}/{x}/{y}.jpg 120 | zmin: 0 121 | zmax: 20 122 | referer: https://maps.stamen.com/ 123 | - name: Stadia Outdoors 124 | icon: default.svg 125 | url: https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}.png 126 | zmin: 0 127 | zmax: 20 128 | referer: https://maps.stamen.com/ 129 | - name: Stamen Toner Background 130 | icon: default.svg 131 | url: https://tiles.stadiamaps.com/tiles/stamen_toner_background/{z}/{x}/{y}.png 132 | zmin: 0 133 | zmax: 20 134 | referer: https://maps.stamen.com/ 135 | - name: Stamen Terrain Background 136 | icon: default.svg 137 | url: https://tiles.stadiamaps.com/tiles/stamen_terrain_background/{z}/{x}/{y}.png 138 | zmin: 0 139 | zmax: 20 140 | referer: https://maps.stamen.com/ 141 | - name: Stamen Watercolor 142 | icon: default.svg 143 | url: https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg 144 | zmin: 0 145 | zmax: 16 146 | referer: https://maps.stamen.com/ 147 | - name: OSM Bright 148 | icon: default.svg 149 | url: https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}.png 150 | zmin: 0 151 | zmax: 20 152 | referer: https://maps.stamen.com/ 153 | OpenRailwayMap: 154 | - name: OpenRailwayMap Standard 155 | icon: default.svg 156 | url: https://a.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png 157 | zmin: 0 158 | zmax: 19 159 | referer: '' 160 | - name: OpenRailwayMap Maxspeed 161 | icon: default.svg 162 | url: https://a.tiles.openrailwaymap.org/maxspeed/{z}/{x}/{y}.png 163 | zmin: 0 164 | zmax: 19 165 | referer: '' 166 | - name: OpenRailwayMap Signals 167 | icon: default.svg 168 | url: https://a.tiles.openrailwaymap.org/signals/{z}/{x}/{y}.png 169 | zmin: 0 170 | zmax: 19 171 | referer: '' 172 | - name: OpenRailwayMap Electrified 173 | icon: default.svg 174 | url: https://a.tiles.openrailwaymap.org/electrified/{z}/{x}/{y}.png 175 | zmin: 0 176 | zmax: 19 177 | referer: '' 178 | - name: OpenRailwayMap Gauge 179 | icon: default.svg 180 | url: https://a.tiles.openrailwaymap.org/gauge/{z}/{x}/{y}.png 181 | zmin: 0 182 | zmax: 19 183 | referer: '' 184 | -------------------------------------------------------------------------------- /tianditu_tools/ui/search.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SearchDockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 422 10 | 303 11 | 12 | 13 | 14 | 天地图API-搜索 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 地名搜索 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 请输入地名 34 | 35 | 36 | 37 | 38 | 39 | 40 | PointingHandCursor 41 | 42 | 43 | 搜索 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 地理编码 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 请输入地名 68 | 69 | 70 | 71 | 72 | 73 | 74 | 查询 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 50 85 | false 86 | 87 | 88 | 89 | 搜索结果: 90 | 91 | 92 | Qt::AutoText 93 | 94 | 95 | false 96 | 97 | 98 | Qt::TextBrowserInteraction 99 | 100 | 101 | 102 | 103 | 104 | 105 | Qt::RichText 106 | 107 | 108 | Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 109 | 110 | 111 | 112 | 113 | 114 | 115 | Qt::Vertical 116 | 117 | 118 | 119 | 20 120 | 40 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 逆地理编码 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 请输入经纬度:经度,纬度 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 60 146 | 16777215 147 | 148 | 149 | 150 | 拾取坐标 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 60 159 | 16777215 160 | 161 | 162 | 163 | 查询 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 搜索结果: 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | true 183 | 184 | 185 | Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 186 | 187 | 188 | 189 | 190 | 191 | 192 | Qt::Vertical 193 | 194 | 195 | 196 | 20 197 | 40 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /tianditu_tools/ui/sd_6.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file './tianditu_tools/ui/sd.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.8.1 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from qgis.PyQt import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_SdDockWidget(object): 13 | def setupUi(self, SdDockWidget): 14 | SdDockWidget.setObjectName("SdDockWidget") 15 | SdDockWidget.resize(376, 442) 16 | self.dockWidgetContents = QtWidgets.QWidget() 17 | self.dockWidgetContents.setObjectName("dockWidgetContents") 18 | self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.groupBox = QtWidgets.QGroupBox(parent=self.dockWidgetContents) 21 | self.groupBox.setObjectName("groupBox") 22 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox) 23 | self.verticalLayout_3.setObjectName("verticalLayout_3") 24 | self.horizontalLayout = QtWidgets.QHBoxLayout() 25 | self.horizontalLayout.setObjectName("horizontalLayout") 26 | self.mLineEdit_sdtk = QgsPasswordLineEdit(parent=self.groupBox) 27 | font = QtGui.QFont() 28 | font.setFamily("Consolas") 29 | font.setPointSize(8) 30 | self.mLineEdit_sdtk.setFont(font) 31 | self.mLineEdit_sdtk.setObjectName("mLineEdit_sdtk") 32 | self.horizontalLayout.addWidget(self.mLineEdit_sdtk) 33 | self.pushButton_save = QtWidgets.QPushButton(parent=self.groupBox) 34 | font = QtGui.QFont() 35 | font.setPointSize(8) 36 | self.pushButton_save.setFont(font) 37 | self.pushButton_save.setObjectName("pushButton_save") 38 | self.horizontalLayout.addWidget(self.pushButton_save) 39 | self.verticalLayout_3.addLayout(self.horizontalLayout) 40 | self.label = QtWidgets.QLabel(parent=self.groupBox) 41 | font = QtGui.QFont() 42 | font.setPointSize(8) 43 | self.label.setFont(font) 44 | self.label.setOpenExternalLinks(True) 45 | self.label.setObjectName("label") 46 | self.verticalLayout_3.addWidget(self.label) 47 | self.verticalLayout.addWidget(self.groupBox) 48 | self.groupBox_2 = QtWidgets.QGroupBox(parent=self.dockWidgetContents) 49 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed) 50 | sizePolicy.setHorizontalStretch(0) 51 | sizePolicy.setVerticalStretch(0) 52 | sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) 53 | self.groupBox_2.setSizePolicy(sizePolicy) 54 | self.groupBox_2.setObjectName("groupBox_2") 55 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_2) 56 | self.verticalLayout_4.setObjectName("verticalLayout_4") 57 | self.listWidget = QtWidgets.QListWidget(parent=self.groupBox_2) 58 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 59 | sizePolicy.setHorizontalStretch(0) 60 | sizePolicy.setVerticalStretch(0) 61 | sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) 62 | self.listWidget.setSizePolicy(sizePolicy) 63 | self.listWidget.setMaximumSize(QtCore.QSize(16777215, 58)) 64 | font = QtGui.QFont() 65 | font.setPointSize(9) 66 | self.listWidget.setFont(font) 67 | self.listWidget.setAutoScroll(True) 68 | self.listWidget.setAutoScrollMargin(80) 69 | self.listWidget.setResizeMode(QtWidgets.QListView.ResizeMode.Adjust) 70 | self.listWidget.setObjectName("listWidget") 71 | self.verticalLayout_4.addWidget(self.listWidget) 72 | self.verticalLayout.addWidget(self.groupBox_2) 73 | self.groupBox_3 = QtWidgets.QGroupBox(parent=self.dockWidgetContents) 74 | self.groupBox_3.setObjectName("groupBox_3") 75 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_3) 76 | self.verticalLayout_2.setObjectName("verticalLayout_2") 77 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 78 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 79 | self.checkBox = QtWidgets.QCheckBox(parent=self.groupBox_3) 80 | self.checkBox.setObjectName("checkBox") 81 | self.horizontalLayout_2.addWidget(self.checkBox) 82 | self.pushButton_search = QtWidgets.QPushButton(parent=self.groupBox_3) 83 | self.pushButton_search.setObjectName("pushButton_search") 84 | self.horizontalLayout_2.addWidget(self.pushButton_search) 85 | self.horizontalLayout_2.setStretch(0, 1) 86 | self.horizontalLayout_2.setStretch(1, 4) 87 | self.verticalLayout_2.addLayout(self.horizontalLayout_2) 88 | self.treeWidget = QtWidgets.QTreeWidget(parent=self.groupBox_3) 89 | self.treeWidget.setObjectName("treeWidget") 90 | self.treeWidget.headerItem().setTextAlignment(0, QtCore.Qt.AlignmentFlag.AlignCenter) 91 | self.treeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignmentFlag.AlignCenter) 92 | self.treeWidget.headerItem().setTextAlignment(2, QtCore.Qt.AlignmentFlag.AlignCenter) 93 | self.treeWidget.headerItem().setTextAlignment(3, QtCore.Qt.AlignmentFlag.AlignCenter) 94 | self.treeWidget.headerItem().setTextAlignment(4, QtCore.Qt.AlignmentFlag.AlignCenter) 95 | self.verticalLayout_2.addWidget(self.treeWidget) 96 | self.verticalLayout.addWidget(self.groupBox_3) 97 | SdDockWidget.setWidget(self.dockWidgetContents) 98 | 99 | self.retranslateUi(SdDockWidget) 100 | QtCore.QMetaObject.connectSlotsByName(SdDockWidget) 101 | 102 | def retranslateUi(self, SdDockWidget): 103 | _translate = QtCore.QCoreApplication.translate 104 | SdDockWidget.setWindowTitle(_translate("SdDockWidget", "天地图·山东")) 105 | self.groupBox.setTitle(_translate("SdDockWidget", "Token 设置")) 106 | self.pushButton_save.setText(_translate("SdDockWidget", "保存")) 107 | self.label.setText(_translate("SdDockWidget", "

Token 申请方法及使用说明

")) 108 | self.groupBox_2.setTitle(_translate("SdDockWidget", "服务资源")) 109 | self.groupBox_3.setTitle(_translate("SdDockWidget", "历史影像")) 110 | self.checkBox.setText(_translate("SdDockWidget", "根据当前画布范围")) 111 | self.pushButton_search.setText(_translate("SdDockWidget", "查询历史影像")) 112 | self.treeWidget.headerItem().setText(0, _translate("SdDockWidget", "名称")) 113 | self.treeWidget.headerItem().setText(1, _translate("SdDockWidget", "时间")) 114 | self.treeWidget.headerItem().setText(2, _translate("SdDockWidget", "分辨率(m)")) 115 | self.treeWidget.headerItem().setText(3, _translate("SdDockWidget", "URL")) 116 | self.treeWidget.headerItem().setText(4, _translate("SdDockWidget", "el")) 117 | from qgspasswordlineedit import QgsPasswordLineEdit 118 | -------------------------------------------------------------------------------- /tianditu_tools/ui/search.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './tianditu_tools/ui/search.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.11 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from qgis.PyQt import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_SearchDockWidget(object): 15 | def setupUi(self, SearchDockWidget): 16 | SearchDockWidget.setObjectName("SearchDockWidget") 17 | SearchDockWidget.resize(422, 303) 18 | self.dockWidgetContents = QtWidgets.QWidget() 19 | self.dockWidgetContents.setObjectName("dockWidgetContents") 20 | self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) 21 | self.verticalLayout.setObjectName("verticalLayout") 22 | self.tabWidget = QtWidgets.QTabWidget(self.dockWidgetContents) 23 | self.tabWidget.setObjectName("tabWidget") 24 | self.tab = QtWidgets.QWidget() 25 | self.tab.setObjectName("tab") 26 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab) 27 | self.verticalLayout_2.setObjectName("verticalLayout_2") 28 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 29 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 30 | self.lineEdit = QtWidgets.QLineEdit(self.tab) 31 | self.lineEdit.setObjectName("lineEdit") 32 | self.horizontalLayout_3.addWidget(self.lineEdit) 33 | self.pushButton = QtWidgets.QPushButton(self.tab) 34 | self.pushButton.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 35 | self.pushButton.setObjectName("pushButton") 36 | self.horizontalLayout_3.addWidget(self.pushButton) 37 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 38 | self.tabWidget.addTab(self.tab, "") 39 | self.tab_2 = QtWidgets.QWidget() 40 | self.tab_2.setObjectName("tab_2") 41 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_2) 42 | self.verticalLayout_3.setObjectName("verticalLayout_3") 43 | self.horizontalLayout = QtWidgets.QHBoxLayout() 44 | self.horizontalLayout.setObjectName("horizontalLayout") 45 | self.lineEdit_2 = QtWidgets.QLineEdit(self.tab_2) 46 | self.lineEdit_2.setInputMask("") 47 | self.lineEdit_2.setText("") 48 | self.lineEdit_2.setObjectName("lineEdit_2") 49 | self.horizontalLayout.addWidget(self.lineEdit_2) 50 | self.pushButton_2 = QtWidgets.QPushButton(self.tab_2) 51 | self.pushButton_2.setObjectName("pushButton_2") 52 | self.horizontalLayout.addWidget(self.pushButton_2) 53 | self.horizontalLayout.setStretch(0, 4) 54 | self.horizontalLayout.setStretch(1, 1) 55 | self.verticalLayout_3.addLayout(self.horizontalLayout) 56 | self.label = QtWidgets.QLabel(self.tab_2) 57 | font = QtGui.QFont() 58 | font.setBold(False) 59 | font.setWeight(50) 60 | self.label.setFont(font) 61 | self.label.setTextFormat(QtCore.Qt.AutoText) 62 | self.label.setOpenExternalLinks(False) 63 | self.label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) 64 | self.label.setObjectName("label") 65 | self.verticalLayout_3.addWidget(self.label) 66 | self.label_2 = QtWidgets.QLabel(self.tab_2) 67 | self.label_2.setTextFormat(QtCore.Qt.RichText) 68 | self.label_2.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) 69 | self.label_2.setObjectName("label_2") 70 | self.verticalLayout_3.addWidget(self.label_2) 71 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 72 | self.verticalLayout_3.addItem(spacerItem) 73 | self.tabWidget.addTab(self.tab_2, "") 74 | self.tab_3 = QtWidgets.QWidget() 75 | self.tab_3.setObjectName("tab_3") 76 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_3) 77 | self.verticalLayout_4.setObjectName("verticalLayout_4") 78 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 79 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 80 | self.lineEdit_3 = QtWidgets.QLineEdit(self.tab_3) 81 | self.lineEdit_3.setObjectName("lineEdit_3") 82 | self.horizontalLayout_2.addWidget(self.lineEdit_3) 83 | self.btn_cap = QtWidgets.QPushButton(self.tab_3) 84 | self.btn_cap.setMaximumSize(QtCore.QSize(60, 16777215)) 85 | self.btn_cap.setObjectName("btn_cap") 86 | self.horizontalLayout_2.addWidget(self.btn_cap) 87 | self.pushButton_3 = QtWidgets.QPushButton(self.tab_3) 88 | self.pushButton_3.setMaximumSize(QtCore.QSize(60, 16777215)) 89 | self.pushButton_3.setObjectName("pushButton_3") 90 | self.horizontalLayout_2.addWidget(self.pushButton_3) 91 | self.verticalLayout_4.addLayout(self.horizontalLayout_2) 92 | self.label_3 = QtWidgets.QLabel(self.tab_3) 93 | self.label_3.setObjectName("label_3") 94 | self.verticalLayout_4.addWidget(self.label_3) 95 | self.label_4 = QtWidgets.QLabel(self.tab_3) 96 | self.label_4.setText("") 97 | self.label_4.setWordWrap(True) 98 | self.label_4.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) 99 | self.label_4.setObjectName("label_4") 100 | self.verticalLayout_4.addWidget(self.label_4) 101 | spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 102 | self.verticalLayout_4.addItem(spacerItem1) 103 | self.tabWidget.addTab(self.tab_3, "") 104 | self.verticalLayout.addWidget(self.tabWidget) 105 | SearchDockWidget.setWidget(self.dockWidgetContents) 106 | 107 | self.retranslateUi(SearchDockWidget) 108 | self.tabWidget.setCurrentIndex(0) 109 | QtCore.QMetaObject.connectSlotsByName(SearchDockWidget) 110 | 111 | def retranslateUi(self, SearchDockWidget): 112 | _translate = QtCore.QCoreApplication.translate 113 | SearchDockWidget.setWindowTitle(_translate("SearchDockWidget", "天地图API-搜索")) 114 | self.lineEdit.setPlaceholderText(_translate("SearchDockWidget", "请输入地名")) 115 | self.pushButton.setText(_translate("SearchDockWidget", "搜索")) 116 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("SearchDockWidget", "地名搜索")) 117 | self.lineEdit_2.setPlaceholderText(_translate("SearchDockWidget", "请输入地名")) 118 | self.pushButton_2.setText(_translate("SearchDockWidget", "查询")) 119 | self.label.setText(_translate("SearchDockWidget", "搜索结果:")) 120 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("SearchDockWidget", "地理编码")) 121 | self.lineEdit_3.setPlaceholderText(_translate("SearchDockWidget", "请输入经纬度:经度,纬度")) 122 | self.btn_cap.setText(_translate("SearchDockWidget", "拾取坐标")) 123 | self.pushButton_3.setText(_translate("SearchDockWidget", "查询")) 124 | self.label_3.setText(_translate("SearchDockWidget", "搜索结果:")) 125 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("SearchDockWidget", "逆地理编码")) 126 | -------------------------------------------------------------------------------- /tianditu_tools/ui/search_6.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file './tianditu_tools/ui/search.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.8.1 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from qgis.PyQt import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_SearchDockWidget(object): 13 | def setupUi(self, SearchDockWidget): 14 | SearchDockWidget.setObjectName("SearchDockWidget") 15 | SearchDockWidget.resize(422, 303) 16 | self.dockWidgetContents = QtWidgets.QWidget() 17 | self.dockWidgetContents.setObjectName("dockWidgetContents") 18 | self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.tabWidget = QtWidgets.QTabWidget(parent=self.dockWidgetContents) 21 | self.tabWidget.setObjectName("tabWidget") 22 | self.tab = QtWidgets.QWidget() 23 | self.tab.setObjectName("tab") 24 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab) 25 | self.verticalLayout_2.setObjectName("verticalLayout_2") 26 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 27 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 28 | self.lineEdit = QtWidgets.QLineEdit(parent=self.tab) 29 | self.lineEdit.setObjectName("lineEdit") 30 | self.horizontalLayout_3.addWidget(self.lineEdit) 31 | self.pushButton = QtWidgets.QPushButton(parent=self.tab) 32 | self.pushButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 33 | self.pushButton.setObjectName("pushButton") 34 | self.horizontalLayout_3.addWidget(self.pushButton) 35 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 36 | self.tabWidget.addTab(self.tab, "") 37 | self.tab_2 = QtWidgets.QWidget() 38 | self.tab_2.setObjectName("tab_2") 39 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_2) 40 | self.verticalLayout_3.setObjectName("verticalLayout_3") 41 | self.horizontalLayout = QtWidgets.QHBoxLayout() 42 | self.horizontalLayout.setObjectName("horizontalLayout") 43 | self.lineEdit_2 = QtWidgets.QLineEdit(parent=self.tab_2) 44 | self.lineEdit_2.setInputMask("") 45 | self.lineEdit_2.setText("") 46 | self.lineEdit_2.setObjectName("lineEdit_2") 47 | self.horizontalLayout.addWidget(self.lineEdit_2) 48 | self.pushButton_2 = QtWidgets.QPushButton(parent=self.tab_2) 49 | self.pushButton_2.setObjectName("pushButton_2") 50 | self.horizontalLayout.addWidget(self.pushButton_2) 51 | self.horizontalLayout.setStretch(0, 4) 52 | self.horizontalLayout.setStretch(1, 1) 53 | self.verticalLayout_3.addLayout(self.horizontalLayout) 54 | self.label = QtWidgets.QLabel(parent=self.tab_2) 55 | font = QtGui.QFont() 56 | font.setBold(False) 57 | font.setWeight(50) 58 | self.label.setFont(font) 59 | self.label.setTextFormat(QtCore.Qt.TextFormat.AutoText) 60 | self.label.setOpenExternalLinks(False) 61 | self.label.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextBrowserInteraction) 62 | self.label.setObjectName("label") 63 | self.verticalLayout_3.addWidget(self.label) 64 | self.label_2 = QtWidgets.QLabel(parent=self.tab_2) 65 | self.label_2.setTextFormat(QtCore.Qt.TextFormat.RichText) 66 | self.label_2.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.LinksAccessibleByMouse|QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) 67 | self.label_2.setObjectName("label_2") 68 | self.verticalLayout_3.addWidget(self.label_2) 69 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) 70 | self.verticalLayout_3.addItem(spacerItem) 71 | self.tabWidget.addTab(self.tab_2, "") 72 | self.tab_3 = QtWidgets.QWidget() 73 | self.tab_3.setObjectName("tab_3") 74 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_3) 75 | self.verticalLayout_4.setObjectName("verticalLayout_4") 76 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 77 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 78 | self.lineEdit_3 = QtWidgets.QLineEdit(parent=self.tab_3) 79 | self.lineEdit_3.setObjectName("lineEdit_3") 80 | self.horizontalLayout_2.addWidget(self.lineEdit_3) 81 | self.btn_cap = QtWidgets.QPushButton(parent=self.tab_3) 82 | self.btn_cap.setMaximumSize(QtCore.QSize(60, 16777215)) 83 | self.btn_cap.setObjectName("btn_cap") 84 | self.horizontalLayout_2.addWidget(self.btn_cap) 85 | self.pushButton_3 = QtWidgets.QPushButton(parent=self.tab_3) 86 | self.pushButton_3.setMaximumSize(QtCore.QSize(60, 16777215)) 87 | self.pushButton_3.setObjectName("pushButton_3") 88 | self.horizontalLayout_2.addWidget(self.pushButton_3) 89 | self.verticalLayout_4.addLayout(self.horizontalLayout_2) 90 | self.label_3 = QtWidgets.QLabel(parent=self.tab_3) 91 | self.label_3.setObjectName("label_3") 92 | self.verticalLayout_4.addWidget(self.label_3) 93 | self.label_4 = QtWidgets.QLabel(parent=self.tab_3) 94 | self.label_4.setText("") 95 | self.label_4.setWordWrap(True) 96 | self.label_4.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.LinksAccessibleByMouse|QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) 97 | self.label_4.setObjectName("label_4") 98 | self.verticalLayout_4.addWidget(self.label_4) 99 | spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) 100 | self.verticalLayout_4.addItem(spacerItem1) 101 | self.tabWidget.addTab(self.tab_3, "") 102 | self.verticalLayout.addWidget(self.tabWidget) 103 | SearchDockWidget.setWidget(self.dockWidgetContents) 104 | 105 | self.retranslateUi(SearchDockWidget) 106 | self.tabWidget.setCurrentIndex(0) 107 | QtCore.QMetaObject.connectSlotsByName(SearchDockWidget) 108 | 109 | def retranslateUi(self, SearchDockWidget): 110 | _translate = QtCore.QCoreApplication.translate 111 | SearchDockWidget.setWindowTitle(_translate("SearchDockWidget", "天地图API-搜索")) 112 | self.lineEdit.setPlaceholderText(_translate("SearchDockWidget", "请输入地名")) 113 | self.pushButton.setText(_translate("SearchDockWidget", "搜索")) 114 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("SearchDockWidget", "地名搜索")) 115 | self.lineEdit_2.setPlaceholderText(_translate("SearchDockWidget", "请输入地名")) 116 | self.pushButton_2.setText(_translate("SearchDockWidget", "查询")) 117 | self.label.setText(_translate("SearchDockWidget", "搜索结果:")) 118 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("SearchDockWidget", "地理编码")) 119 | self.lineEdit_3.setPlaceholderText(_translate("SearchDockWidget", "请输入经纬度:经度,纬度")) 120 | self.btn_cap.setText(_translate("SearchDockWidget", "拾取坐标")) 121 | self.pushButton_3.setText(_translate("SearchDockWidget", "查询")) 122 | self.label_3.setText(_translate("SearchDockWidget", "搜索结果:")) 123 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("SearchDockWidget", "逆地理编码")) 124 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Setting/mapmanager.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from pathlib import Path 3 | 4 | import yaml 5 | from qgis.PyQt.QtCore import QSize, QTimer 6 | from qgis.PyQt.QtGui import QFont 7 | from qgis.PyQt.QtNetwork import QNetworkReply 8 | from qgis.PyQt.QtWidgets import QPushButton, QTreeWidget, QTreeWidgetItem 9 | from qgis.core import QgsNetworkAccessManager 10 | 11 | from ...compat import AlignCenter, Checked, Unchecked, NoError, MatchExactly 12 | from ...utils import load_yaml, PluginConfig, make_request, HEADER, APP_FONT 13 | 14 | ui_font = QFont() 15 | ui_font.setFamily(APP_FONT) 16 | ui_font.setPointSize(8) 17 | 18 | 19 | class MapManager(QTreeWidget): 20 | """ 21 | 地图管理 22 | """ 23 | 24 | def __init__( 25 | self, map_folder: Path, parent=None, update_btn=None, status_label=None 26 | ): 27 | super().__init__(parent) 28 | self.map_folder = map_folder 29 | self.update_btn = update_btn 30 | self.status_label = status_label 31 | self.setFont(ui_font) 32 | self.update_host = "https://maps.liuxs.pro/dist/" 33 | self.update_url = f"{self.update_host}summary.yml" 34 | self.conf = PluginConfig() 35 | # self.check_update() 36 | # 设置 status label 的定时器 37 | self.timer = QTimer(self) 38 | self.timer.timeout.connect(self.clear_status_label) 39 | self.setupUI() 40 | 41 | def setupUI(self): 42 | self.clear() 43 | self.setColumnCount(3) # 设置列 44 | self.setHeaderLabels(["名称", "Local", "LastUpdated", "操作"]) 45 | self.header().setDefaultAlignment(AlignCenter) 46 | self.setUniformRowHeights(True) 47 | # 设置宽度 48 | self.setColumnWidth(0, 190) 49 | self.setColumnWidth(1, 125) 50 | self.setColumnWidth(2, 125) 51 | self.setColumnWidth(3, 90) 52 | self.load_map_summary() 53 | self.expandAll() 54 | 55 | def load_map_detail(self, map_id): 56 | mapfile_path = self.map_folder.joinpath(f"{map_id}.yml") 57 | data = load_yaml(mapfile_path) 58 | return data 59 | 60 | def get_summary(self): 61 | summary_path = self.map_folder.joinpath("summary.yml") 62 | return load_yaml(summary_path) 63 | 64 | def get_map_id_by_name(self, name): 65 | """通过地图名称获取 id""" 66 | summary = self.get_summary() 67 | for item in summary.values(): 68 | if item["name"] == name: 69 | return item["id"] 70 | return None 71 | 72 | def load_map_summary(self): 73 | summary = self.get_summary() 74 | sorted_summary_list = ["tianditu_province", "extra"] # 按照此顺序组织列表 75 | for value_key in sorted_summary_list: 76 | value = summary[value_key] 77 | update_btn = QPushButton("更新") 78 | update_btn.setStyleSheet("QPushButton{margin:2px 20px;}") 79 | update_btn.clicked.connect(self.update_btn_clicked) 80 | update_btn.setEnabled(False) 81 | item = QTreeWidgetItem(self, [value["name"], value["lastUpdated"], "/"]) 82 | item.setSizeHint(0, QSize(160, 28)) 83 | item.setTextAlignment(1, AlignCenter) 84 | item.setTextAlignment(2, AlignCenter) 85 | self.setItemWidget(item, 3, update_btn) 86 | extra_maps_status = self.conf.get_extra_maps_status() 87 | map_detail = self.load_map_detail(value["id"])["maps"] 88 | section_maps_status = extra_maps_status[value["id"]] 89 | # 添加地图item 90 | for map_name in map_detail.keys(): 91 | child_item = QTreeWidgetItem(item) 92 | child_item.setText(0, map_name) 93 | # 是否启用 94 | if map_name in section_maps_status: 95 | child_item.setCheckState(0, Checked) 96 | else: 97 | child_item.setCheckState(0, Unchecked) 98 | 99 | self.addTopLevelItem(item) 100 | 101 | def update_btn_clicked(self): 102 | """ 103 | 更新地图配置文件 104 | """ 105 | sender_btn = self.sender() # 获取发出信号的按钮 106 | if sender_btn: 107 | item = self.itemFromIndex( 108 | self.indexAt(sender_btn.pos()) 109 | ) # 获取包含按钮的项 110 | if item: 111 | map_id = self.get_map_id_by_name(item.text(0)) 112 | self.download_map_conf(map_id) 113 | # 重新禁用按钮 114 | update_btn = self.itemWidget(item, 3) 115 | update_btn.setEnabled(False) 116 | # 重绘UI 117 | self.setupUI() 118 | 119 | def download_map_conf(self, map_id): 120 | download_url = f"{self.update_host}{map_id}.yml" 121 | mapfile_path = self.map_folder.joinpath(f"{map_id}.yml") 122 | # 更新 summary 123 | network_manager = QgsNetworkAccessManager.instance() 124 | request = make_request(self.update_url) 125 | reply = network_manager.blockingGet(request) 126 | if reply.error() == NoError: 127 | with open(self.map_folder.joinpath("summary.yml"), "wb") as f: 128 | f.write(reply.content()) 129 | request = make_request(download_url) 130 | reply = network_manager.blockingGet(request) 131 | if reply.error() == NoError: 132 | with open(mapfile_path, "wb") as f: 133 | f.write(reply.content()) 134 | 135 | def handle_check_update_response(self, reply: QNetworkReply): 136 | if reply.error() == NoError: 137 | response_data = str(reply.readAll(), "utf-8", "ignore") 138 | self.set_status_label("检查更新成功") 139 | self.update_btn.setEnabled(True) 140 | update_summary = yaml.safe_load(response_data) 141 | for _, map_sum in update_summary.items(): 142 | name = map_sum["name"] 143 | item = self.findItems(name, MatchExactly)[0] 144 | item.setText(2, map_sum["lastUpdated"]) 145 | if item.text(1) != item.text(2): 146 | # 将按钮设置为启用状态 147 | update_btn = self.itemWidget(item, 3) 148 | update_btn.setEnabled(True) 149 | else: 150 | self.set_status_label("检查更新失败,请稍后重试...") 151 | self.update_btn.setEnabled(True) 152 | reply.deleteLater() 153 | 154 | def set_status_label(self, text: str): 155 | self.status_label.setText(text) 156 | self.timer.start(2000) 157 | 158 | def clear_status_label(self): 159 | self.status_label.clear() 160 | self.timer.stop() 161 | 162 | def check_update(self): 163 | self.status_label.setText("检查更新中...") 164 | self.update_btn.setEnabled(False) 165 | network_manager = QgsNetworkAccessManager.instance() 166 | request = make_request(self.update_url, HEADER["Referer"]) 167 | reply = network_manager.get(request) 168 | reply.finished.connect(partial(self.handle_check_update_response, reply)) 169 | 170 | def update_map_enable_state(self): 171 | top_level_item_count = self.topLevelItemCount() 172 | current_status = {} 173 | for i in range(top_level_item_count): 174 | top_level_item = self.topLevelItem(i) 175 | map_name = top_level_item.text(0) 176 | map_id = self.get_map_id_by_name(map_name) 177 | # 获取子项的数量 178 | child_count = top_level_item.childCount() 179 | # 遍历子项 180 | checked_item = [] 181 | for j in range(child_count): 182 | child_item = top_level_item.child(j) 183 | if child_item.checkState(0) == 2: 184 | checked_item.append(child_item.text(0)) 185 | current_status[map_id] = checked_item 186 | # 保存状态 187 | self.conf.set_extra_maps_status(current_status) 188 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/AddMap/sd.py: -------------------------------------------------------------------------------- 1 | import json 2 | import math 3 | 4 | from qgis.PyQt import QtWidgets 5 | from qgis.PyQt.QtWidgets import QAction, QTreeWidgetItem, QListWidgetItem 6 | from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject 7 | from qgis.core import QgsNetworkAccessManager 8 | 9 | from ..icons import icons 10 | from ...compat import ( 11 | Ui_SdDockWidget, 12 | RightDockWidgetArea, 13 | NoError, 14 | AlignCenter, 15 | DescendingOrder, 16 | ) 17 | from ...qgis_utils import add_raster_layer, push_message 18 | from ...utils import PluginConfig, make_request 19 | 20 | 21 | class SdDock(QtWidgets.QDockWidget, Ui_SdDockWidget): 22 | def __init__(self, iface): 23 | super().__init__() 24 | self.iface = iface 25 | self.setupUi(self) 26 | self.pushButton_search.clicked.connect(self.get_his_maps) 27 | self.init_tree_widget() 28 | self.conf = PluginConfig() 29 | self.tk = None 30 | self.pushButton_save.clicked.connect(self.save_tk) 31 | self.sd_tile_data = { 32 | "山东省影像注记": "sdrasterpubmapdj", 33 | "山东省电子地图": "sdpubmap", 34 | "山东省影像地图": "sdrasterpubmap", 35 | } 36 | self.sd_tile_LayerId = { 37 | "sdrasterpubmapdj": "SDRasterPubMapDJ", 38 | "sdpubmap": "SDPubMap", 39 | "sdrasterpubmap": "SDRasterPubMap", 40 | } 41 | self.init_tk() 42 | self.init_list_widget() 43 | 44 | def init_tk(self): 45 | self.tk = self.conf.get_value("Other/sd_tk") 46 | if self.tk is None: 47 | self.pushButton_search.setEnabled(False) 48 | self.listWidget.setEnabled(False) 49 | else: 50 | self.mLineEdit_sdtk.setText(self.tk) 51 | 52 | def init_tree_widget(self): 53 | self.treeWidget.setColumnWidth(0, 160) 54 | self.treeWidget.setColumnWidth(1, 100) 55 | self.treeWidget.setColumnWidth(2, 60) 56 | # 连接双击事件 57 | self.treeWidget.itemDoubleClicked.connect(self.on_item_double_clicked) 58 | # 启用排序功能 59 | self.treeWidget.setSortingEnabled(True) 60 | # 隐藏 url 和 el 列 61 | self.treeWidget.header().hideSection(3) 62 | self.treeWidget.header().hideSection(4) 63 | 64 | def save_tk(self): 65 | tk = self.mLineEdit_sdtk.text() 66 | if tk != "": 67 | self.conf.set_value("Other/sd_tk", tk) 68 | self.init_tk() 69 | self.pushButton_search.setEnabled(True) 70 | self.listWidget.setEnabled(True) 71 | 72 | def init_list_widget(self): 73 | self.listWidget.adjustSize() 74 | self.listWidget.itemDoubleClicked.connect(self.on_listitem_double_clicked) 75 | 76 | for name in self.sd_tile_data: 77 | item = QListWidgetItem(name) 78 | self.listWidget.addItem(item) 79 | 80 | def on_listitem_double_clicked(self, item): 81 | item_name = item.text() 82 | t_id = self.sd_tile_data[item_name] 83 | cap_url = f"https://service.sdmap.gov.cn/tileservice/{t_id}?tk%3D{self.tk}" 84 | cap_url += "%26Service%3DWMTS%26Request%3DGetCapabilities" 85 | 86 | uri = f"crs=EPSG:4490&dpiMode=7&format=image/jpeg&layers={self.sd_tile_LayerId[t_id]}" 87 | if item_name == "山东省影像注记": 88 | uri += ( 89 | f"&styles=default&tileMatrixSet=rasterdj&tilePixelRatio=0&url={cap_url}" 90 | ) 91 | else: 92 | uri += ( 93 | f"&styles=default&tileMatrixSet=raster&tilePixelRatio=0&url={cap_url}" 94 | ) 95 | add_raster_layer(uri, item_name) 96 | 97 | def get_center_point(self): 98 | canvas = self.iface.mapCanvas() 99 | center_point = canvas.center() 100 | source_crs = canvas.mapSettings().destinationCrs() 101 | target_crs = QgsCoordinateReferenceSystem("EPSG:4326") 102 | transform = QgsCoordinateTransform( 103 | source_crs, target_crs, QgsProject.instance() 104 | ) 105 | center_point_4326 = transform.transform(center_point) 106 | return center_point_4326.x(), center_point_4326.y() 107 | 108 | def get_zoom_level(self): 109 | canvas = self.iface.mapCanvas() 110 | current_scale = canvas.scale() 111 | scale_list = [ 112 | 2 * math.pi * 6378137 / (256 * pow(2, x) * 0.00028) for x in range(22) 113 | ] 114 | level = min( 115 | range(len(scale_list)), key=lambda i: abs(scale_list[i] - current_scale) 116 | ) 117 | return level 118 | 119 | def get_his_maps(self): 120 | point = (117.7, 36) 121 | level = 7 122 | # 是否根据画布范围查询 123 | if self.checkBox.isChecked(): 124 | point = self.get_center_point() 125 | level = self.get_zoom_level() 126 | 127 | # 构建查询 128 | network_manager = QgsNetworkAccessManager.instance() 129 | 130 | url = "https://service.sdmap.gov.cn/imgmeta?" 131 | url += f"wktpoint=POINT({point[0]} {point[1]})&level={level}&tk={self.tk}" 132 | request = make_request(url) 133 | reply = network_manager.blockingGet(request) 134 | if reply.error() == NoError: 135 | try: 136 | raw_data = json.loads(reply.content().data()) 137 | except json.decoder.JSONDecodeError: 138 | raw_data = [] 139 | message_title = "天地图·山东 - 查询出错" 140 | if level > 18: 141 | push_message(self.iface, message_title, "缩放层级不能超过18级") 142 | else: 143 | push_message( 144 | self.iface, 145 | message_title, 146 | "请检查画布中心是否在山东省境内!", 147 | ) 148 | 149 | # 筛选出历史影像 150 | # tileservice/sdrasterpubmap 添加方式不同 151 | his_data = [d for d in raw_data if "hisimage" in d["url"]] 152 | sorted_data = sorted(his_data, key=lambda x: x["st"]) 153 | self.add_item(sorted_data) 154 | # 默认按照时间降序排序 155 | self.treeWidget.sortByColumn(1, DescendingOrder) 156 | 157 | def add_item(self, map_data): 158 | self.treeWidget.clear() # 先清空 item 159 | for m in map_data: 160 | item = QTreeWidgetItem(self.treeWidget) 161 | item.setText(0, m.get("name", "")) 162 | item.setText(1, str(m["st"])) 163 | item.setText(2, str(m["reso"])) 164 | item.setText(3, m["url"]) 165 | item.setText(4, str(m["el"])) 166 | item.setTextAlignment(0, AlignCenter) 167 | item.setTextAlignment(1, AlignCenter) 168 | item.setTextAlignment(2, AlignCenter) 169 | 170 | def on_item_double_clicked(self, item): 171 | """ 172 | 处理双击事件 173 | :param item: 被双击的 QTreeWidgetItem 174 | """ 175 | name = f"山东 - {item.text(0)}" 176 | url = item.text(3) 177 | el = item.text(4) 178 | 179 | mapid = url.split("/")[-1] 180 | # 为山东天地图历史影像写了一个简单的 WMTS Capabilities 生成服务 181 | # 源代码见 https://github.com/liuxspro/wmts/blob/main/src/server.ts 182 | his_wmts_server = "https://wmts.liuxs.pro/" 183 | cap_url = f"{his_wmts_server}tianditu/sdhis/{mapid}/{el}?tk%3D{self.tk}" 184 | uri = f"crs=EPSG:4490&format=image/jpeg&layers={mapid}" 185 | uri += f"&styles=default&tileMatrixSet=CGCS2000QuadF3T{el}&url={cap_url}" 186 | 187 | add_raster_layer(uri, name) 188 | 189 | 190 | class SdAction(QAction): 191 | def __init__(self, iface, parent=None): 192 | super().__init__(parent) 193 | self.parent = parent 194 | self.iface = iface 195 | self.dock = SdDock(iface) 196 | self.setIcon(icons["map"]) 197 | self.setText("天地图·山东") 198 | self.triggered.connect(self.open_dock) 199 | 200 | def open_dock(self): 201 | # https://qgis.org/pyqgis/master/gui/QgisInterface.html#qgis.gui.QgisInterface.addTabifiedDockWidget 202 | self.iface.addTabifiedDockWidget( 203 | RightDockWidgetArea, self.dock, ["sd"], raiseTab=True 204 | ) 205 | -------------------------------------------------------------------------------- /tianditu_tools/Styles/PointStyle_316.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 93 | 94 | 95 | 102 | 116 | 117 | 118 | 119 | 0 120 | 0 121 | 0 122 | 123 | -------------------------------------------------------------------------------- /tianditu_tools/ui/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './tianditu_tools/ui/setting.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.11 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from qgis.PyQt import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_SettingDialog(object): 15 | def setupUi(self, SettingDialog): 16 | SettingDialog.setObjectName("SettingDialog") 17 | SettingDialog.resize(582, 312) 18 | self.verticalLayout = QtWidgets.QVBoxLayout(SettingDialog) 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.tabWidget = QtWidgets.QTabWidget(SettingDialog) 21 | self.tabWidget.setObjectName("tabWidget") 22 | self.tab_keyset = QtWidgets.QWidget() 23 | self.tab_keyset.setObjectName("tab_keyset") 24 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_keyset) 25 | self.verticalLayout_4.setObjectName("verticalLayout_4") 26 | self.groupBox = QtWidgets.QGroupBox(self.tab_keyset) 27 | self.groupBox.setObjectName("groupBox") 28 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) 29 | self.verticalLayout_2.setObjectName("verticalLayout_2") 30 | self.horizontalLayout = QtWidgets.QHBoxLayout() 31 | self.horizontalLayout.setObjectName("horizontalLayout") 32 | self.label = QtWidgets.QLabel(self.groupBox) 33 | self.label.setObjectName("label") 34 | self.horizontalLayout.addWidget(self.label) 35 | self.mLineEdit_key = QgsPasswordLineEdit(self.groupBox) 36 | font = QtGui.QFont() 37 | font.setFamily("Consolas") 38 | self.mLineEdit_key.setFont(font) 39 | self.mLineEdit_key.setClearButtonEnabled(False) 40 | self.mLineEdit_key.setShowLockIcon(False) 41 | self.mLineEdit_key.setObjectName("mLineEdit_key") 42 | self.horizontalLayout.addWidget(self.mLineEdit_key) 43 | self.saveButton = QtWidgets.QPushButton(self.groupBox) 44 | self.saveButton.setObjectName("saveButton") 45 | self.horizontalLayout.addWidget(self.saveButton) 46 | self.horizontalLayout.setStretch(0, 1) 47 | self.horizontalLayout.setStretch(1, 10) 48 | self.horizontalLayout.setStretch(2, 2) 49 | self.verticalLayout_2.addLayout(self.horizontalLayout) 50 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 51 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 52 | self.label_2 = QtWidgets.QLabel(self.groupBox) 53 | self.label_2.setObjectName("label_2") 54 | self.horizontalLayout_2.addWidget(self.label_2) 55 | self.keyComboBox = QtWidgets.QComboBox(self.groupBox) 56 | font = QtGui.QFont() 57 | font.setFamily("Consolas") 58 | self.keyComboBox.setFont(font) 59 | self.keyComboBox.setObjectName("keyComboBox") 60 | self.horizontalLayout_2.addWidget(self.keyComboBox) 61 | self.checkBox_key_rand = QtWidgets.QCheckBox(self.groupBox) 62 | self.checkBox_key_rand.setObjectName("checkBox_key_rand") 63 | self.horizontalLayout_2.addWidget(self.checkBox_key_rand) 64 | self.pushButton_copy = QtWidgets.QPushButton(self.groupBox) 65 | self.pushButton_copy.setMaximumSize(QtCore.QSize(50, 16777215)) 66 | self.pushButton_copy.setObjectName("pushButton_copy") 67 | self.horizontalLayout_2.addWidget(self.pushButton_copy) 68 | self.pushButton_delete = QtWidgets.QPushButton(self.groupBox) 69 | self.pushButton_delete.setMaximumSize(QtCore.QSize(50, 16777215)) 70 | self.pushButton_delete.setObjectName("pushButton_delete") 71 | self.horizontalLayout_2.addWidget(self.pushButton_delete) 72 | self.horizontalLayout_2.setStretch(0, 1) 73 | self.horizontalLayout_2.setStretch(1, 10) 74 | self.horizontalLayout_2.setStretch(4, 2) 75 | self.verticalLayout_2.addLayout(self.horizontalLayout_2) 76 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 77 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 78 | self.label_3 = QtWidgets.QLabel(self.groupBox) 79 | self.label_3.setObjectName("label_3") 80 | self.horizontalLayout_3.addWidget(self.label_3) 81 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 82 | self.horizontalLayout_3.addItem(spacerItem) 83 | self.subdomainComboBox = QtWidgets.QComboBox(self.groupBox) 84 | self.subdomainComboBox.setMinimumSize(QtCore.QSize(80, 0)) 85 | self.subdomainComboBox.setBaseSize(QtCore.QSize(200, 0)) 86 | self.subdomainComboBox.setCurrentText("") 87 | self.subdomainComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) 88 | self.subdomainComboBox.setObjectName("subdomainComboBox") 89 | self.horizontalLayout_3.addWidget(self.subdomainComboBox) 90 | self.checkBox_domain_rand = QtWidgets.QCheckBox(self.groupBox) 91 | self.checkBox_domain_rand.setObjectName("checkBox_domain_rand") 92 | self.horizontalLayout_3.addWidget(self.checkBox_domain_rand) 93 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 94 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout() 95 | self.horizontalLayout_4.setObjectName("horizontalLayout_4") 96 | self.verticalLayout_2.addLayout(self.horizontalLayout_4) 97 | self.label_4 = QtWidgets.QLabel(self.groupBox) 98 | self.label_4.setTextFormat(QtCore.Qt.RichText) 99 | self.label_4.setOpenExternalLinks(True) 100 | self.label_4.setObjectName("label_4") 101 | self.verticalLayout_2.addWidget(self.label_4) 102 | spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 103 | self.verticalLayout_2.addItem(spacerItem1) 104 | self.info_status = QtWidgets.QLabel(self.groupBox) 105 | self.info_status.setStyleSheet("color: rgb(128, 128, 128);") 106 | self.info_status.setText("") 107 | self.info_status.setObjectName("info_status") 108 | self.verticalLayout_2.addWidget(self.info_status) 109 | self.verticalLayout_4.addWidget(self.groupBox) 110 | self.tabWidget.addTab(self.tab_keyset, "") 111 | self.tab_map = QtWidgets.QWidget() 112 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 113 | sizePolicy.setHorizontalStretch(0) 114 | sizePolicy.setVerticalStretch(0) 115 | sizePolicy.setHeightForWidth(self.tab_map.sizePolicy().hasHeightForWidth()) 116 | self.tab_map.setSizePolicy(sizePolicy) 117 | self.tab_map.setObjectName("tab_map") 118 | self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.tab_map) 119 | self.verticalLayout_5.setObjectName("verticalLayout_5") 120 | self.verticalLayout_6 = QtWidgets.QVBoxLayout() 121 | self.verticalLayout_6.setObjectName("verticalLayout_6") 122 | self.verticalLayout_5.addLayout(self.verticalLayout_6) 123 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout() 124 | self.horizontalLayout_6.setObjectName("horizontalLayout_6") 125 | self.btn_checkupdate = QtWidgets.QPushButton(self.tab_map) 126 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) 127 | sizePolicy.setHorizontalStretch(0) 128 | sizePolicy.setVerticalStretch(0) 129 | sizePolicy.setHeightForWidth(self.btn_checkupdate.sizePolicy().hasHeightForWidth()) 130 | self.btn_checkupdate.setSizePolicy(sizePolicy) 131 | self.btn_checkupdate.setObjectName("btn_checkupdate") 132 | self.horizontalLayout_6.addWidget(self.btn_checkupdate) 133 | self.label_checkstatus = QtWidgets.QLabel(self.tab_map) 134 | self.label_checkstatus.setMaximumSize(QtCore.QSize(16777215, 40)) 135 | font = QtGui.QFont() 136 | font.setPointSize(8) 137 | self.label_checkstatus.setFont(font) 138 | self.label_checkstatus.setObjectName("label_checkstatus") 139 | self.horizontalLayout_6.addWidget(self.label_checkstatus) 140 | self.verticalLayout_5.addLayout(self.horizontalLayout_6) 141 | self.verticalLayout_5.setStretch(0, 5) 142 | self.verticalLayout_5.setStretch(1, 1) 143 | self.tabWidget.addTab(self.tab_map, "") 144 | self.verticalLayout.addWidget(self.tabWidget) 145 | 146 | self.retranslateUi(SettingDialog) 147 | self.tabWidget.setCurrentIndex(0) 148 | QtCore.QMetaObject.connectSlotsByName(SettingDialog) 149 | 150 | def retranslateUi(self, SettingDialog): 151 | _translate = QtCore.QCoreApplication.translate 152 | SettingDialog.setWindowTitle(_translate("SettingDialog", "设置")) 153 | self.groupBox.setTitle(_translate("SettingDialog", "Key 设置")) 154 | self.label.setText(_translate("SettingDialog", "添加 Key:")) 155 | self.mLineEdit_key.setPlaceholderText(_translate("SettingDialog", "输入天地图key")) 156 | self.saveButton.setText(_translate("SettingDialog", "保存")) 157 | self.label_2.setText(_translate("SettingDialog", "选择 Key:")) 158 | self.checkBox_key_rand.setText(_translate("SettingDialog", "随机")) 159 | self.pushButton_copy.setText(_translate("SettingDialog", "复制")) 160 | self.pushButton_delete.setText(_translate("SettingDialog", "删除")) 161 | self.label_3.setText(_translate("SettingDialog", "子域 Subdomain:")) 162 | self.checkBox_domain_rand.setToolTip(_translate("SettingDialog", "

添加底图时随机选择子域名,可减轻服务器压力,提高可用性。

")) 163 | self.checkBox_domain_rand.setText(_translate("SettingDialog", "随机")) 164 | self.label_4.setText(_translate("SettingDialog", "点击此处前往天地图申请 Key\n" 165 | "

Key 类型建议选择为“浏览器端” 或者 “Android 平台

如果工程同步到 QField 中使用,选择“Android 平台

")) 166 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_keyset), _translate("SettingDialog", "天地图设置")) 167 | self.btn_checkupdate.setText(_translate("SettingDialog", "检查更新")) 168 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_map), _translate("SettingDialog", "地图管理")) 169 | from qgspasswordlineedit import QgsPasswordLineEdit 170 | -------------------------------------------------------------------------------- /tianditu_tools/ui/setting.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SettingDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 582 10 | 312 11 | 12 | 13 | 14 | 设置 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 天地图设置 25 | 26 | 27 | 28 | 29 | 30 | Key 设置 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 添加 Key: 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Consolas 47 | 48 | 49 | 50 | 输入天地图key 51 | 52 | 53 | false 54 | 55 | 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 保存 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 选择 Key: 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Consolas 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 随机 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 50 99 | 16777215 100 | 101 | 102 | 103 | 复制 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 50 112 | 16777215 113 | 114 | 115 | 116 | 删除 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 子域 Subdomain: 128 | 129 | 130 | 131 | 132 | 133 | 134 | Qt::Horizontal 135 | 136 | 137 | 138 | 40 139 | 20 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 80 149 | 0 150 | 151 | 152 | 153 | 154 | 200 155 | 0 156 | 157 | 158 | 159 | 160 | 161 | 162 | QComboBox::InsertAtTop 163 | 164 | 165 | 166 | 167 | 168 | 169 | <html><head/><body><p><span style=" font-size:10pt;">添加底图时随机选择子域名,可减轻服务器压力,</span>提高可用性。</p></body></html> 170 | 171 | 172 | 随机 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | <html><head/><body><style>p { margin: 0.5px;font-size:10px} </style><a href="https://console.tianditu.gov.cn/api/key"><span style=" font-weight:600; text-decoration: underline; color:#808080;">点击此处前往天地图申请 Key</span></a> 185 | <p><span style=" color:#808080;margin: 0; padding: 5px 0;">Key 类型建议选择为“</span><span style=" text-decoration: underline; color:#808080;">浏览器端</span><span style=" color:#808080;">” 或者 “</span><span style=" text-decoration: underline; color:#808080;">Android 平台</span><span style=" color:#808080;">”</span></p><p style="margin: 0; padding: 5px 0;"><span style=" color:#808080;">如果工程同步到 QField 中使用,选择“</span><span style=" text-decoration: underline; color:#808080;">Android 平台</span><span style=" color:#808080;">”</span></p></body></html> 186 | 187 | 188 | Qt::RichText 189 | 190 | 191 | true 192 | 193 | 194 | 195 | 196 | 197 | 198 | Qt::Vertical 199 | 200 | 201 | 202 | 20 203 | 40 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | color: rgb(128, 128, 128); 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 0 227 | 0 228 | 229 | 230 | 231 | 地图管理 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 0 244 | 0 245 | 246 | 247 | 248 | 检查更新 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 16777215 257 | 40 258 | 259 | 260 | 261 | 262 | 8 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | QgsPasswordLineEdit 278 | QLineEdit 279 |
qgspasswordlineedit.h
280 |
281 |
282 | 283 | 284 |
285 | -------------------------------------------------------------------------------- /tianditu_tools/ui/setting_6.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file './tianditu_tools/ui/setting.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.8.1 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from qgis.PyQt import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_SettingDialog(object): 13 | def setupUi(self, SettingDialog): 14 | SettingDialog.setObjectName("SettingDialog") 15 | SettingDialog.resize(582, 312) 16 | self.verticalLayout = QtWidgets.QVBoxLayout(SettingDialog) 17 | self.verticalLayout.setObjectName("verticalLayout") 18 | self.tabWidget = QtWidgets.QTabWidget(parent=SettingDialog) 19 | self.tabWidget.setObjectName("tabWidget") 20 | self.tab_keyset = QtWidgets.QWidget() 21 | self.tab_keyset.setObjectName("tab_keyset") 22 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_keyset) 23 | self.verticalLayout_4.setObjectName("verticalLayout_4") 24 | self.groupBox = QtWidgets.QGroupBox(parent=self.tab_keyset) 25 | self.groupBox.setObjectName("groupBox") 26 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) 27 | self.verticalLayout_2.setObjectName("verticalLayout_2") 28 | self.horizontalLayout = QtWidgets.QHBoxLayout() 29 | self.horizontalLayout.setObjectName("horizontalLayout") 30 | self.label = QtWidgets.QLabel(parent=self.groupBox) 31 | self.label.setObjectName("label") 32 | self.horizontalLayout.addWidget(self.label) 33 | self.mLineEdit_key = QgsPasswordLineEdit(parent=self.groupBox) 34 | font = QtGui.QFont() 35 | font.setFamily("Consolas") 36 | self.mLineEdit_key.setFont(font) 37 | self.mLineEdit_key.setClearButtonEnabled(False) 38 | self.mLineEdit_key.setShowLockIcon(False) 39 | self.mLineEdit_key.setObjectName("mLineEdit_key") 40 | self.horizontalLayout.addWidget(self.mLineEdit_key) 41 | self.saveButton = QtWidgets.QPushButton(parent=self.groupBox) 42 | self.saveButton.setObjectName("saveButton") 43 | self.horizontalLayout.addWidget(self.saveButton) 44 | self.horizontalLayout.setStretch(0, 1) 45 | self.horizontalLayout.setStretch(1, 10) 46 | self.horizontalLayout.setStretch(2, 2) 47 | self.verticalLayout_2.addLayout(self.horizontalLayout) 48 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 49 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 50 | self.label_2 = QtWidgets.QLabel(parent=self.groupBox) 51 | self.label_2.setObjectName("label_2") 52 | self.horizontalLayout_2.addWidget(self.label_2) 53 | self.keyComboBox = QtWidgets.QComboBox(parent=self.groupBox) 54 | font = QtGui.QFont() 55 | font.setFamily("Consolas") 56 | self.keyComboBox.setFont(font) 57 | self.keyComboBox.setObjectName("keyComboBox") 58 | self.horizontalLayout_2.addWidget(self.keyComboBox) 59 | self.checkBox_key_rand = QtWidgets.QCheckBox(parent=self.groupBox) 60 | self.checkBox_key_rand.setObjectName("checkBox_key_rand") 61 | self.horizontalLayout_2.addWidget(self.checkBox_key_rand) 62 | self.pushButton_copy = QtWidgets.QPushButton(parent=self.groupBox) 63 | self.pushButton_copy.setMaximumSize(QtCore.QSize(50, 16777215)) 64 | self.pushButton_copy.setObjectName("pushButton_copy") 65 | self.horizontalLayout_2.addWidget(self.pushButton_copy) 66 | self.pushButton_delete = QtWidgets.QPushButton(parent=self.groupBox) 67 | self.pushButton_delete.setMaximumSize(QtCore.QSize(50, 16777215)) 68 | self.pushButton_delete.setObjectName("pushButton_delete") 69 | self.horizontalLayout_2.addWidget(self.pushButton_delete) 70 | self.horizontalLayout_2.setStretch(0, 1) 71 | self.horizontalLayout_2.setStretch(1, 10) 72 | self.horizontalLayout_2.setStretch(4, 2) 73 | self.verticalLayout_2.addLayout(self.horizontalLayout_2) 74 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 75 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 76 | self.label_3 = QtWidgets.QLabel(parent=self.groupBox) 77 | self.label_3.setObjectName("label_3") 78 | self.horizontalLayout_3.addWidget(self.label_3) 79 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 80 | self.horizontalLayout_3.addItem(spacerItem) 81 | self.subdomainComboBox = QtWidgets.QComboBox(parent=self.groupBox) 82 | self.subdomainComboBox.setMinimumSize(QtCore.QSize(80, 0)) 83 | self.subdomainComboBox.setBaseSize(QtCore.QSize(200, 0)) 84 | self.subdomainComboBox.setCurrentText("") 85 | self.subdomainComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAtTop) 86 | self.subdomainComboBox.setObjectName("subdomainComboBox") 87 | self.horizontalLayout_3.addWidget(self.subdomainComboBox) 88 | self.checkBox_domain_rand = QtWidgets.QCheckBox(parent=self.groupBox) 89 | self.checkBox_domain_rand.setObjectName("checkBox_domain_rand") 90 | self.horizontalLayout_3.addWidget(self.checkBox_domain_rand) 91 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 92 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout() 93 | self.horizontalLayout_4.setObjectName("horizontalLayout_4") 94 | self.verticalLayout_2.addLayout(self.horizontalLayout_4) 95 | self.label_4 = QtWidgets.QLabel(parent=self.groupBox) 96 | self.label_4.setTextFormat(QtCore.Qt.TextFormat.RichText) 97 | self.label_4.setOpenExternalLinks(True) 98 | self.label_4.setObjectName("label_4") 99 | self.verticalLayout_2.addWidget(self.label_4) 100 | spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) 101 | self.verticalLayout_2.addItem(spacerItem1) 102 | self.info_status = QtWidgets.QLabel(parent=self.groupBox) 103 | self.info_status.setStyleSheet("color: rgb(128, 128, 128);") 104 | self.info_status.setText("") 105 | self.info_status.setObjectName("info_status") 106 | self.verticalLayout_2.addWidget(self.info_status) 107 | self.verticalLayout_4.addWidget(self.groupBox) 108 | self.tabWidget.addTab(self.tab_keyset, "") 109 | self.tab_map = QtWidgets.QWidget() 110 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) 111 | sizePolicy.setHorizontalStretch(0) 112 | sizePolicy.setVerticalStretch(0) 113 | sizePolicy.setHeightForWidth(self.tab_map.sizePolicy().hasHeightForWidth()) 114 | self.tab_map.setSizePolicy(sizePolicy) 115 | self.tab_map.setObjectName("tab_map") 116 | self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.tab_map) 117 | self.verticalLayout_5.setObjectName("verticalLayout_5") 118 | self.verticalLayout_6 = QtWidgets.QVBoxLayout() 119 | self.verticalLayout_6.setObjectName("verticalLayout_6") 120 | self.verticalLayout_5.addLayout(self.verticalLayout_6) 121 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout() 122 | self.horizontalLayout_6.setObjectName("horizontalLayout_6") 123 | self.btn_checkupdate = QtWidgets.QPushButton(parent=self.tab_map) 124 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) 125 | sizePolicy.setHorizontalStretch(0) 126 | sizePolicy.setVerticalStretch(0) 127 | sizePolicy.setHeightForWidth(self.btn_checkupdate.sizePolicy().hasHeightForWidth()) 128 | self.btn_checkupdate.setSizePolicy(sizePolicy) 129 | self.btn_checkupdate.setObjectName("btn_checkupdate") 130 | self.horizontalLayout_6.addWidget(self.btn_checkupdate) 131 | self.label_checkstatus = QtWidgets.QLabel(parent=self.tab_map) 132 | self.label_checkstatus.setMaximumSize(QtCore.QSize(16777215, 40)) 133 | font = QtGui.QFont() 134 | font.setPointSize(8) 135 | self.label_checkstatus.setFont(font) 136 | self.label_checkstatus.setObjectName("label_checkstatus") 137 | self.horizontalLayout_6.addWidget(self.label_checkstatus) 138 | self.verticalLayout_5.addLayout(self.horizontalLayout_6) 139 | self.verticalLayout_5.setStretch(0, 5) 140 | self.verticalLayout_5.setStretch(1, 1) 141 | self.tabWidget.addTab(self.tab_map, "") 142 | self.verticalLayout.addWidget(self.tabWidget) 143 | 144 | self.retranslateUi(SettingDialog) 145 | self.tabWidget.setCurrentIndex(0) 146 | QtCore.QMetaObject.connectSlotsByName(SettingDialog) 147 | 148 | def retranslateUi(self, SettingDialog): 149 | _translate = QtCore.QCoreApplication.translate 150 | SettingDialog.setWindowTitle(_translate("SettingDialog", "设置")) 151 | self.groupBox.setTitle(_translate("SettingDialog", "Key 设置")) 152 | self.label.setText(_translate("SettingDialog", "添加 Key:")) 153 | self.mLineEdit_key.setPlaceholderText(_translate("SettingDialog", "输入天地图key")) 154 | self.saveButton.setText(_translate("SettingDialog", "保存")) 155 | self.label_2.setText(_translate("SettingDialog", "选择 Key:")) 156 | self.checkBox_key_rand.setText(_translate("SettingDialog", "随机")) 157 | self.pushButton_copy.setText(_translate("SettingDialog", "复制")) 158 | self.pushButton_delete.setText(_translate("SettingDialog", "删除")) 159 | self.label_3.setText(_translate("SettingDialog", "子域 Subdomain:")) 160 | self.checkBox_domain_rand.setToolTip(_translate("SettingDialog", "

添加底图时随机选择子域名,可减轻服务器压力,提高可用性。

")) 161 | self.checkBox_domain_rand.setText(_translate("SettingDialog", "随机")) 162 | self.label_4.setText(_translate("SettingDialog", "点击此处前往天地图申请 Key\n" 163 | "

Key 类型建议选择为“浏览器端” 或者 “Android 平台

如果工程同步到 QField 中使用,选择“Android 平台

")) 164 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_keyset), _translate("SettingDialog", "天地图设置")) 165 | self.btn_checkupdate.setText(_translate("SettingDialog", "检查更新")) 166 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_map), _translate("SettingDialog", "地图管理")) 167 | from qgspasswordlineedit import QgsPasswordLineEdit 168 | -------------------------------------------------------------------------------- /tianditu_tools/widgets/Setting/dialog.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from qgis.PyQt import QtWidgets 4 | from qgis.PyQt.QtCore import QTimer 5 | from qgis.PyQt.QtWidgets import QApplication 6 | from qgis.core import QgsNetworkAccessManager 7 | 8 | from .mapmanager import MapManager 9 | from ...compat import Ui_SettingDialog, NoError, HttpStatusCodeAttribute, ModeClipboard 10 | from ...utils import ( 11 | tianditu_map_url, 12 | PluginConfig, 13 | PluginDir, 14 | make_request, 15 | HEADER, 16 | ) 17 | 18 | 19 | def check_key_format(key: str) -> object: 20 | """检查key格式 21 | 22 | Args: 23 | key (str): 天地图key 24 | 25 | Returns: 26 | object: 27 | "key_length_error": key的长度有误, 28 | "has_special_character": 含有除字母数字外的其他字符 29 | """ 30 | correct_length = 32 31 | key_length = len(key) 32 | key_length_error = False 33 | if key_length != correct_length: 34 | key_length_error = True 35 | return { 36 | "key_length_error": key_length_error, 37 | "has_special_character": not key.isalnum(), 38 | } 39 | 40 | 41 | class SettingDialog(QtWidgets.QDialog, Ui_SettingDialog): 42 | def __init__(self, toolbar): 43 | super().__init__() 44 | self.check_thread = None 45 | self.mapm = None 46 | self.toolbar = toolbar 47 | # 读取配置 48 | self.conf = PluginConfig() 49 | self.subdomain_list = [f"t{i}" for i in range(8)] 50 | # 设置 status label 的定时器 51 | self.timer = QTimer(self) 52 | self.timer.timeout.connect(self.clear_status_label) 53 | # 设置界面 54 | self.setupUi(self) 55 | self.initUI() 56 | 57 | def initUI(self): 58 | self.init_keyCombo() 59 | # 如果key输入框是空白的,则禁用保存按钮 60 | if len(self.mLineEdit_key.text()) == 0: 61 | self.saveButton.setEnabled(False) 62 | # 给控件添加事件 63 | self.mLineEdit_key.textChanged.connect(self.on_key_LineEdit_changed) 64 | self.saveButton.clicked.connect(self.save_key) 65 | self.pushButton_copy.clicked.connect(self.copy_key) 66 | self.pushButton_delete.clicked.connect(self.del_key) 67 | self.keyComboBox.currentIndexChanged.connect(self.select_key) 68 | # 是否启用key随机 69 | if self.conf.get_bool_value("Tianditu/random_key"): 70 | self.checkBox_key_rand.setChecked(True) 71 | self.keyComboBox.setEnabled(False) 72 | self.pushButton_copy.setEnabled(False) 73 | self.pushButton_delete.setEnabled(False) 74 | self.checkBox_key_rand.stateChanged.connect(self.enable_key_random) 75 | # subdomain 选择 76 | self.checkBox_domain_rand.setChecked( 77 | self.conf.get_bool_value("Tianditu/random") 78 | ) 79 | self.checkBox_domain_rand.stateChanged.connect(self.enable_random) 80 | # subdomian 设置 81 | self.subdomainComboBox.addItems(self.subdomain_list) 82 | self.subdomainComboBox.setCurrentIndex( 83 | self.subdomain_list.index(self.conf.get_value("Tianditu/subdomain")) 84 | ) 85 | self.subdomainComboBox.setEnabled(not self.conf.get_value("Tianditu/random")) 86 | self.subdomainComboBox.currentIndexChanged.connect(self.select_subdomain) 87 | # init map manager 88 | map_folder = PluginDir.joinpath("maps") 89 | self.mapm = MapManager( 90 | map_folder=map_folder, 91 | parent=self.tab_map, 92 | update_btn=self.btn_checkupdate, 93 | status_label=self.label_checkstatus, 94 | ) 95 | self.verticalLayout_6.addWidget(self.mapm) 96 | self.btn_checkupdate.clicked.connect(self.mapm.check_update) 97 | # 98 | self.tabWidget.currentChanged.connect(self.adjust_tab_height) 99 | 100 | def adjust_tab_height(self): 101 | current_index = self.tabWidget.currentIndex() 102 | if current_index == 0: 103 | self.setFixedHeight(312) 104 | else: 105 | self.setFixedHeight(500) # 根据内容计算高度 106 | 107 | def set_status_label(self, text: str): 108 | """ 109 | 创建提示 110 | """ 111 | self.info_status.setText(text) 112 | self.timer.start(2000) 113 | 114 | def clear_status_label(self): 115 | self.info_status.clear() 116 | self.timer.stop() 117 | 118 | def get_key_by_masked(self, masked): 119 | key_list = self.conf.get_key_list() 120 | filtered_items = [key for key in key_list if key.startswith(masked[:8])] 121 | if len(filtered_items) > 0: 122 | return filtered_items[0] 123 | return "" 124 | 125 | def init_keyCombo(self): 126 | self.keyComboBox.clear() # 先清除 127 | # 如果 key 列表中没有值, keyComboBox 添加提示 128 | key_list = self.conf.get_key_list() 129 | if len(key_list) == 0: 130 | self.keyComboBox.addItem("请添加 key") 131 | self.pushButton_delete.setEnabled(False) 132 | self.pushButton_copy.setEnabled(False) 133 | else: 134 | self.keyComboBox.addItems([f"{key[:8]}****" for key in key_list]) 135 | # 将 keyComboBox 的当前值改为当前选用的 key 136 | current_key = self.conf.get_key() 137 | index = key_list.index(current_key) 138 | self.keyComboBox.setCurrentIndex(index) 139 | self.pushButton_delete.setEnabled(True) 140 | self.pushButton_copy.setEnabled(True) 141 | 142 | def copy_key(self): 143 | current_key = self.keyComboBox.currentText() 144 | full_key = self.get_key_by_masked(current_key) 145 | clipboard = QApplication.clipboard() 146 | clipboard.setText(full_key, ModeClipboard) 147 | self.set_status_label(f"已复制{current_key}到剪贴板") 148 | 149 | def handle_key_check(self, reply, key): 150 | key_list = self.conf.get_key_list() 151 | # 获取状态码 152 | status_code = reply.attribute(HttpStatusCodeAttribute) 153 | if reply.error() == NoError: 154 | response_data = reply.readAll() 155 | png_signature = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" 156 | if response_data[:8] == png_signature: 157 | if self.keyComboBox.itemText(0) == "请添加 key": 158 | self.keyComboBox.removeItem(0) 159 | self.mLineEdit_key.setText("") 160 | key_list.append(key) 161 | self.conf.save_key_list(key_list) 162 | self.saveButton.setEnabled(False) 163 | self.set_status_label("保存成功") 164 | self.init_keyCombo() 165 | else: 166 | if status_code == 403: 167 | # 当期 2024-06-09 似乎不需要设置 Referer 也能正常访问 168 | # {'msg': '非法Key', 'resolve': '请到API控制台重新申请Key', 'code': 1} 169 | # {'msg': '域名不匹配', 'resolve': '请到API控制台查看域名配置并修改', 'code': 7} 170 | # {'msg': '权限类型错误', 'resolve': 'Key权限类型为:服务端,请使用服务端访问!', 'code': 12} 171 | # {'msg': '权限类型错误', 'resolve': '不支持的key类型!', 'code': 18} 172 | response_data = str(reply.readAll(), "utf-8", "ignore") 173 | json_data = json.loads(response_data) 174 | self.set_status_label( 175 | f"错误: {json_data.get('msg')} {json_data.get('resolve')}" 176 | ) 177 | elif status_code == 418: 178 | self.set_status_label("错误: 疑似攻击") 179 | else: 180 | self.set_status_label("未知错误") 181 | 182 | def save_key(self): 183 | key = self.mLineEdit_key.text() 184 | key_list = self.conf.get_key_list() 185 | if key in key_list: 186 | self.set_status_label("key 已存在") 187 | return 188 | url = tianditu_map_url("vec", key, "t0") 189 | tile_url = url.format(x=0, y=0, z=0) 190 | network_manager = QgsNetworkAccessManager.instance() 191 | request = make_request(tile_url, HEADER.get("Referer")) 192 | reply = network_manager.get(request) 193 | # reply.finished.connect(partial(self.handle_key_check, reply, key)) 194 | reply.finished.connect(lambda: self.handle_key_check(reply, key)) 195 | 196 | def select_key(self): 197 | if self.keyComboBox.count() > 0: 198 | masked_key = self.keyComboBox.currentText() 199 | key = self.get_key_by_masked(masked_key) 200 | self.conf.set_key(key) 201 | self.set_status_label(f"设置当前 key 为{masked_key}") 202 | 203 | def del_key(self): 204 | current_index = self.keyComboBox.currentIndex() 205 | key_list = self.conf.get_key_list() 206 | del key_list[current_index] 207 | self.conf.save_key_list(key_list) 208 | self.set_status_label("已删除") 209 | self.init_keyCombo() 210 | 211 | def handle_ping_finished(self, status): 212 | min_time = min(status) 213 | min_index = status.index(min_time) 214 | for i in range(8): 215 | self.comboBox.setItemText(i, f"t{i} {status[i]}") 216 | self.comboBox.setItemText(min_index, f"t{min_index} {status[min_index]}*") 217 | 218 | def on_key_LineEdit_changed(self): 219 | current_text = self.mLineEdit_key.text() 220 | # 删除key中的空格以及非打印字符 221 | filtered_text = "".join( 222 | [c for c in current_text if c.isprintable() and not c.isspace()] 223 | ) 224 | if filtered_text != current_text: 225 | self.mLineEdit_key.setText(filtered_text) 226 | # 检查key格式 227 | if len(self.mLineEdit_key.text()) > 0: 228 | key_format = check_key_format(self.mLineEdit_key.text()) 229 | if key_format["key_length_error"]: 230 | self.info_status.setText("无效key: 格式错误(长度不对)") 231 | self.saveButton.setEnabled(False) 232 | elif key_format["has_special_character"]: 233 | self.info_status.setText("无效key: 含非常规字符") 234 | self.saveButton.setEnabled(False) 235 | else: 236 | self.info_status.setText("点击保存") 237 | self.saveButton.setEnabled(True) 238 | else: 239 | self.info_status.clear() 240 | 241 | def select_subdomain(self): 242 | selected_index = self.subdomainComboBox.currentIndex() 243 | selected_domain = self.subdomain_list[selected_index] 244 | self.conf.set_value("Tianditu/subdomain", selected_domain) 245 | self.set_status_label(f"设置 subdomain 为 {selected_domain}") 246 | 247 | def enable_random(self): 248 | if self.checkBox_domain_rand.isChecked(): 249 | self.conf.set_value("Tianditu/random", True) 250 | self.subdomainComboBox.setEnabled(False) 251 | self.set_status_label("设置 subdomain 为 随机") 252 | else: 253 | self.conf.set_value("Tianditu/random", False) 254 | self.subdomainComboBox.setEnabled(True) 255 | 256 | def enable_key_random(self): 257 | if self.checkBox_key_rand.isChecked(): 258 | self.conf.set_value("Tianditu/random_key", True) 259 | self.keyComboBox.setEnabled(False) 260 | self.pushButton_copy.setEnabled(False) 261 | self.pushButton_delete.setEnabled(False) 262 | self.set_status_label("设置 key 为 随机") 263 | else: 264 | self.conf.set_value("Tianditu/random_key", False) 265 | self.keyComboBox.setEnabled(True) 266 | self.pushButton_copy.setEnabled(True) 267 | self.pushButton_delete.setEnabled(True) 268 | 269 | def closeEvent(self, event): 270 | # 在对话框关闭时触发的事件 271 | self.mapm.update_map_enable_state() 272 | self.toolbar.add_button.setup_action() 273 | event.accept() # 接受关闭事件,关闭对话框 274 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.12" 4 | resolution-markers = [ 5 | "sys_platform == 'win32'", 6 | "sys_platform != 'win32'", 7 | ] 8 | 9 | [manifest] 10 | constraints = [{ name = "pyqt5-qt5", marker = "sys_platform == 'win32'", specifier = "<=5.15.2" }] 11 | 12 | [[package]] 13 | name = "pyqt5" 14 | version = "5.15.11" 15 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 16 | dependencies = [ 17 | { name = "pyqt5-qt5" }, 18 | { name = "pyqt5-sip" }, 19 | ] 20 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52" } 21 | wheels = [ 22 | { url = "https://mirrors.aliyun.com/pypi/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2" }, 23 | { url = "https://mirrors.aliyun.com/pypi/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6" }, 24 | { url = "https://mirrors.aliyun.com/pypi/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1" }, 25 | { url = "https://mirrors.aliyun.com/pypi/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd" }, 26 | { url = "https://mirrors.aliyun.com/pypi/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517" }, 27 | ] 28 | 29 | [[package]] 30 | name = "pyqt5-qt5" 31 | version = "5.15.2" 32 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 33 | wheels = [ 34 | { url = "https://mirrors.aliyun.com/pypi/packages/62/09/99a222b0360616250fb2e6003a54e43a2a06b0774f0f8d5daafb86a2c375/PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154" }, 35 | { url = "https://mirrors.aliyun.com/pypi/packages/83/d4/241a6a518d0bcf0a9fcdcbad5edfed18d43e884317eab8d5230a2b27e206/PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a" }, 36 | { url = "https://mirrors.aliyun.com/pypi/packages/1c/7e/ce7c66a541a105fa98b41d6405fe84940564695e29fc7dccf6d9e8c5f898/PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327" }, 37 | { url = "https://mirrors.aliyun.com/pypi/packages/37/97/5d3b222b924fa2ed4c2488925155cd0b03fd5d09ee1cfcf7c553c11c9f66/PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962" }, 38 | ] 39 | 40 | [[package]] 41 | name = "pyqt5-sip" 42 | version = "12.17.0" 43 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 44 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/01/79/086b50414bafa71df494398ad277d72e58229a3d1c1b1c766d12b14c2e6d/pyqt5_sip-12.17.0.tar.gz", hash = "sha256:682dadcdbd2239af9fdc0c0628e2776b820e128bec88b49b8d692fe682f90b4f" } 45 | wheels = [ 46 | { url = "https://mirrors.aliyun.com/pypi/packages/a3/e6/e51367c28d69b5a462f38987f6024e766fd8205f121fe2f4d8ba2a6886b9/PyQt5_sip-12.17.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ea08341c8a5da00c81df0d689ecd4ee47a95e1ecad9e362581c92513f2068005" }, 47 | { url = "https://mirrors.aliyun.com/pypi/packages/64/3b/e6d1f772b41d8445d6faf86cc9da65910484ebd9f7df83abc5d4955437d0/PyQt5_sip-12.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4a92478d6808040fbe614bb61500fbb3f19f72714b99369ec28d26a7e3494115" }, 48 | { url = "https://mirrors.aliyun.com/pypi/packages/ed/c5/d17fc2ddb9156a593710c88afd98abcf4055a2224b772f8bec2c6eea879c/PyQt5_sip-12.17.0-cp312-cp312-win32.whl", hash = "sha256:b0ff280b28813e9bfd3a4de99490739fc29b776dc48f1c849caca7239a10fc8b" }, 49 | { url = "https://mirrors.aliyun.com/pypi/packages/fe/c5/1174988d52c732d07033cf9a5067142b01d76be7731c6394a64d5c3ef65c/PyQt5_sip-12.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:54c31de7706d8a9a8c0fc3ea2c70468aba54b027d4974803f8eace9c22aad41c" }, 50 | { url = "https://mirrors.aliyun.com/pypi/packages/fd/5d/f234e505af1a85189310521447ebc6052ebb697efded850d0f2b2555f7aa/PyQt5_sip-12.17.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c7a7ff355e369616b6bcb41d45b742327c104b2bf1674ec79b8d67f8f2fa9543" }, 51 | { url = "https://mirrors.aliyun.com/pypi/packages/cd/cb/3b2050e9644d0021bdf25ddf7e4c3526e1edd0198879e76ba308e5d44faf/PyQt5_sip-12.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:419b9027e92b0b707632c370cfc6dc1f3b43c6313242fc4db57a537029bd179c" }, 52 | { url = "https://mirrors.aliyun.com/pypi/packages/51/61/b8ebde7e0b32d0de44c521a0ace31439885b0423d7d45d010a2f7d92808c/PyQt5_sip-12.17.0-cp313-cp313-win32.whl", hash = "sha256:351beab964a19f5671b2a3e816ecf4d3543a99a7e0650f88a947fea251a7589f" }, 53 | { url = "https://mirrors.aliyun.com/pypi/packages/15/ed/ff94d6b2910e7627380cb1fc9a518ff966e6d78285c8e54c9422b68305db/PyQt5_sip-12.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:672c209d05661fab8e17607c193bf43991d268a1eefbc2c4551fbf30fd8bb2ca" }, 54 | ] 55 | 56 | [[package]] 57 | name = "pyqt6" 58 | version = "6.8.1" 59 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 60 | dependencies = [ 61 | { name = "pyqt6-qt6" }, 62 | { name = "pyqt6-sip" }, 63 | ] 64 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ce/bf/ff284a136b39cb1873c18e4fca4a40a8847c84a1910c5fb38c6a77868968/pyqt6-6.8.1.tar.gz", hash = "sha256:91d937d6166274fafd70f4dee11a8da6dbfdb0da53de05f5d62361ddf775e256" } 65 | wheels = [ 66 | { url = "https://mirrors.aliyun.com/pypi/packages/09/da/70971b3d7f53a68644ea32544d3786dfbbb162d18572ac1defcf5a6481d5/PyQt6-6.8.1-cp39-abi3-macosx_10_14_universal2.whl", hash = "sha256:0425f9eebdd5d4e57ab36424c9382f2ea06670c3c550fa0028c2b19bd0a1d7bd" }, 67 | { url = "https://mirrors.aliyun.com/pypi/packages/be/25/a4392c323a0fb97eb5f449b7594f37e93d9794b900756b43cd65772def77/PyQt6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:36bf48e3df3a6ff536e703315d155480ef4e260396eb5469eb7a875bc5bb7ab4" }, 68 | { url = "https://mirrors.aliyun.com/pypi/packages/de/a3/e528b4cc3394f2ae15b531c17f27b53de756a8c0404dfa9c184502367c48/PyQt6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:2eac2267a34828b8db7660dd3cc3b3b5fd76a92e61ad45471565b01221cb558b" }, 69 | { url = "https://mirrors.aliyun.com/pypi/packages/f2/69/11404cfcb916bd7207805c21432ecab0401779361d48b67f28ae9337f70d/PyQt6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:70bad7b890a8f9e9e5fb9598c544b832d9d9d99a9519e0009cb29c1e15e96632" }, 70 | { url = "https://mirrors.aliyun.com/pypi/packages/00/2a/21a555aea9bc8abc4f09017b922dbdf509c421f70506d4c83d2e8f4315b2/PyQt6-6.8.1-cp39-abi3-win_arm64.whl", hash = "sha256:a40f878e8e5eeeb0bba995152d07eeef9375ea0116df0f4aad0a6b97c8ad1175" }, 71 | ] 72 | 73 | [[package]] 74 | name = "pyqt6-qt6" 75 | version = "6.8.2" 76 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 77 | wheels = [ 78 | { url = "https://mirrors.aliyun.com/pypi/packages/1e/a4/3d764e05955382b3dc7227cbfde090700edd63431147f1c66d428ccac45c/PyQt6_Qt6-6.8.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:470dd4211fe5a67b0565e0202e7aa67816e5dcf7d713528b88327adaebd0934e" }, 79 | { url = "https://mirrors.aliyun.com/pypi/packages/d6/b3/6d4f8257b46554fb2c89b33a6773a3f05ed961b3cd83828caee5dc79899f/PyQt6_Qt6-6.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40cda901a3e1617e79225c354fe9d89b80249f0a6c6aaa18b40938e05bbf7d1f" }, 80 | { url = "https://mirrors.aliyun.com/pypi/packages/92/95/0036435b9e2cbd22e08f14eec2362c32fc641660c6e4aea6f59d165cb5fc/PyQt6_Qt6-6.8.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fb6d0acdd7d43c33fb8b9d2dd7922d381cdedd00da316049fbe01fc1973e6f05" }, 81 | { url = "https://mirrors.aliyun.com/pypi/packages/6e/fb/c01dde044eca1542d88cac72fc99369af76a981cc2f52790236efa566e01/PyQt6_Qt6-6.8.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:5970c85d22cbe5c476418994549161b23ed938e25b04fc4ca8fabf6dcac7b03f" }, 82 | { url = "https://mirrors.aliyun.com/pypi/packages/1a/f7/31f03a9f5e6c7cc23ceb2bd0d9c2df0518837f7af0e693e15b6e0881b8b0/PyQt6_Qt6-6.8.2-py3-none-win_amd64.whl", hash = "sha256:28e2bb641f05b01e498503c3ef01c8a919d6e0e96b50230301c0baac2b7d1433" }, 83 | { url = "https://mirrors.aliyun.com/pypi/packages/00/c9/102c9537795ca11c12120ec9d5f554d9437787f52d8e23fbc8269e6a2699/PyQt6_Qt6-6.8.2-py3-none-win_arm64.whl", hash = "sha256:912afdddd0dfc666ce1c16bc4695e2acd680db72343e4f7a2b7c053a0146b4bc" }, 84 | ] 85 | 86 | [[package]] 87 | name = "pyqt6-sip" 88 | version = "13.10.0" 89 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } 90 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/90/18/0405c54acba0c8e276dd6f0601890e6e735198218d031a6646104870fe22/pyqt6_sip-13.10.0.tar.gz", hash = "sha256:d6daa95a0bd315d9ec523b549e0ce97455f61ded65d5eafecd83ed2aa4ae5350" } 91 | wheels = [ 92 | { url = "https://mirrors.aliyun.com/pypi/packages/69/81/66d9bdacb790592a0641378749a047f12e3b254cdc2cb51f7ed636cf01d2/PyQt6_sip-13.10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:48791db2914fc39c3218519a02d2a5fd3fcd354a1be3141a57bf2880701486f2" }, 93 | { url = "https://mirrors.aliyun.com/pypi/packages/26/2c/4796c209009a018e0d4a5c406d5a519234c5a378f370dc679d0ad5f455b2/PyQt6_sip-13.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:466d6b4791973c9fcbdc2e0087ed194b9ea802a8c3948867a849498f0841c70c" }, 94 | { url = "https://mirrors.aliyun.com/pypi/packages/99/34/2ec54bd475f0a811df1d32be485f2344cf9e8b388ce7adb26b46ce5552d4/PyQt6_sip-13.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ae15358941f127cd3d1ab09c1ebd45c4dabb0b2e91587b9eebde0279d0039c54" }, 95 | { url = "https://mirrors.aliyun.com/pypi/packages/0c/e4/82099bb4ab8bc152b5718541e93c0b3adf7566c0f307c9e58e2368b8c517/PyQt6_sip-13.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad573184fa8b00041944e5a17d150ab0d08db2d2189e39c9373574ebab3f2e58" }, 96 | { url = "https://mirrors.aliyun.com/pypi/packages/e3/09/90e0378887a3cb9664da77061229cf8e97e6ec25a5611b7dbc9cc3e02c78/PyQt6_sip-13.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:2d579d810d0047d40bde9c6aef281d6ed218db93c9496ebc9e55b9e6f27a229d" }, 97 | { url = "https://mirrors.aliyun.com/pypi/packages/6b/0c/8d1de48b45b565a46bf4757341f13f9b1853a7d2e6b023700f0af2c213ab/PyQt6_sip-13.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7b6e250c2e7c14702a623f2cc1479d7fb8db2b6eee9697cac10d06fe79c281bb" }, 98 | { url = "https://mirrors.aliyun.com/pypi/packages/af/13/e2cc2b667a9f5d44c2d0e18fa6e1066fca3f4521dcb301f4b5374caeb33e/PyQt6_sip-13.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fcb30756568f8cd59290f9ef2ae5ee3e72ff9cdd61a6f80c9e3d3b95ae676be" }, 99 | { url = "https://mirrors.aliyun.com/pypi/packages/20/1a/5c6fcae85edb65cf236c9dc6d23b279b5316e94cdca1abdee6d0a217ddbb/PyQt6_sip-13.10.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:757ac52c92b2ef0b56ecc7cd763b55a62d3c14271d7ea8d03315af85a70090ff" }, 100 | { url = "https://mirrors.aliyun.com/pypi/packages/b9/db/6924ec985be7d746772806b96ab81d24263ef72f0249f0573a82adaed75e/PyQt6_sip-13.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:571900c44a3e38738d696234d94fe2043972b9de0633505451c99e2922cb6a34" }, 101 | { url = "https://mirrors.aliyun.com/pypi/packages/77/c3/9e44729b582ee7f1d45160e8c292723156889f3e38ce6574f88d5ab8fa02/PyQt6_sip-13.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:39cba2cc71cf80a99b4dc8147b43508d4716e128f9fb99f5eb5860a37f082282" }, 102 | ] 103 | 104 | [[package]] 105 | name = "qgis-plugin-tianditu" 106 | version = "0.5.2" 107 | source = { virtual = "." } 108 | dependencies = [ 109 | { name = "pyqt5" }, 110 | { name = "pyqt6" }, 111 | ] 112 | 113 | [package.metadata] 114 | requires-dist = [ 115 | { name = "pyqt5", specifier = ">=5.15.11" }, 116 | { name = "pyqt6", specifier = ">=6.8.1" }, 117 | ] 118 | --------------------------------------------------------------------------------