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.
- gcj_rectify-0.1.0/.gitignore +207 -0
- gcj_rectify-0.1.0/.python-version +1 -0
- gcj_rectify-0.1.0/LICENSE +21 -0
- gcj_rectify-0.1.0/PKG-INFO +18 -0
- gcj_rectify-0.1.0/README.md +6 -0
- gcj_rectify-0.1.0/gcj_rectify_server/__init__.py +1 -0
- gcj_rectify-0.1.0/gcj_rectify_server/fetch.py +70 -0
- gcj_rectify-0.1.0/gcj_rectify_server/main.py +89 -0
- gcj_rectify-0.1.0/gcj_rectify_server/maps.json +14 -0
- gcj_rectify-0.1.0/gcj_rectify_server/rectify.py +200 -0
- gcj_rectify-0.1.0/gcj_rectify_server/transform.py +140 -0
- gcj_rectify-0.1.0/gcj_rectify_server/utils.py +134 -0
- gcj_rectify-0.1.0/pyproject.toml +28 -0
- gcj_rectify-0.1.0/uv.lock +286 -0
|
@@ -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 @@
|
|
|
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
|
+
]
|