gcj-rectify 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,207 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
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
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,21 @@
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.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcj-rectify
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: fastapi>=0.116.1
8
+ Requires-Dist: httpx>=0.28.1
9
+ Requires-Dist: pillow>=11.3.0
10
+ Requires-Dist: uvicorn>=0.35.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # gcj-rectify
14
+
15
+
16
+ ```bash
17
+ uv run uvicorn gcj_rectify_server.main:app --reload
18
+ ```
@@ -0,0 +1,6 @@
1
+ # gcj-rectify
2
+
3
+
4
+ ```bash
5
+ uv run uvicorn gcj_rectify_server.main:app --reload
6
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,70 @@
1
+ from typing import Optional
2
+
3
+ from httpx import AsyncClient
4
+
5
+ # 全局异步HTTP客户端
6
+ _async_client: Optional[AsyncClient] = None
7
+
8
+
9
+ def get_async_client() -> AsyncClient:
10
+ """获取或创建异步HTTP客户端"""
11
+ global _async_client
12
+ if _async_client is None:
13
+ _async_client = AsyncClient(timeout=30.0)
14
+ return _async_client
15
+
16
+
17
+ def close_async_client():
18
+ """关闭异步HTTP客户端(同步版本)"""
19
+ global _async_client
20
+ if _async_client is not None:
21
+ # 强制设置为 None,让下次调用时重新创建
22
+ _async_client = None
23
+
24
+
25
+ async def close_async_client_async():
26
+ """关闭异步HTTP客户端(异步版本)"""
27
+ global _async_client
28
+ if _async_client is not None:
29
+ await _async_client.aclose()
30
+ _async_client = None
31
+
32
+
33
+ def reset_async_client():
34
+ """重置异步HTTP客户端,强制重新创建"""
35
+ global _async_client
36
+ if _async_client is not None:
37
+ _async_client = None
38
+
39
+
40
+ async def fetch_tile(url: str) -> bytes:
41
+ """
42
+ Fetch a tile image from the specified URL using an asynchronous HTTP client.
43
+
44
+ Args:
45
+ url (str): The URL to fetch the tile from.
46
+
47
+ Returns:
48
+ bytes: The content of the url and its content type.
49
+ Raises:
50
+ Exception: If the request fails or the response status is not 200.
51
+ """
52
+ try:
53
+ client = get_async_client()
54
+ async with client.stream("GET", url) as response:
55
+ if response.status_code != 200:
56
+ raise Exception(f"Failed to fetch tile from {url}")
57
+ content = await response.aread()
58
+ return content
59
+ except Exception as e:
60
+ # 如果出现事件循环相关错误,重置客户端并重试一次
61
+ if "Event loop is closed" in str(e) or "RuntimeError" in str(e):
62
+ reset_async_client()
63
+ client = get_async_client()
64
+ async with client.stream("GET", url) as response:
65
+ if response.status_code != 200:
66
+ raise Exception(f"Failed to fetch tile from {url}")
67
+ content = await response.aread()
68
+ return content
69
+ else:
70
+ raise e
@@ -0,0 +1,89 @@
1
+ from contextlib import asynccontextmanager
2
+ from pathlib import Path
3
+ import argparse
4
+
5
+ from fastapi import FastAPI, Response, Request
6
+ import uvicorn
7
+
8
+ from .fetch import reset_async_client
9
+ from .rectify import get_tile_gcj_cached, get_tile_wgs_cached
10
+ from .utils import get_cache_dir
11
+
12
+
13
+ @asynccontextmanager
14
+ async def lifespan(app: FastAPI):
15
+ """应用生命周期管理"""
16
+ # 启动时执行
17
+ # 在启动服务器前重置异步客户端,确保使用新的事件循环
18
+ reset_async_client()
19
+ yield
20
+ # 关闭时执行
21
+ from .fetch import close_async_client_async
22
+
23
+ await close_async_client_async()
24
+
25
+
26
+ app = FastAPI(lifespan=lifespan)
27
+
28
+
29
+ app.state.cache_dir = Path(get_cache_dir())
30
+
31
+
32
+ @app.get("/")
33
+ def index():
34
+ return {"message": "Server is Running"}
35
+
36
+
37
+ @app.get("/config")
38
+ def get_config(request: Request):
39
+ return {"cache_dir": str(request.app.state.cache_dir)}
40
+
41
+
42
+ @app.get("/tiles/{map_id}/{z}/{x}/{y}")
43
+ async def tile(map_id: str, z: int, x: int, y: int, request: Request):
44
+ """
45
+ Get a tile image for the specified map ID, zoom level, and row/column numbers.
46
+
47
+ Args:
48
+ map_id (str): The ID of the map.
49
+ z (int): Zoom level.
50
+ x (int): Tile column number.
51
+ y (int): Tile row number.
52
+ request: Fastapi Request
53
+ """
54
+ state_cache_dir = request.app.state.cache_dir
55
+ try:
56
+ if z <= 9:
57
+ # For zoom levels 9 and below, use GCJ02 tiles directly
58
+ img_bytes = await get_tile_gcj_cached(x, y, z, map_id, state_cache_dir)
59
+ else:
60
+ img_bytes = await get_tile_wgs_cached(x, y, z, map_id, state_cache_dir)
61
+
62
+ if img_bytes is None:
63
+ # 如果获取瓦片失败,返回空图片或错误响应
64
+ return Response(status_code=500, content="Failed to fetch tile")
65
+
66
+ return Response(content=img_bytes, media_type="image/png")
67
+ except Exception as e:
68
+ print(f"获取瓦片时发生错误: {e}")
69
+ return Response(status_code=500, content="Internal server error")
70
+
71
+
72
+ def run(host: str = "0.0.0.0", port: int = 8000):
73
+ """运行 GCJ Rectify 服务器
74
+
75
+ Args:
76
+ host: 服务器主机地址,默认为0.0.0.0
77
+ port: 服务器端口,默认为8000
78
+ """
79
+ # 解析命令行参数
80
+ parser = argparse.ArgumentParser(description="GCJ Rectify 服务器")
81
+ parser.add_argument("--host", default=host, help="服务器主机地址 (默认: 0.0.0.0)")
82
+ parser.add_argument(
83
+ "--port", type=int, default=port, help="服务器端口 (默认: 8000)"
84
+ )
85
+
86
+ args = parser.parse_args()
87
+
88
+ print(f"启动服务器: http://{args.host}:{args.port}")
89
+ uvicorn.run(app, host=args.host, port=args.port)
@@ -0,0 +1,14 @@
1
+ {
2
+ "amap-vec": {
3
+ "name": "高德地图 - 矢量地图",
4
+ "url": "https://wprd02.is.autonavi.com//appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}"
5
+ },
6
+ "amap-sat": {
7
+ "name": "高德地图 - 卫星影像",
8
+ "url": "https://wprd02.is.autonavi.com//appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}"
9
+ },
10
+ "tencent-vec": {
11
+ "name": "腾讯地图 - 矢量地图",
12
+ "url": "http://rt0.map.gtimg.com/realtimerender?z={z}&x={x}&y={-y}&type=vector&style=0"
13
+ }
14
+ }
@@ -0,0 +1,200 @@
1
+ import asyncio
2
+ from io import BytesIO
3
+ from pathlib import Path
4
+
5
+ from PIL import Image
6
+
7
+ from .fetch import fetch_tile
8
+ from .utils import (
9
+ xyz_to_bbox,
10
+ wgsbbox_to_gcjbbox,
11
+ lonlat_to_xyz,
12
+ image_to_bytes,
13
+ bytes_to_image,
14
+ get_maps,
15
+ )
16
+
17
+ map_data = get_maps()
18
+
19
+
20
+ async def get_tile_gcj(x: int, y: int, z: int, mapid: str) -> bytes:
21
+ """
22
+ 获取指定瓦片的图像,这里下载的是原始瓦片(GCJ02 坐标系)。
23
+ Args:
24
+ x (int): Tile X coordinate.
25
+ y (int): Tile Y coordinate.
26
+ z (int): Zoom level.
27
+ mapid (str): Map Id
28
+ Returns:
29
+ bytes: Tile image bytes.
30
+ """
31
+ url = map_data[mapid]["url"]
32
+ if "-y" in url:
33
+ # 如果 URL 中包含 -y,认为是TMS格式,需要调整 Y 值
34
+ url = url.replace("-y", "y")
35
+ url = url.format(x=x, y=(2**z - 1 - y), z=z)
36
+ else:
37
+ url = url.format(x=x, y=y, z=z)
38
+
39
+ # 使用异步HTTP客户端获取瓦片
40
+ content = await fetch_tile(url)
41
+
42
+ return content
43
+
44
+
45
+ async def get_tile_gcj_cached(
46
+ x: int, y: int, z: int, mapid: str, cache_dir: Path = Path.cwd().joinpath("cache")
47
+ ) -> Image:
48
+ """
49
+ 获取指定瓦片的图像,使用缓存。
50
+ Args:
51
+ x (int): Tile X coordinate.
52
+ y (int): Tile Y coordinate.
53
+ z (int): Zoom level.
54
+ mapid (str): Map Id
55
+ cache_dir (str): 缓存目录
56
+ Returns:
57
+ Image: Tile image.
58
+ """
59
+ tile_file_path = cache_dir.joinpath(f"{mapid}/GCJ/{z}/{x}/{y}.png")
60
+ if tile_file_path.exists():
61
+ # print(f"从缓存中获取了瓦片: {tile_file_path}")
62
+ image = Image.open(tile_file_path)
63
+ return image_to_bytes(image)
64
+ # 如果缓存不存在,则下载瓦片并保存到缓存
65
+ tile_file_path.parent.mkdir(parents=True, exist_ok=True)
66
+ try:
67
+ image_bytes = await get_tile_gcj(x, y, z, mapid)
68
+ image = bytes_to_image(image_bytes)
69
+ image.save(tile_file_path, "PNG")
70
+ except Exception as e:
71
+ print(f"获取瓦片失败: {e}")
72
+ # 如果是事件循环错误,尝试重置客户端并重试
73
+ if "Event loop is closed" in str(e) or "RuntimeError" in str(e):
74
+ from .fetch import reset_async_client
75
+
76
+ reset_async_client()
77
+ try:
78
+ image_bytes = await get_tile_gcj(x, y, z, mapid)
79
+ image = bytes_to_image(image_bytes)
80
+ image.save(tile_file_path, "PNG")
81
+ except Exception as retry_e:
82
+ print(f"重试获取瓦片失败: {retry_e}")
83
+ return None
84
+ else:
85
+ return None
86
+
87
+ return image_bytes
88
+
89
+
90
+ async def get_tile_wgs(x: int, y: int, z: int, mapid: str) -> Image:
91
+ """
92
+ 获取瓦片(调整为 WGS84 坐标系)
93
+ """
94
+ if z <= 9:
95
+ print("Z 小于 9 时 没有明显的偏移 直接使用 GCJ02 坐标系的瓦片")
96
+ return
97
+
98
+ wgs_bbox = xyz_to_bbox(x, y, z)
99
+ gcj_bbox = wgsbbox_to_gcjbbox(wgs_bbox)
100
+ left_upper, right_lower = gcj_bbox
101
+
102
+ # 计算左上角和右下角的瓦片行列号
103
+ x_min, y_min = lonlat_to_xyz(left_upper[0], left_upper[1], z) # 左上角
104
+ x_max, y_max = lonlat_to_xyz(right_lower[0], right_lower[1], z) # 右下角
105
+
106
+ # 创建任务列表,异步获取所有需要的瓦片
107
+ tasks = []
108
+ for ax in range(x_min, x_max + 1):
109
+ for ay in range(y_min, y_max + 1):
110
+ tasks.append(get_tile_gcj_cached(ax, ay, z, mapid))
111
+
112
+ # 并发执行所有瓦片下载任务
113
+ tiles = await asyncio.gather(*tasks)
114
+ tile_images = [Image.open(BytesIO(content)) for content in tiles]
115
+
116
+ # 拼合瓦片
117
+ composite = Image.new(
118
+ "RGBA", ((x_max - x_min + 1) * 256, (y_max - y_min + 1) * 256)
119
+ )
120
+
121
+ tile_index = 0
122
+ for i, ax in enumerate(range(x_min, x_max + 1)):
123
+ for j, ay in enumerate(range(y_min, y_max + 1)):
124
+ tile = tile_images[tile_index]
125
+ if tile:
126
+ composite.paste(tile, (i * 256, j * 256))
127
+ tile_index += 1
128
+
129
+ # 计算拼合后的瓦片范围
130
+ megred_bbox = xyz_to_bbox(x_min, y_min, z)[0], xyz_to_bbox(x_max, y_max, z)[1]
131
+
132
+ x_range = megred_bbox[1][0] - megred_bbox[0][0]
133
+ y_range = megred_bbox[0][1] - megred_bbox[1][1]
134
+
135
+ left_percent = (gcj_bbox[0][0] - megred_bbox[0][0]) / x_range
136
+ top_percent = (megred_bbox[0][1] - gcj_bbox[0][1]) / y_range
137
+ img_width, img_height = composite.size
138
+ # 裁剪选区(left, top, right, bottom)
139
+ crop_bbox = (
140
+ int(left_percent * img_width),
141
+ int(top_percent * img_height),
142
+ int(left_percent * img_width) + 256,
143
+ int(top_percent * img_height) + 256,
144
+ )
145
+
146
+ # 从拼合的瓦片中裁剪出对应的区域
147
+ croped_image = composite.crop(crop_bbox)
148
+ return image_to_bytes(croped_image)
149
+
150
+
151
+ async def get_tile_wgs_cached(
152
+ x: int, y: int, z: int, mapid: str, cache_dir: Path = Path.cwd().joinpath("cache")
153
+ ) -> Image:
154
+ """
155
+ 获取指定瓦片的图像,使用缓存。
156
+ Args:
157
+ x (int): Tile X coordinate.
158
+ y (int): Tile Y coordinate.
159
+ z (int): Zoom level.
160
+ mapid (str): Map Id
161
+ cache_dir (str): 缓存目录
162
+ Returns:
163
+ Image: Tile image.
164
+ """
165
+ tile_file_path = cache_dir.joinpath(f"{mapid}/WGS/{z}/{x}/{y}.png")
166
+ if tile_file_path.exists():
167
+ # print(f"从缓存中获取了瓦片: {tile_file_path}")
168
+ image = Image.open(tile_file_path)
169
+ return image_to_bytes(image)
170
+ # 如果缓存不存在,则下载瓦片并保存到缓存
171
+ tile_file_path.parent.mkdir(parents=True, exist_ok=True)
172
+ try:
173
+ image_bytes = await get_tile_wgs(x, y, z, mapid)
174
+ image = bytes_to_image(image_bytes)
175
+ image.save(tile_file_path, "PNG")
176
+ except Exception as e:
177
+ print(f"获取WGS瓦片失败: {e}")
178
+ # 如果是事件循环错误,尝试重置客户端并重试
179
+ if "Event loop is closed" in str(e) or "RuntimeError" in str(e):
180
+ from .fetch import reset_async_client
181
+
182
+ reset_async_client()
183
+ try:
184
+ image_bytes = await get_tile_wgs(x, y, z, mapid)
185
+ image = bytes_to_image(image_bytes)
186
+ image.save(tile_file_path, "PNG")
187
+ except Exception as retry_e:
188
+ print(f"重试获取WGS瓦片失败: {retry_e}")
189
+ return None
190
+ else:
191
+ return None
192
+
193
+ return image_bytes
194
+
195
+
196
+ # 测试
197
+
198
+
199
+ # http://127.0.0.1:8000/tiles/amap-sat/11/1661/807
200
+ # https://wprd02.is.autonavi.com//appmaptile?lang=zh_cn&size=1&scale=1&style=6&x=1661&y=807&z=11
@@ -0,0 +1,140 @@
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
@@ -0,0 +1,134 @@
1
+ import json
2
+ from io import BytesIO
3
+ import os
4
+ from math import atan, cos, log, pi, sinh, tan
5
+ from pathlib import Path
6
+
7
+ from PIL import Image
8
+
9
+ from .transform import wgs2gcj
10
+
11
+ APP_DIR = Path(__file__).parent
12
+
13
+
14
+ def get_cache_dir() -> str:
15
+ """
16
+ Get the cache directory from the environment variable or default to the app directory.
17
+
18
+ Returns:
19
+ str: The path to the cache directory.
20
+ """
21
+ env_cache = os.getenv("GCJRE_CACHE", "")
22
+ if env_cache:
23
+ print(f"Using cache directory from environment: {env_cache}")
24
+ return env_cache
25
+ print(f"Using current directory for cache: {Path.cwd().joinpath('cache')}")
26
+ return str(Path.cwd().joinpath("cache"))
27
+
28
+
29
+ def get_maps():
30
+ return json.load(open(str(APP_DIR.joinpath("maps.json")), "r", encoding="utf-8"))
31
+
32
+
33
+ def bytes_to_image(content: bytes) -> Image:
34
+ """
35
+ Convert bytes to a PIL Image.
36
+
37
+ Args:
38
+ content (bytes): Image data in bytes.
39
+
40
+ Returns:
41
+ Image: PIL Image object.
42
+ """
43
+ return Image.open(BytesIO(content))
44
+
45
+
46
+ def image_to_bytes(image: Image, format: str = "PNG") -> bytes:
47
+ """
48
+ Convert a PIL Image to bytes.
49
+
50
+ Args:
51
+ image (Image): PIL Image object.
52
+ format (str): Format to save the image, default is "PNG".
53
+
54
+ Returns:
55
+ bytes: Image data in bytes.
56
+ """
57
+ img_buffer = BytesIO()
58
+ image.save(img_buffer, format=format)
59
+ img_bytes = img_buffer.getvalue()
60
+ img_buffer.close()
61
+ return img_bytes
62
+
63
+
64
+ def xyz_to_lonlat(x: int, y: int, z: int) -> tuple:
65
+ """
66
+ 将XYZ瓦片坐标转换为经纬度(左上角点)。
67
+
68
+ Args:
69
+ x (int): Tile X coordinate.
70
+ y (int): Tile Y coordinate.
71
+ z (int): Zoom level.
72
+
73
+ Returns:
74
+ tuple: Longitude and latitude in degrees.
75
+ """
76
+ n = 2.0**z
77
+ lon_deg = x / n * 360.0 - 180.0
78
+ lat_rad = atan(sinh(pi * (1 - 2 * y / n)))
79
+ lat_deg = lat_rad * 180.0 / pi
80
+ return lon_deg, lat_deg
81
+
82
+
83
+ def lonlat_to_xyz(lon: float, lat: float, z: int) -> tuple:
84
+ """
85
+ Convert longitude and latitude to XYZ tile coordinates.
86
+
87
+ Args:
88
+ lon (float): Longitude in degrees.
89
+ lat (float): Latitude in degrees.
90
+ z (int): Zoom level.
91
+
92
+ Returns:
93
+ tuple: Tile X and Y coordinates.
94
+ """
95
+ n = 2.0**z
96
+ x = (lon + 180.0) / 360.0 * n
97
+ lat_rad = lat * pi / 180.0
98
+ t = log(tan(lat_rad) + 1 / cos(lat_rad))
99
+ y = (1 - t / pi) * n / 2
100
+ return int(x), int(y)
101
+
102
+
103
+ def xyz_to_bbox(x, y, z):
104
+ """
105
+ Convert XYZ tile coordinates to bounding box coordinates.
106
+
107
+ Args:
108
+ x (int): Tile X coordinate.
109
+ y (int): Tile Y coordinate.
110
+ z (int): Zoom level.
111
+
112
+ Returns:
113
+ tuple: Bounding box in the format (min_lon, min_lat, max_lon, max_lat).
114
+ """
115
+ left_upper_lon, left_upper_lat = xyz_to_lonlat(x, y, z)
116
+ right_lower_lon, right_lower_lat = xyz_to_lonlat(x + 1, y + 1, z)
117
+
118
+ return (left_upper_lon, left_upper_lat), (right_lower_lon, right_lower_lat)
119
+
120
+
121
+ def wgsbbox_to_gcjbbox(wgs_bbox):
122
+ """
123
+ Convert WGS84 bounding box to GCJ02 bounding box.
124
+
125
+ Args:
126
+ wgs_bbox (tuple): Bounding box in the format (min_lon, min_lat, max_lon, max_lat).
127
+
128
+ Returns:
129
+ tuple: GCJ02 bounding box in the same format.
130
+ """
131
+ left_upper, right_lower = wgs_bbox
132
+ gcj_left_upper = wgs2gcj(left_upper[0], left_upper[1])
133
+ gcj_right_lower = wgs2gcj(right_lower[0], right_lower[1])
134
+ return gcj_left_upper, gcj_right_lower
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = ["gcj_rectify_server"]
7
+
8
+
9
+ [project]
10
+ name = "gcj-rectify"
11
+ # version = "0.1.0"
12
+ description = "Add your description here"
13
+ readme = "README.md"
14
+ requires-python = ">=3.12"
15
+ dependencies = [
16
+ "fastapi>=0.116.1",
17
+ "httpx>=0.28.1",
18
+ "pillow>=11.3.0",
19
+ "uvicorn>=0.35.0",
20
+ ]
21
+ dynamic = ["version"]
22
+
23
+
24
+ [project.scripts]
25
+ gcj_rectify = "gcj_rectify_server.main:run"
26
+
27
+ [tool.hatch.version]
28
+ path = "gcj_rectify_server/__init__.py"
@@ -0,0 +1,286 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.13"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
9
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
10
+ wheels = [
11
+ { url = "https://mirrors.aliyun.com/pypi/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.10.0"
17
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
18
+ dependencies = [
19
+ { name = "idna" },
20
+ { name = "sniffio" },
21
+ ]
22
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6" }
23
+ wheels = [
24
+ { url = "https://mirrors.aliyun.com/pypi/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1" },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "certifi"
29
+ version = "2025.8.3"
30
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
31
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407" }
32
+ wheels = [
33
+ { url = "https://mirrors.aliyun.com/pypi/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "click"
38
+ version = "8.2.1"
39
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
40
+ dependencies = [
41
+ { name = "colorama", marker = "sys_platform == 'win32'" },
42
+ ]
43
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202" }
44
+ wheels = [
45
+ { url = "https://mirrors.aliyun.com/pypi/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" },
46
+ ]
47
+
48
+ [[package]]
49
+ name = "colorama"
50
+ version = "0.4.6"
51
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
52
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
53
+ wheels = [
54
+ { url = "https://mirrors.aliyun.com/pypi/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
55
+ ]
56
+
57
+ [[package]]
58
+ name = "fastapi"
59
+ version = "0.116.1"
60
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
61
+ dependencies = [
62
+ { name = "pydantic" },
63
+ { name = "starlette" },
64
+ { name = "typing-extensions" },
65
+ ]
66
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143" }
67
+ wheels = [
68
+ { url = "https://mirrors.aliyun.com/pypi/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565" },
69
+ ]
70
+
71
+ [[package]]
72
+ name = "gcj-rectify"
73
+ source = { editable = "." }
74
+ dependencies = [
75
+ { name = "fastapi" },
76
+ { name = "httpx" },
77
+ { name = "pillow" },
78
+ { name = "uvicorn" },
79
+ ]
80
+
81
+ [package.metadata]
82
+ requires-dist = [
83
+ { name = "fastapi", specifier = ">=0.116.1" },
84
+ { name = "httpx", specifier = ">=0.28.1" },
85
+ { name = "pillow", specifier = ">=11.3.0" },
86
+ { name = "uvicorn", specifier = ">=0.35.0" },
87
+ ]
88
+
89
+ [[package]]
90
+ name = "h11"
91
+ version = "0.16.0"
92
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
93
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
94
+ wheels = [
95
+ { url = "https://mirrors.aliyun.com/pypi/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
96
+ ]
97
+
98
+ [[package]]
99
+ name = "httpcore"
100
+ version = "1.0.9"
101
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
102
+ dependencies = [
103
+ { name = "certifi" },
104
+ { name = "h11" },
105
+ ]
106
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
107
+ wheels = [
108
+ { url = "https://mirrors.aliyun.com/pypi/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
109
+ ]
110
+
111
+ [[package]]
112
+ name = "httpx"
113
+ version = "0.28.1"
114
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
115
+ dependencies = [
116
+ { name = "anyio" },
117
+ { name = "certifi" },
118
+ { name = "httpcore" },
119
+ { name = "idna" },
120
+ ]
121
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
122
+ wheels = [
123
+ { url = "https://mirrors.aliyun.com/pypi/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
124
+ ]
125
+
126
+ [[package]]
127
+ name = "idna"
128
+ version = "3.10"
129
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
130
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" }
131
+ wheels = [
132
+ { url = "https://mirrors.aliyun.com/pypi/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" },
133
+ ]
134
+
135
+ [[package]]
136
+ name = "pillow"
137
+ version = "11.3.0"
138
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
139
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523" }
140
+ wheels = [
141
+ { url = "https://mirrors.aliyun.com/pypi/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd" },
142
+ { url = "https://mirrors.aliyun.com/pypi/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8" },
143
+ { url = "https://mirrors.aliyun.com/pypi/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f" },
144
+ { url = "https://mirrors.aliyun.com/pypi/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c" },
145
+ { url = "https://mirrors.aliyun.com/pypi/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd" },
146
+ { url = "https://mirrors.aliyun.com/pypi/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e" },
147
+ { url = "https://mirrors.aliyun.com/pypi/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1" },
148
+ { url = "https://mirrors.aliyun.com/pypi/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805" },
149
+ { url = "https://mirrors.aliyun.com/pypi/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8" },
150
+ { url = "https://mirrors.aliyun.com/pypi/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2" },
151
+ { url = "https://mirrors.aliyun.com/pypi/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b" },
152
+ { url = "https://mirrors.aliyun.com/pypi/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3" },
153
+ { url = "https://mirrors.aliyun.com/pypi/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51" },
154
+ { url = "https://mirrors.aliyun.com/pypi/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580" },
155
+ { url = "https://mirrors.aliyun.com/pypi/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e" },
156
+ { url = "https://mirrors.aliyun.com/pypi/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d" },
157
+ { url = "https://mirrors.aliyun.com/pypi/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced" },
158
+ { url = "https://mirrors.aliyun.com/pypi/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c" },
159
+ { url = "https://mirrors.aliyun.com/pypi/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8" },
160
+ { url = "https://mirrors.aliyun.com/pypi/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59" },
161
+ { url = "https://mirrors.aliyun.com/pypi/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe" },
162
+ { url = "https://mirrors.aliyun.com/pypi/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c" },
163
+ { url = "https://mirrors.aliyun.com/pypi/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788" },
164
+ { url = "https://mirrors.aliyun.com/pypi/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31" },
165
+ { url = "https://mirrors.aliyun.com/pypi/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e" },
166
+ { url = "https://mirrors.aliyun.com/pypi/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12" },
167
+ { url = "https://mirrors.aliyun.com/pypi/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a" },
168
+ { url = "https://mirrors.aliyun.com/pypi/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632" },
169
+ { url = "https://mirrors.aliyun.com/pypi/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673" },
170
+ { url = "https://mirrors.aliyun.com/pypi/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027" },
171
+ { url = "https://mirrors.aliyun.com/pypi/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77" },
172
+ { url = "https://mirrors.aliyun.com/pypi/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874" },
173
+ { url = "https://mirrors.aliyun.com/pypi/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a" },
174
+ { url = "https://mirrors.aliyun.com/pypi/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214" },
175
+ { url = "https://mirrors.aliyun.com/pypi/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635" },
176
+ { url = "https://mirrors.aliyun.com/pypi/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6" },
177
+ { url = "https://mirrors.aliyun.com/pypi/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae" },
178
+ { url = "https://mirrors.aliyun.com/pypi/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653" },
179
+ { url = "https://mirrors.aliyun.com/pypi/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6" },
180
+ { url = "https://mirrors.aliyun.com/pypi/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36" },
181
+ { url = "https://mirrors.aliyun.com/pypi/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b" },
182
+ { url = "https://mirrors.aliyun.com/pypi/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477" },
183
+ { url = "https://mirrors.aliyun.com/pypi/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50" },
184
+ { url = "https://mirrors.aliyun.com/pypi/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b" },
185
+ { url = "https://mirrors.aliyun.com/pypi/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12" },
186
+ { url = "https://mirrors.aliyun.com/pypi/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db" },
187
+ { url = "https://mirrors.aliyun.com/pypi/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa" },
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 = "sniffio"
235
+ version = "1.3.1"
236
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
237
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
238
+ wheels = [
239
+ { url = "https://mirrors.aliyun.com/pypi/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
240
+ ]
241
+
242
+ [[package]]
243
+ name = "starlette"
244
+ version = "0.47.2"
245
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
246
+ dependencies = [
247
+ { name = "anyio" },
248
+ ]
249
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8" }
250
+ wheels = [
251
+ { url = "https://mirrors.aliyun.com/pypi/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b" },
252
+ ]
253
+
254
+ [[package]]
255
+ name = "typing-extensions"
256
+ version = "4.14.1"
257
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
258
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36" }
259
+ wheels = [
260
+ { url = "https://mirrors.aliyun.com/pypi/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" },
261
+ ]
262
+
263
+ [[package]]
264
+ name = "typing-inspection"
265
+ version = "0.4.1"
266
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
267
+ dependencies = [
268
+ { name = "typing-extensions" },
269
+ ]
270
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28" }
271
+ wheels = [
272
+ { url = "https://mirrors.aliyun.com/pypi/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51" },
273
+ ]
274
+
275
+ [[package]]
276
+ name = "uvicorn"
277
+ version = "0.35.0"
278
+ source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
279
+ dependencies = [
280
+ { name = "click" },
281
+ { name = "h11" },
282
+ ]
283
+ sdist = { url = "https://mirrors.aliyun.com/pypi/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01" }
284
+ wheels = [
285
+ { url = "https://mirrors.aliyun.com/pypi/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a" },
286
+ ]