├── .python-version
├── qgis-plugin-amap
├── README.md
├── images
│ ├── icon.png
│ ├── start.svg
│ ├── stop.svg
│ └── amap.svg
├── __init__.py
├── metadata.txt
├── utils.py
├── server.py
├── plugin.py
├── transform.py
└── tile.py
├── README.md
├── pyproject.toml
├── LICENSE
├── .gitignore
└── uv.lock
/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/README.md:
--------------------------------------------------------------------------------
1 | see: https://garden.liuxs.pro/amap-rectify
--------------------------------------------------------------------------------
/qgis-plugin-amap/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuxspro/amap-rectify/main/qgis-plugin-amap/images/icon.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # amap-rectify
2 | Rectify the map from GCJ-02 to WGS-84 coordinate system
3 |
4 | see: https://garden.liuxs.pro/amap-rectify
--------------------------------------------------------------------------------
/qgis-plugin-amap/__init__.py:
--------------------------------------------------------------------------------
1 | from .plugin import AMAP
2 |
3 |
4 | def classFactory(iface):
5 | """QGIS Plugin"""
6 | return AMAP(iface)
7 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/images/start.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/images/stop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "amap-rectify"
3 | version = "0.1.0"
4 | description = "Rectify the map from GCJ-02 to WGS-84 coordinate system"
5 | readme = "README.md"
6 | requires-python = ">=3.13"
7 | dependencies = [
8 | "fastapi>=0.115.13",
9 | "httpx>=0.28.1",
10 | "pillow>=11.2.1",
11 | "requests>=2.32.4",
12 | "uvicorn>=0.34.3",
13 | ]
14 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/metadata.txt:
--------------------------------------------------------------------------------
1 | [general]
2 | name=AMAP
3 | qgisMinimumVersion=3.14
4 | description=高德瓦片地图(无偏)加载
5 | about=高德瓦片地图(无偏)加载,启动本地服务后方可添加高德图层.仅供测试:)
6 | version=0.1.0-dev
7 | tags=basemap, xyz, 高德地图
8 | icon=images/icon.png
9 | experimental=False
10 | deprecated=False
11 | plugin_dependencies=pillow,httpx,fastapi,uvicorn
12 | author=liuxspro
13 | email=liuxspro@gmail.com
14 | homepage=https://github.com/liuxspro/amap-rectify
15 | tracker=https://github.com/liuxspro/amap-rectify/issues
16 | repository=https://github.com/liuxspro/amap-rectify
17 | changelog=
--------------------------------------------------------------------------------
/qgis-plugin-amap/utils.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from qgis.core import QgsProject, QgsRasterLayer, QgsMessageLog, Qgis
4 |
5 | PluginDir = Path(__file__).parent
6 | CACHE_DIR = PluginDir.joinpath("./cache")
7 |
8 |
9 | def log_message(message):
10 | QgsMessageLog.logMessage(f"{message}", "AMAP", Qgis.Info)
11 |
12 |
13 | def add_raster_layer(uri: str, name: str, provider_type: str = "wms") -> None:
14 | """QGIS 添加栅格图层
15 |
16 | Args:
17 | uri (str): 栅格图层uri
18 | name (str): 栅格图层名称
19 | provider_type(str): 栅格图层类型(wms,arcgismapserver)
20 | Reference: https://qgis.org/pyqgis/3.32/core/QgsRasterLayer.html
21 | """
22 | raster_layer = QgsRasterLayer(uri, name, provider_type)
23 | if raster_layer.isValid():
24 | QgsProject.instance().addMapLayer(raster_layer)
25 | else:
26 | print(f"无效的图层 invalid Layer\n{uri}")
27 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/images/amap.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 liuxspro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/server.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import time
3 | from io import BytesIO
4 | from typing import Optional
5 |
6 | from fastapi import FastAPI, Response
7 | from uvicorn import Server, Config
8 |
9 | from .tile import get_tile_async
10 | from .utils import log_message
11 |
12 | app = FastAPI()
13 |
14 |
15 | @app.get("/tile/amap/{map_id}/{z}/{x}/{y}")
16 | async def tile(map_id, z: int, x: int, y: int):
17 | image = await get_tile_async(x, y, z, map_id)
18 | img_buffer = BytesIO()
19 | image.save(img_buffer, format="PNG")
20 | img_bytes = img_buffer.getvalue()
21 | img_buffer.close()
22 | return Response(content=img_bytes, media_type="image/png")
23 |
24 |
25 | class ServerManager:
26 | def __init__(self, _app: FastAPI, host: str = "0.0.0.0", port: int = 8080):
27 | self.app = _app
28 | self.host = host
29 | self.port = port
30 | self.server: Optional[Server] = None
31 | self.server_thread: Optional[threading.Thread] = None
32 | self._is_running = False
33 |
34 | def is_running(self) -> bool:
35 | """检查服务器是否正在运行"""
36 | return self._is_running and self.server_thread and self.server_thread.is_alive()
37 |
38 | def start(self) -> bool:
39 | # 如果服务器已在运行,先停止
40 |
41 | if self.is_running():
42 | self.stop()
43 |
44 | try:
45 | # 创建服务器配置
46 | config = Config(
47 | app=self.app,
48 | host=self.host,
49 | port=self.port,
50 | lifespan="on",
51 | # log_level="info",
52 | log_config=None
53 | )
54 | self.server = Server(config)
55 |
56 | # 创建并启动服务器线程
57 | self.server_thread = threading.Thread(target=self._run_server)
58 | self.server_thread.daemon = True
59 | self.server_thread.start()
60 |
61 | # 等待服务器启动(最多5秒)
62 | start_time = time.time()
63 | while not self._is_running and (time.time() - start_time) < 5:
64 | time.sleep(0.1)
65 |
66 | if self._is_running:
67 | return True
68 | else:
69 | return False
70 | except Exception as e:
71 | log_message(f"Error: {e}")
72 | return False
73 |
74 | def _run_server(self):
75 | """内部方法:运行服务器"""
76 | try:
77 | self._is_running = True
78 | self.server.run()
79 | except Exception as e:
80 | print(f"🚨 Server crashed: {str(e)}")
81 | finally:
82 | self._is_running = False
83 |
84 | def stop(self, timeout: float = 5.0) -> bool | None:
85 | if not self.is_running():
86 | return False
87 |
88 | try:
89 | # 通知服务器退出
90 | if self.server:
91 | self.server.should_exit = True
92 |
93 | # 等待线程结束
94 | if self.server_thread:
95 | self.server_thread.join(timeout=timeout)
96 |
97 | # 如果线程仍然存活,强制终止
98 | if self.server_thread and self.server_thread.is_alive():
99 | if self.server:
100 | self.server.force_exit = True
101 | self.server_thread.join(timeout=1.0)
102 | # 最后尝试强制终止
103 | if self.server_thread.is_alive():
104 | return False
105 | return True
106 | except Exception as e:
107 | print(f"Error: {e}")
108 | return False
109 | finally:
110 | # 清理资源
111 | self.server = None
112 | self.server_thread = None
113 | self._is_running = False
114 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/plugin.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import quote
2 |
3 | from qgis.PyQt.QtGui import QIcon
4 | from qgis.PyQt.QtWidgets import QAction
5 |
6 | from .server import ServerManager, app
7 | from .tile import amap_name
8 | from .utils import PluginDir, add_raster_layer, log_message
9 |
10 |
11 | def add_map(mapid):
12 | map_url = f"http://localhost:8080/tile/amap/{mapid}/{{z}}/{{x}}/{{y}}"
13 | map_name = amap_name[mapid]
14 | # URL编码处理
15 | encoded_url = quote(map_url, safe=":/?=")
16 | uri = f"type=xyz&url={encoded_url}&zmax=18&zmin=2"
17 | add_raster_layer(uri, f"高德地图 - {map_name}")
18 |
19 |
20 | class AMAP:
21 | def __init__(self, iface):
22 | self.iface = iface
23 | self.server = ServerManager(app, port=8080)
24 | # 添加动作列表
25 | self.actions = []
26 | self.start_action = None
27 | self.add_map_cations = []
28 | self.stop_action = None
29 | # 添加菜单
30 | self.menu = None
31 |
32 | def initGui(self):
33 | """创建菜单项和工具栏图标"""
34 | log_message("初始化AMAP插件...")
35 |
36 | # 创建菜单
37 | self.menu = self.iface.mainWindow().menuBar().addMenu("&AMAP")
38 |
39 | # 启动服务 Action
40 | icon = QIcon(str(PluginDir / "images" / "start.svg"))
41 | self.start_action = QAction(icon, "启动服务器", self.iface.mainWindow())
42 | self.start_action.triggered.connect(self.start_server)
43 |
44 | self.actions.append(self.start_action)
45 |
46 | # 停止服务 Action
47 | self.stop_action = QAction(
48 | QIcon(str(PluginDir / "images" / "stop.svg")),
49 | "停止服务器",
50 | self.iface.mainWindow(),
51 | )
52 | self.stop_action.triggered.connect(self.stop_server)
53 | self.actions.append(self.stop_action)
54 |
55 | # 添加 Action 到菜单
56 | self.menu.addAction(self.start_action)
57 | self.menu.addAction(self.stop_action)
58 | self.menu.addSeparator()
59 |
60 | # 添加地图 Action
61 | amap_icon = QIcon(str(PluginDir / "images" / "amap.svg"))
62 | for mapid in amap_name:
63 | action = QAction(
64 | amap_icon, f"高德地图 - {amap_name[mapid]}", self.iface.mainWindow()
65 | )
66 | action.triggered.connect(lambda checked, mid=mapid: add_map(mid))
67 | self.add_map_cations.append(action)
68 |
69 | for action in self.add_map_cations:
70 | self.actions.append(action)
71 | self.menu.addAction(action)
72 |
73 | # 添加动作到工具栏
74 | # self.iface.addToolBarIcon(self.start_action)
75 | # self.iface.addToolBarIcon(self.stop_action)
76 |
77 | # 初始状态:启动按钮可用,停止按钮不可用
78 | self.stop_action.setEnabled(False)
79 | for action in self.add_map_cations:
80 | action.setEnabled(False)
81 |
82 | log_message("AMAP插件初始化完成")
83 |
84 | def start_server(self):
85 | if self.server.start():
86 | log_message("✅ 服务器启动成功!")
87 | log_message(
88 | f"🌐 可以访问以下地址:http://localhost:{self.server.port}/docs",
89 | )
90 | # 更新UI状态
91 | self.start_action.setEnabled(False)
92 | self.stop_action.setEnabled(True)
93 | for action in self.add_map_cations:
94 | action.setEnabled(True)
95 | else:
96 | log_message("❌ 服务器启动失败")
97 |
98 | def stop_server(self):
99 | if self.server.stop():
100 | # 更新UI状态
101 | self.start_action.setEnabled(True)
102 | self.stop_action.setEnabled(False)
103 | for action in self.add_map_cations:
104 | action.setEnabled(False)
105 | log_message("✅ 服务器已停止")
106 |
107 | def unload(self):
108 | """从QGIS界面卸载插件"""
109 | log_message("卸载AMAP插件...")
110 |
111 | # 停止服务器
112 | self.stop_server()
113 |
114 | # 移除菜单
115 | if self.menu:
116 | self.iface.mainWindow().menuBar().removeAction(self.menu.menuAction())
117 |
118 | # 移除工具栏图标
119 | for action in self.actions:
120 | self.iface.removeToolBarIcon(action)
121 |
122 | print("AMAP插件已卸载")
123 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
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 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # UV
98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | #uv.lock
102 |
103 | # poetry
104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | # This is especially recommended for binary packages to ensure reproducibility, and is more
106 | # commonly ignored for libraries.
107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 |
110 | # pdm
111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | # in version control.
115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 |
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 |
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 |
127 | # SageMath parsed files
128 | *.sage.py
129 |
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 |
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 |
143 | # Rope project settings
144 | .ropeproject
145 |
146 | # mkdocs documentation
147 | /site
148 |
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 |
154 | # Pyre type checker
155 | .pyre/
156 |
157 | # pytype static type analyzer
158 | .pytype/
159 |
160 | # Cython debug symbols
161 | cython_debug/
162 |
163 | # PyCharm
164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | # and can be added to the global gitignore or merged into this file. For a more nuclear
167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 |
170 | # Abstra
171 | # Abstra is an AI-powered process automation framework.
172 | # Ignore directories containing user credentials, local state, and settings.
173 | # Learn more at https://abstra.io/docs
174 | .abstra/
175 |
176 | # Visual Studio Code
177 | # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
178 | # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179 | # and can be added to the global gitignore or merged into this file. However, if you prefer,
180 | # you could uncomment the following to ignore the enitre vscode folder
181 | # .vscode/
182 |
183 | # Ruff stuff:
184 | .ruff_cache/
185 |
186 | # PyPI configuration file
187 | .pypirc
188 |
189 | # Cursor
190 | # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
191 | # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
192 | # refer to https://docs.cursor.com/context/ignore-files
193 | .cursorignore
194 | .cursorindexingignore
195 | cache/
196 | .idea/
--------------------------------------------------------------------------------
/qgis-plugin-amap/transform.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ##########################################################################################
3 | """
4 | /***************************************************************************
5 | OffsetWGS84Core
6 | A QGIS plugin
7 | Class with methods for geometry and attributes processing
8 | -------------------
9 | begin : 2016-10-11
10 | git sha : $Format:%H$
11 | copyright : (C) 2017 by sshuair
12 | email : sshuair@gmail.com
13 | ***************************************************************************/
14 |
15 | /***************************************************************************
16 | * *
17 | * This program is free software; you can redistribute it and/or modify *
18 | * it under the terms of the GNU General Public License as published by *
19 | * the Free Software Foundation; either version 2 of the License, or *
20 | * (at your option) any later version. *
21 | * *
22 | ***************************************************************************/
23 | """
24 | from __future__ import print_function
25 |
26 | import math
27 |
28 | ##########################################################################################
29 | from builtins import zip
30 | from math import atan2, cos, fabs
31 | from math import pi as PI
32 | from math import sin, sqrt
33 |
34 | # from numba import jit
35 |
36 |
37 | # =================================================sshuair=============================================================
38 | # define ellipsoid
39 | a = 6378245.0
40 | f = 1 / 298.3
41 | b = a * (1 - f)
42 | ee = 1 - (b * b) / (a * a)
43 |
44 |
45 | # check if the point in china
46 | def outOfChina(lng, lat):
47 | return not (72.004 <= lng <= 137.8347 and 0.8293 <= lat <= 55.8271)
48 |
49 |
50 | # @jit
51 | def geohey_transformLat(x, y):
52 | ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x))
53 | ret = ret + (20.0 * sin(6.0 * x * PI) + 20.0 * sin(2.0 * x * PI)) * 2.0 / 3.0
54 | ret = ret + (20.0 * sin(y * PI) + 40.0 * sin(y / 3.0 * PI)) * 2.0 / 3.0
55 | ret = ret + (160.0 * sin(y / 12.0 * PI) + 320.0 * sin(y * PI / 30.0)) * 2.0 / 3.0
56 | return ret
57 |
58 |
59 | # @jit
60 | def geohey_transformLon(x, y):
61 | ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x))
62 | ret = ret + (20.0 * sin(6.0 * x * PI) + 20.0 * sin(2.0 * x * PI)) * 2.0 / 3.0
63 | ret = ret + (20.0 * sin(x * PI) + 40.0 * sin(x / 3.0 * PI)) * 2.0 / 3.0
64 | ret = ret + (150.0 * sin(x / 12.0 * PI) + 300.0 * sin(x * PI / 30.0)) * 2.0 / 3.0
65 | return ret
66 |
67 |
68 | # @jit
69 | def wgs2gcj(wgsLon, wgsLat):
70 | if outOfChina(wgsLon, wgsLat):
71 | return wgsLon, wgsLat
72 | dLat = geohey_transformLat(wgsLon - 105.0, wgsLat - 35.0)
73 | dLon = geohey_transformLon(wgsLon - 105.0, wgsLat - 35.0)
74 | radLat = wgsLat / 180.0 * PI
75 | magic = math.sin(radLat)
76 | magic = 1 - ee * magic * magic
77 | sqrtMagic = sqrt(magic)
78 | dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI)
79 | dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * PI)
80 | gcjLat = wgsLat + dLat
81 | gcjLon = wgsLon + dLon
82 | return (gcjLon, gcjLat)
83 |
84 |
85 | def gcj2wgs(gcjLon, gcjLat):
86 | g0 = (gcjLon, gcjLat)
87 | w0 = g0
88 | g1 = wgs2gcj(w0[0], w0[1])
89 | # w1 = w0 - (g1 - g0)
90 | w1 = tuple([x[0] - (x[1] - x[2]) for x in zip(w0, g1, g0)])
91 | # delta = w1 - w0
92 | delta = tuple([x[0] - x[1] for x in zip(w1, w0)])
93 | while abs(delta[0]) >= 1e-6 or abs(delta[1]) >= 1e-6:
94 | w0 = w1
95 | g1 = wgs2gcj(w0[0], w0[1])
96 | # w1 = w0 - (g1 - g0)
97 | w1 = tuple([x[0] - (x[1] - x[2]) for x in zip(w0, g1, g0)])
98 | # delta = w1 - w0
99 | delta = tuple([x[0] - x[1] for x in zip(w1, w0)])
100 | return w1
101 |
102 |
103 | def gcj2bd(gcjLon, gcjLat):
104 | z = sqrt(gcjLon * gcjLon + gcjLat * gcjLat) + 0.00002 * sin(
105 | gcjLat * PI * 3000.0 / 180.0
106 | )
107 | theta = atan2(gcjLat, gcjLon) + 0.000003 * cos(gcjLon * PI * 3000.0 / 180.0)
108 | bdLon = z * cos(theta) + 0.0065
109 | bdLat = z * sin(theta) + 0.006
110 | return (bdLon, bdLat)
111 |
112 |
113 | def bd2gcj(bdLon, bdLat):
114 | x = bdLon - 0.0065
115 | y = bdLat - 0.006
116 | z = sqrt(x * x + y * y) - 0.00002 * sin(y * PI * 3000.0 / 180.0)
117 | theta = atan2(y, x) - 0.000003 * cos(x * PI * 3000.0 / 180.0)
118 | gcjLon = z * cos(theta)
119 | gcjLat = z * sin(theta)
120 | return (gcjLon, gcjLat)
121 |
122 |
123 | def wgs2bd(wgsLon, wgsLat):
124 | gcj = wgs2gcj(wgsLon, wgsLat)
125 | return gcj2bd(gcj[0], gcj[1])
126 |
127 |
128 | def bd2wgs(bdLon, bdLat):
129 | gcj = bd2gcj(bdLon, bdLat)
130 | return gcj2wgs(gcj[0], gcj[1])
131 |
132 |
133 | if __name__ == "__main__":
134 | # wgs2gcj
135 | # coord = (112, 40)
136 | # trans = WGS2GCJ()
137 | print(wgs2gcj(117.136230, 34.252676))
138 | print(gcj2wgs(112.00678230985764, 40.00112245823686))
139 |
140 | # gcj2wgs
141 |
--------------------------------------------------------------------------------
/qgis-plugin-amap/tile.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from io import BytesIO
3 | from math import atan, cos, log, pi, sinh, tan
4 | from typing import Optional
5 |
6 | import httpx
7 | import requests
8 | from PIL import Image
9 |
10 | # 来自 Geohey 的转换算法 https://github.com/GeoHey-Team/qgis-geohey-toolbox
11 | from .transform import wgs2gcj
12 | from .utils import CACHE_DIR
13 |
14 | AMAP_HOST = "https://wprd02.is.autonavi.com"
15 |
16 | amap_url = {
17 | "vec": f"{AMAP_HOST}/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={{x}}&y={{y}}&z={{z}}",
18 | "vec_note": f"{AMAP_HOST}/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={{x}}&y={{y}}&z={{z}}",
19 | }
20 |
21 | amap_name = {"vec": "矢量地图", "vec_note": "矢量注记"}
22 |
23 | # 全局异步HTTP客户端
24 | _async_client: Optional[httpx.AsyncClient] = None
25 |
26 |
27 | def get_async_client() -> httpx.AsyncClient:
28 | """获取或创建异步HTTP客户端"""
29 | global _async_client
30 | if _async_client is None:
31 | _async_client = httpx.AsyncClient(timeout=30.0)
32 | return _async_client
33 |
34 |
35 | async def close_async_client():
36 | """关闭异步HTTP客户端"""
37 | global _async_client
38 | if _async_client is not None:
39 | await _async_client.aclose()
40 | _async_client = None
41 |
42 |
43 | def xyz_to_lonlat(x: int, y: int, z: int) -> tuple:
44 | """
45 | 将XYZ瓦片坐标转换为经纬度(左上角点)。
46 |
47 | Args:
48 | x (int): Tile X coordinate.
49 | y (int): Tile Y coordinate.
50 | z (int): Zoom level.
51 |
52 | Returns:
53 | tuple: Longitude and latitude in degrees.
54 | """
55 | n = 2.0**z
56 | lon_deg = x / n * 360.0 - 180.0
57 | lat_rad = atan(sinh(pi * (1 - 2 * y / n)))
58 | lat_deg = lat_rad * 180.0 / pi
59 | return lon_deg, lat_deg
60 |
61 |
62 | def lonlat_to_xyz(lon: float, lat: float, z: int) -> tuple:
63 | """
64 | Convert longitude and latitude to XYZ tile coordinates.
65 |
66 | Args:
67 | lon (float): Longitude in degrees.
68 | lat (float): Latitude in degrees.
69 | z (int): Zoom level.
70 |
71 | Returns:
72 | tuple: Tile X and Y coordinates.
73 | """
74 | n = 2.0**z
75 | x = (lon + 180.0) / 360.0 * n
76 | lat_rad = lat * pi / 180.0
77 | t = log(tan(lat_rad) + 1 / cos(lat_rad))
78 | y = (1 - t / pi) * n / 2
79 | return int(x), int(y)
80 |
81 |
82 | def xyz_to_bbox(x, y, z):
83 | """
84 | Convert XYZ tile coordinates to bounding box coordinates.
85 |
86 | Args:
87 | x (int): Tile X coordinate.
88 | y (int): Tile Y coordinate.
89 | z (int): Zoom level.
90 |
91 | Returns:
92 | tuple: Bounding box in the format (min_lon, min_lat, max_lon, max_lat).
93 | """
94 | left_upper_lon, left_upper_lat = xyz_to_lonlat(x, y, z)
95 | right_lower_lon, right_lower_lat = xyz_to_lonlat(x + 1, y + 1, z)
96 |
97 | return (left_upper_lon, left_upper_lat), (right_lower_lon, right_lower_lat)
98 |
99 |
100 | def wgsbbox_to_gcjbbox(wgs_bbox):
101 | """
102 | Convert WGS84 bounding box to GCJ02 bounding box.
103 |
104 | Args:
105 | wgs_bbox (tuple): Bounding box in the format (min_lon, min_lat, max_lon, max_lat).
106 |
107 | Returns:
108 | tuple: GCJ02 bounding box in the same format.
109 | """
110 | left_upper, right_lower = wgs_bbox
111 | gcj_left_upper = wgs2gcj(left_upper[0], left_upper[1])
112 | gcj_right_lower = wgs2gcj(right_lower[0], right_lower[1])
113 | return gcj_left_upper, gcj_right_lower
114 |
115 |
116 | async def get_tile_gcj_async(x: int, y: int, z: int, mapid: str) -> Image:
117 | """
118 | 异步获取指定瓦片的图像,如果缓存中存在则直接返回,否则从服务器下载。
119 | 这里下载的是高德地图的GCJ02坐标系瓦片。
120 | 该函数会检查缓存目录,如果瓦片已经存在,则直接从缓存中读取并返回。
121 |
122 | Args:
123 | x (int): Tile X coordinate.
124 | y (int): Tile Y coordinate.
125 | z (int): Zoom level.
126 | mapid (str): Map Id
127 |
128 | Returns:
129 | Image: Tile image.
130 | """
131 | url = amap_url[mapid]
132 | url = url.format(x=x, y=y, z=z)
133 | map_name = amap_name[mapid]
134 |
135 | # 如果缓存中存在,直接返回缓存的瓦片
136 | tile_file_path = CACHE_DIR.joinpath(f"./{map_name}/gcj/{z}/{x}/{y}.png")
137 | if tile_file_path.exists():
138 | return Image.open(tile_file_path)
139 |
140 | # 使用异步HTTP客户端获取瓦片
141 | client = get_async_client()
142 | async with client.stream("GET", url) as response:
143 | if response.status_code != 200:
144 | raise Exception(f"Failed to fetch tile from {url}")
145 |
146 | # 读取响应内容
147 | content = await response.aread()
148 |
149 | # 保存瓦片图像到缓存
150 | file_path = CACHE_DIR.joinpath(f"./{map_name}/gcj/{z}/{x}/{y}.png")
151 | file_path.parent.mkdir(parents=True, exist_ok=True)
152 | file_path.write_bytes(content)
153 |
154 | return Image.open(BytesIO(content))
155 |
156 |
157 | def get_tile_gcj(x: int, y: int, z: int, mapid: str) -> Image:
158 | """
159 | 获取指定瓦片的图像,如果缓存中存在则直接返回,否则从服务器下载。
160 | 这里下载的是高德地图的GCJ02坐标系瓦片。
161 | 该函数会检查缓存目录,如果瓦片已经存在,则直接从缓存中读取并返回。
162 |
163 | Args:
164 | x (int): Tile X coordinate.
165 | y (int): Tile Y coordinate.
166 | z (int): Zoom level.
167 | mapid (str): Map Id
168 |
169 | Returns:
170 | Image: Tile image.
171 | """
172 | url = amap_url[mapid]
173 | url = url.format(x=x, y=y, z=z)
174 | map_name = amap_name[mapid]
175 | # if in cache, return the cached tile
176 | tile_file_path = CACHE_DIR.joinpath(f"./{map_name}/gcj/{z}/{x}/{y}.png")
177 | if tile_file_path.exists():
178 | return Image.open(tile_file_path)
179 |
180 | r = requests.get(url)
181 | if r.status_code != 200:
182 | raise Exception(f"Failed to fetch tile from {url}")
183 | # save the tile image to cache
184 | file_path = CACHE_DIR.joinpath(f"./{map_name}/gcj/{z}/{x}/{y}.png")
185 | file_path.parent.mkdir(parents=True, exist_ok=True)
186 | file_path.write_bytes(r.content)
187 | return Image.open(BytesIO(r.content))
188 |
189 |
190 | async def get_tile_async(x: int, y: int, z: int, mapid: str) -> Image:
191 | """
192 | 异步获取瓦片,支持高精度坐标转换
193 | """
194 | map_name = amap_name[mapid]
195 | # 小于 9 级时 没有明显的偏移 直接使用 GCJ02 坐标系的瓦片
196 | if z <= 9:
197 | return await get_tile_gcj_async(x, y, z, mapid)
198 |
199 | # 如果缓存中存在,直接返回缓存的瓦片
200 | tile_file_path = CACHE_DIR.joinpath(f"./{map_name}/wgs/{z}/{x}/{y}.png")
201 | if tile_file_path.exists():
202 | return Image.open(tile_file_path)
203 |
204 | wgs_bbox = xyz_to_bbox(x, y, z)
205 | gcj_bbox = wgsbbox_to_gcjbbox(wgs_bbox)
206 | left_upper, right_lower = gcj_bbox
207 |
208 | # 计算左上角和右下角的瓦片行列号
209 | x_min, y_min = lonlat_to_xyz(left_upper[0], left_upper[1], z) # 左上角
210 | x_max, y_max = lonlat_to_xyz(right_lower[0], right_lower[1], z) # 右下角
211 |
212 | # 创建任务列表,异步获取所有需要的瓦片
213 | tasks = []
214 | for ax in range(x_min, x_max + 1):
215 | for ay in range(y_min, y_max + 1):
216 | tasks.append(get_tile_gcj_async(ax, ay, z, mapid))
217 |
218 | # 并发执行所有瓦片下载任务
219 | tiles = await asyncio.gather(*tasks)
220 |
221 | # 拼合瓦片
222 | composite = Image.new(
223 | "RGBA", ((x_max - x_min + 1) * 256, (y_max - y_min + 1) * 256)
224 | )
225 |
226 | tile_index = 0
227 | for i, ax in enumerate(range(x_min, x_max + 1)):
228 | for j, ay in enumerate(range(y_min, y_max + 1)):
229 | tile = tiles[tile_index]
230 | if tile:
231 | composite.paste(tile, (i * 256, j * 256))
232 | tile_index += 1
233 |
234 | # 计算拼合后的瓦片范围
235 | megred_bbox = xyz_to_bbox(x_min, y_min, z)[0], xyz_to_bbox(x_max, y_max, z)[1]
236 |
237 | x_range = megred_bbox[1][0] - megred_bbox[0][0]
238 | y_range = megred_bbox[0][1] - megred_bbox[1][1]
239 |
240 | left_percent = (gcj_bbox[0][0] - megred_bbox[0][0]) / x_range
241 | top_percent = (megred_bbox[0][1] - gcj_bbox[0][1]) / y_range
242 | img_width, img_height = composite.size
243 | # 裁剪选区(left, top, right, bottom)
244 | crop_bbox = (
245 | int(left_percent * img_width),
246 | int(top_percent * img_height),
247 | int(left_percent * img_width) + 256,
248 | int(top_percent * img_height) + 256,
249 | )
250 |
251 | # 从拼合的瓦片中裁剪出对应的区域
252 | croped_image = composite.crop(crop_bbox)
253 | wgs_tile_path = CACHE_DIR.joinpath(f"./{map_name}/wgs/{z}/{x}/{y}.png")
254 | wgs_tile_path.parent.mkdir(parents=True, exist_ok=True)
255 | croped_image.save(wgs_tile_path)
256 | return croped_image
257 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 1
3 | requires-python = ">=3.13"
4 |
5 | [[package]]
6 | name = "amap-rectify"
7 | version = "0.1.0"
8 | source = { virtual = "." }
9 | dependencies = [
10 | { name = "fastapi" },
11 | { name = "httpx" },
12 | { name = "pillow" },
13 | { name = "requests" },
14 | { name = "uvicorn" },
15 | ]
16 |
17 | [package.metadata]
18 | requires-dist = [
19 | { name = "fastapi", specifier = ">=0.115.13" },
20 | { name = "httpx", specifier = ">=0.28.1" },
21 | { name = "pillow", specifier = ">=11.2.1" },
22 | { name = "requests", specifier = ">=2.32.4" },
23 | { name = "uvicorn", specifier = ">=0.34.3" },
24 | ]
25 |
26 | [[package]]
27 | name = "annotated-types"
28 | version = "0.7.0"
29 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
30 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
31 | wheels = [
32 | { url = "https://mirrors.aliyun.com/pypi/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
33 | ]
34 |
35 | [[package]]
36 | name = "anyio"
37 | version = "4.9.0"
38 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
39 | dependencies = [
40 | { name = "idna" },
41 | { name = "sniffio" },
42 | ]
43 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028" }
44 | wheels = [
45 | { url = "https://mirrors.aliyun.com/pypi/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c" },
46 | ]
47 |
48 | [[package]]
49 | name = "certifi"
50 | version = "2025.6.15"
51 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
52 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" }
53 | wheels = [
54 | { url = "https://mirrors.aliyun.com/pypi/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057" },
55 | ]
56 |
57 | [[package]]
58 | name = "charset-normalizer"
59 | version = "3.4.2"
60 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
61 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63" }
62 | wheels = [
63 | { url = "https://mirrors.aliyun.com/pypi/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0" },
64 | { url = "https://mirrors.aliyun.com/pypi/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf" },
65 | { url = "https://mirrors.aliyun.com/pypi/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e" },
66 | { url = "https://mirrors.aliyun.com/pypi/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1" },
67 | { url = "https://mirrors.aliyun.com/pypi/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c" },
68 | { url = "https://mirrors.aliyun.com/pypi/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691" },
69 | { url = "https://mirrors.aliyun.com/pypi/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0" },
70 | { url = "https://mirrors.aliyun.com/pypi/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b" },
71 | { url = "https://mirrors.aliyun.com/pypi/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff" },
72 | { url = "https://mirrors.aliyun.com/pypi/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b" },
73 | { url = "https://mirrors.aliyun.com/pypi/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148" },
74 | { url = "https://mirrors.aliyun.com/pypi/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7" },
75 | { url = "https://mirrors.aliyun.com/pypi/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980" },
76 | { url = "https://mirrors.aliyun.com/pypi/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0" },
77 | ]
78 |
79 | [[package]]
80 | name = "click"
81 | version = "8.2.1"
82 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
83 | dependencies = [
84 | { name = "colorama", marker = "sys_platform == 'win32'" },
85 | ]
86 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202" }
87 | wheels = [
88 | { url = "https://mirrors.aliyun.com/pypi/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" },
89 | ]
90 |
91 | [[package]]
92 | name = "colorama"
93 | version = "0.4.6"
94 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
95 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
96 | wheels = [
97 | { url = "https://mirrors.aliyun.com/pypi/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
98 | ]
99 |
100 | [[package]]
101 | name = "fastapi"
102 | version = "0.115.13"
103 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
104 | dependencies = [
105 | { name = "pydantic" },
106 | { name = "starlette" },
107 | { name = "typing-extensions" },
108 | ]
109 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307" }
110 | wheels = [
111 | { url = "https://mirrors.aliyun.com/pypi/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865" },
112 | ]
113 |
114 | [[package]]
115 | name = "h11"
116 | version = "0.16.0"
117 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
118 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
119 | wheels = [
120 | { url = "https://mirrors.aliyun.com/pypi/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
121 | ]
122 |
123 | [[package]]
124 | name = "httpcore"
125 | version = "1.0.9"
126 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
127 | dependencies = [
128 | { name = "certifi" },
129 | { name = "h11" },
130 | ]
131 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
132 | wheels = [
133 | { url = "https://mirrors.aliyun.com/pypi/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
134 | ]
135 |
136 | [[package]]
137 | name = "httpx"
138 | version = "0.28.1"
139 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
140 | dependencies = [
141 | { name = "anyio" },
142 | { name = "certifi" },
143 | { name = "httpcore" },
144 | { name = "idna" },
145 | ]
146 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
147 | wheels = [
148 | { url = "https://mirrors.aliyun.com/pypi/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
149 | ]
150 |
151 | [[package]]
152 | name = "idna"
153 | version = "3.10"
154 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
155 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" }
156 | wheels = [
157 | { url = "https://mirrors.aliyun.com/pypi/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" },
158 | ]
159 |
160 | [[package]]
161 | name = "pillow"
162 | version = "11.2.1"
163 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
164 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6" }
165 | wheels = [
166 | { url = "https://mirrors.aliyun.com/pypi/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28" },
167 | { url = "https://mirrors.aliyun.com/pypi/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830" },
168 | { url = "https://mirrors.aliyun.com/pypi/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0" },
169 | { url = "https://mirrors.aliyun.com/pypi/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1" },
170 | { url = "https://mirrors.aliyun.com/pypi/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f" },
171 | { url = "https://mirrors.aliyun.com/pypi/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155" },
172 | { url = "https://mirrors.aliyun.com/pypi/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14" },
173 | { url = "https://mirrors.aliyun.com/pypi/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b" },
174 | { url = "https://mirrors.aliyun.com/pypi/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2" },
175 | { url = "https://mirrors.aliyun.com/pypi/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691" },
176 | { url = "https://mirrors.aliyun.com/pypi/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c" },
177 | { url = "https://mirrors.aliyun.com/pypi/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22" },
178 | { url = "https://mirrors.aliyun.com/pypi/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7" },
179 | { url = "https://mirrors.aliyun.com/pypi/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16" },
180 | { url = "https://mirrors.aliyun.com/pypi/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b" },
181 | { url = "https://mirrors.aliyun.com/pypi/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406" },
182 | { url = "https://mirrors.aliyun.com/pypi/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91" },
183 | { url = "https://mirrors.aliyun.com/pypi/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751" },
184 | { url = "https://mirrors.aliyun.com/pypi/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9" },
185 | { url = "https://mirrors.aliyun.com/pypi/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd" },
186 | { url = "https://mirrors.aliyun.com/pypi/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e" },
187 | { url = "https://mirrors.aliyun.com/pypi/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681" },
188 | ]
189 |
190 | [[package]]
191 | name = "pydantic"
192 | version = "2.11.7"
193 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
194 | dependencies = [
195 | { name = "annotated-types" },
196 | { name = "pydantic-core" },
197 | { name = "typing-extensions" },
198 | { name = "typing-inspection" },
199 | ]
200 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db" }
201 | wheels = [
202 | { url = "https://mirrors.aliyun.com/pypi/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b" },
203 | ]
204 |
205 | [[package]]
206 | name = "pydantic-core"
207 | version = "2.33.2"
208 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
209 | dependencies = [
210 | { name = "typing-extensions" },
211 | ]
212 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc" }
213 | wheels = [
214 | { url = "https://mirrors.aliyun.com/pypi/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f" },
215 | { url = "https://mirrors.aliyun.com/pypi/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6" },
216 | { url = "https://mirrors.aliyun.com/pypi/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef" },
217 | { url = "https://mirrors.aliyun.com/pypi/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a" },
218 | { url = "https://mirrors.aliyun.com/pypi/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916" },
219 | { url = "https://mirrors.aliyun.com/pypi/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a" },
220 | { url = "https://mirrors.aliyun.com/pypi/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d" },
221 | { url = "https://mirrors.aliyun.com/pypi/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56" },
222 | { url = "https://mirrors.aliyun.com/pypi/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5" },
223 | { url = "https://mirrors.aliyun.com/pypi/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e" },
224 | { url = "https://mirrors.aliyun.com/pypi/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162" },
225 | { url = "https://mirrors.aliyun.com/pypi/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849" },
226 | { url = "https://mirrors.aliyun.com/pypi/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9" },
227 | { url = "https://mirrors.aliyun.com/pypi/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9" },
228 | { url = "https://mirrors.aliyun.com/pypi/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac" },
229 | { url = "https://mirrors.aliyun.com/pypi/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5" },
230 | { url = "https://mirrors.aliyun.com/pypi/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9" },
231 | ]
232 |
233 | [[package]]
234 | name = "requests"
235 | version = "2.32.4"
236 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
237 | dependencies = [
238 | { name = "certifi" },
239 | { name = "charset-normalizer" },
240 | { name = "idna" },
241 | { name = "urllib3" },
242 | ]
243 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" }
244 | wheels = [
245 | { url = "https://mirrors.aliyun.com/pypi/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c" },
246 | ]
247 |
248 | [[package]]
249 | name = "sniffio"
250 | version = "1.3.1"
251 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
252 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
253 | wheels = [
254 | { url = "https://mirrors.aliyun.com/pypi/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
255 | ]
256 |
257 | [[package]]
258 | name = "starlette"
259 | version = "0.46.2"
260 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
261 | dependencies = [
262 | { name = "anyio" },
263 | ]
264 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5" }
265 | wheels = [
266 | { url = "https://mirrors.aliyun.com/pypi/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35" },
267 | ]
268 |
269 | [[package]]
270 | name = "typing-extensions"
271 | version = "4.14.0"
272 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
273 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4" }
274 | wheels = [
275 | { url = "https://mirrors.aliyun.com/pypi/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" },
276 | ]
277 |
278 | [[package]]
279 | name = "typing-inspection"
280 | version = "0.4.1"
281 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
282 | dependencies = [
283 | { name = "typing-extensions" },
284 | ]
285 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28" }
286 | wheels = [
287 | { url = "https://mirrors.aliyun.com/pypi/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51" },
288 | ]
289 |
290 | [[package]]
291 | name = "urllib3"
292 | version = "2.5.0"
293 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
294 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760" }
295 | wheels = [
296 | { url = "https://mirrors.aliyun.com/pypi/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" },
297 | ]
298 |
299 | [[package]]
300 | name = "uvicorn"
301 | version = "0.34.3"
302 | source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
303 | dependencies = [
304 | { name = "click" },
305 | { name = "h11" },
306 | ]
307 | sdist = { url = "https://mirrors.aliyun.com/pypi/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a" }
308 | wheels = [
309 | { url = "https://mirrors.aliyun.com/pypi/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885" },
310 | ]
311 |
--------------------------------------------------------------------------------