ymgis 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.
- ymgis-0.1.0/.gitignore +30 -0
- ymgis-0.1.0/LICENSE +21 -0
- ymgis-0.1.0/PKG-INFO +259 -0
- ymgis-0.1.0/README.md +229 -0
- ymgis-0.1.0/examples/basic_viewer.py +124 -0
- ymgis-0.1.0/examples/online_basemap.py +187 -0
- ymgis-0.1.0/pyproject.toml +67 -0
- ymgis-0.1.0/src/ymgis/__about__.py +1 -0
- ymgis-0.1.0/src/ymgis/__init__.py +85 -0
- ymgis-0.1.0/src/ymgis/canvas/__init__.py +5 -0
- ymgis-0.1.0/src/ymgis/canvas/map_canvas.py +218 -0
- ymgis-0.1.0/src/ymgis/crs.py +142 -0
- ymgis-0.1.0/src/ymgis/extent.py +92 -0
- ymgis-0.1.0/src/ymgis/layers/__init__.py +41 -0
- ymgis-0.1.0/src/ymgis/layers/base.py +96 -0
- ymgis-0.1.0/src/ymgis/layers/manager.py +92 -0
- ymgis-0.1.0/src/ymgis/layers/raster.py +173 -0
- ymgis-0.1.0/src/ymgis/layers/services/__init__.py +29 -0
- ymgis-0.1.0/src/ymgis/layers/services/arcgis.py +83 -0
- ymgis-0.1.0/src/ymgis/layers/services/baidu.py +179 -0
- ymgis-0.1.0/src/ymgis/layers/services/baidu_math.py +91 -0
- ymgis-0.1.0/src/ymgis/layers/services/http_image.py +60 -0
- ymgis-0.1.0/src/ymgis/layers/services/providers.py +125 -0
- ymgis-0.1.0/src/ymgis/layers/services/supermap.py +67 -0
- ymgis-0.1.0/src/ymgis/layers/services/viewport.py +135 -0
- ymgis-0.1.0/src/ymgis/layers/services/webmercator.py +74 -0
- ymgis-0.1.0/src/ymgis/layers/services/wms.py +66 -0
- ymgis-0.1.0/src/ymgis/layers/services/xyz.py +164 -0
- ymgis-0.1.0/src/ymgis/layers/vector.py +173 -0
- ymgis-0.1.0/src/ymgis/py.typed +1 -0
- ymgis-0.1.0/src/ymgis/tools/__init__.py +15 -0
- ymgis-0.1.0/src/ymgis/tools/base.py +49 -0
- ymgis-0.1.0/src/ymgis/tools/draw_point.py +31 -0
- ymgis-0.1.0/src/ymgis/tools/draw_polygon.py +108 -0
- ymgis-0.1.0/src/ymgis/tools/draw_rect.py +75 -0
- ymgis-0.1.0/src/ymgis/tools/pan.py +55 -0
- ymgis-0.1.0/src/ymgis/transform.py +45 -0
- ymgis-0.1.0/src/ymgis/widgets/__init__.py +5 -0
- ymgis-0.1.0/src/ymgis/widgets/layer_tree.py +73 -0
- ymgis-0.1.0/tests/test_crs.py +67 -0
- ymgis-0.1.0/tests/test_extent.py +21 -0
- ymgis-0.1.0/tests/test_services.py +55 -0
ymgis-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Build / publish artifacts
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
upload_log.txt
|
|
7
|
+
|
|
8
|
+
# Local secrets — never commit
|
|
9
|
+
scripts/publish.local.ps1
|
|
10
|
+
.env
|
|
11
|
+
.pypirc
|
|
12
|
+
PyPI-Recovery-Codes*.txt
|
|
13
|
+
*Recovery-Codes*
|
|
14
|
+
*API*token*
|
|
15
|
+
*.pem
|
|
16
|
+
|
|
17
|
+
# Python / tooling
|
|
18
|
+
__pycache__/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
*.pyc
|
|
25
|
+
|
|
26
|
+
# OS / IDE
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
ymgis-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yima
|
|
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.
|
ymgis-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ymgis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight PyQt GIS canvas with dynamic CRS reprojection, online basemaps and drawing tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/yima/ymgis
|
|
6
|
+
Project-URL: Issues, https://github.com/yima/ymgis/issues
|
|
7
|
+
Author: Yima
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: arcgis,canvas,geotiff,gis,pyqt,raster,shapely,tianditu,wms,xyz
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: numpy>=1.24
|
|
20
|
+
Requires-Dist: pyproj>=3.6
|
|
21
|
+
Requires-Dist: pyqt6>=6.6
|
|
22
|
+
Requires-Dist: rasterio>=1.3
|
|
23
|
+
Requires-Dist: shapely>=2.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# ymgis
|
|
32
|
+
|
|
33
|
+
面向桌面 GIS 小工具的轻量组件库:**画布 + 图层管理 + 图层树 + 基础绘制 + 在线地图服务预览 + 动态重投影**。
|
|
34
|
+
|
|
35
|
+
不依赖 QGIS。基于 **PyQt6 + rasterio + Shapely + pyproj**,可发布到 PyPI,供 QuickAnno、下载器、质检工具等复用。
|
|
36
|
+
|
|
37
|
+
## 能力概览
|
|
38
|
+
|
|
39
|
+
| 能力 | 说明 |
|
|
40
|
+
|------|------|
|
|
41
|
+
| 地图画布 | 平移、滚轮缩放、图层栈渲染、地图工具切换 |
|
|
42
|
+
| 动态重投影 | 可叠加不同坐标系数据;**视图 CRS 由第一个带 CRS 的图层决定** |
|
|
43
|
+
| 本地栅格 | GeoTIFF(rasterio)按视口窗口读取;跨 CRS 时 WarpedVRT 重投影 |
|
|
44
|
+
| 矢量标注 | 内存 Shapely 点/线/面 + 简单样式;渲染时按需重投影 |
|
|
45
|
+
| 图层树 | 勾选显隐、图层列表 |
|
|
46
|
+
| 绘制工具 | 平移 / 点 / 矩形 / 多边形 |
|
|
47
|
+
| 切片底图 | 通用 XYZ/TMS;预设天地图、高德、百度、谷歌 |
|
|
48
|
+
| OGC WMS | `GetMap` 视口图像预览 |
|
|
49
|
+
| ArcGIS REST | MapServer / ImageServer `export`;亦可 XYZ `tile/{z}/{y}/{x}` |
|
|
50
|
+
| 超图 iServer | 地图 `image.png` 视口预览;亦可 `zxyTileImage` 瓦片 |
|
|
51
|
+
|
|
52
|
+
在线服务图层自身为 **EPSG:3857**,叠到其它视图 CRS 时会动态重投影(AABB 近似)。高德/百度存在国内加密坐标偏移,适合底图预览,不宜与精密测绘数据直接叠合量测。
|
|
53
|
+
|
|
54
|
+
## 安装
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 开发态(本仓库)
|
|
58
|
+
pip install -e .
|
|
59
|
+
|
|
60
|
+
# 发布后
|
|
61
|
+
pip install ymgis
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 快速开始
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from PyQt6.QtWidgets import QApplication, QMainWindow, QSplitter
|
|
68
|
+
from ymgis import MapCanvas, RasterLayer, VectorLayer, LayerTreeWidget, PanTool, DrawPolygonTool
|
|
69
|
+
|
|
70
|
+
app = QApplication([])
|
|
71
|
+
win = QMainWindow()
|
|
72
|
+
canvas = MapCanvas()
|
|
73
|
+
tree = LayerTreeWidget(canvas.layer_manager)
|
|
74
|
+
|
|
75
|
+
# 第一个带 CRS 的图层决定视图坐标系(无需手动 set_crs)
|
|
76
|
+
raster = RasterLayer(r"D:\data\demo.tif")
|
|
77
|
+
canvas.layer_manager.add_layer(raster)
|
|
78
|
+
canvas.zoom_to_full_extent()
|
|
79
|
+
|
|
80
|
+
# 标注层自动继承视图 CRS;绘制坐标为画布 CRS
|
|
81
|
+
annos = VectorLayer("标注")
|
|
82
|
+
canvas.layer_manager.add_layer(annos)
|
|
83
|
+
|
|
84
|
+
tool = DrawPolygonTool()
|
|
85
|
+
tool.polygon_drawn.connect(
|
|
86
|
+
lambda g: annos.add_feature(g, {"class": "demo"}, source_crs=canvas.crs)
|
|
87
|
+
)
|
|
88
|
+
canvas.set_map_tool(tool)
|
|
89
|
+
|
|
90
|
+
split = QSplitter()
|
|
91
|
+
split.addWidget(tree)
|
|
92
|
+
split.addWidget(canvas)
|
|
93
|
+
win.setCentralWidget(split)
|
|
94
|
+
win.resize(1200, 800)
|
|
95
|
+
win.show()
|
|
96
|
+
app.exec()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 多坐标系叠加
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from ymgis import MapCanvas, RasterLayer, VectorLayer, create_amap
|
|
103
|
+
from shapely.geometry import Point
|
|
104
|
+
|
|
105
|
+
canvas = MapCanvas()
|
|
106
|
+
|
|
107
|
+
# 先加载 CGCS2000 / 任意投影影像 → 视图 CRS = 该影像 CRS
|
|
108
|
+
canvas.layer_manager.add_layer(RasterLayer(r"D:\data\cgcs2000.tif"))
|
|
109
|
+
|
|
110
|
+
# 再叠加 WGS84 矢量 → 渲染时自动重投影到视图 CRS
|
|
111
|
+
pts = VectorLayer("点", crs="EPSG:4326")
|
|
112
|
+
pts.add_feature(Point(116.4, 39.9))
|
|
113
|
+
canvas.layer_manager.add_layer(pts)
|
|
114
|
+
|
|
115
|
+
# 在线底图(EPSG:3857)也会重投影到当前视图
|
|
116
|
+
canvas.layer_manager.add_layer(create_amap(), to_top=False)
|
|
117
|
+
|
|
118
|
+
canvas.zoom_to_full_extent()
|
|
119
|
+
# 如需强制指定视图 CRS(并锁定,不再被首层覆盖):
|
|
120
|
+
# canvas.set_crs("EPSG:3857")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 在线底图 / 地图服务预览
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from ymgis import (
|
|
127
|
+
MapCanvas,
|
|
128
|
+
create_amap,
|
|
129
|
+
create_baidu,
|
|
130
|
+
create_google,
|
|
131
|
+
create_tianditu,
|
|
132
|
+
WmsLayer,
|
|
133
|
+
ArcGisRestLayer,
|
|
134
|
+
SuperMapRestLayer,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
canvas = MapCanvas()
|
|
138
|
+
|
|
139
|
+
canvas.layer_manager.add_layer(create_amap(), to_top=False)
|
|
140
|
+
# canvas.layer_manager.add_layer(create_baidu(), to_top=False)
|
|
141
|
+
# canvas.layer_manager.add_layer(create_google(), to_top=False)
|
|
142
|
+
# canvas.layer_manager.add_layer(create_tianditu("你的天地图tk"), to_top=False)
|
|
143
|
+
|
|
144
|
+
# WMS / ArcGIS / 超图
|
|
145
|
+
# canvas.layer_manager.add_layer(WmsLayer("https://host/wms", "workspace:layer"))
|
|
146
|
+
# canvas.layer_manager.add_layer(
|
|
147
|
+
# ArcGisRestLayer("https://host/arcgis/rest/services/Demo/MapServer")
|
|
148
|
+
# )
|
|
149
|
+
# canvas.layer_manager.add_layer(
|
|
150
|
+
# SuperMapRestLayer("http://host:8090/iserver/services/map-demo/rest/maps/Demo")
|
|
151
|
+
# )
|
|
152
|
+
|
|
153
|
+
canvas.zoom_to_full_extent()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
自定义 XYZ:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from ymgis import XyzTileLayer
|
|
160
|
+
|
|
161
|
+
layer = XyzTileLayer(
|
|
162
|
+
"https://tile.example.com/{z}/{x}/{y}.png",
|
|
163
|
+
name="自定义瓦片",
|
|
164
|
+
subdomains=["a", "b", "c"], # 对应模板 {s}
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
运行示例:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install -e .
|
|
172
|
+
python examples/basic_viewer.py
|
|
173
|
+
# 可选传入 GeoTIFF:
|
|
174
|
+
python examples/basic_viewer.py D:\data\demo.tif
|
|
175
|
+
|
|
176
|
+
# 在线底图 / WMS / ArcGIS / 超图预览:
|
|
177
|
+
python examples/online_basemap.py
|
|
178
|
+
# 天地图也可:set TIANDITU_TOKEN=你的tk
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 模块
|
|
182
|
+
|
|
183
|
+
| 模块 | 说明 |
|
|
184
|
+
|------|------|
|
|
185
|
+
| `MapCanvas` | 地图画布;首层定 CRS、动态重投影渲染 |
|
|
186
|
+
| `LayerManager` | 图层栈;`combined_extent(target_crs)` |
|
|
187
|
+
| `RasterLayer` | GeoTIFF 窗口读取 + WarpedVRT 重投影 |
|
|
188
|
+
| `VectorLayer` | 内存 Shapely 要素;可指定 `crs=` |
|
|
189
|
+
| `ymgis.crs` | `transform_xy` / `transform_extent` / `transform_geometry` |
|
|
190
|
+
| `XyzTileLayer` | 通用 XYZ/TMS 异步瓦片底图 |
|
|
191
|
+
| `BaiduTileLayer` / `create_*` | 百度及天地图/高德/谷歌预设 |
|
|
192
|
+
| `WmsLayer` | OGC WMS GetMap 视口预览 |
|
|
193
|
+
| `ArcGisRestLayer` | ArcGIS REST export 预览 |
|
|
194
|
+
| `SuperMapRestLayer` | 超图 iServer 地图预览 |
|
|
195
|
+
| `LayerTreeWidget` | 勾选显隐、图层列表 |
|
|
196
|
+
| `PanTool` / `DrawPointTool` / `DrawRectTool` / `DrawPolygonTool` | 交互工具 |
|
|
197
|
+
|
|
198
|
+
## 设计约定
|
|
199
|
+
|
|
200
|
+
- **视图 CRS**:由**第一个加载且带有 CRS 的图层**自动设定;`canvas.set_crs(...)` 可显式指定并锁定。
|
|
201
|
+
- **动态重投影**:后续图层 CRS 不同时,在渲染/`combined_extent` 时转换到视图 CRS(依赖 pyproj;栅格用 WarpedVRT)。
|
|
202
|
+
- 无 CRS 的图层(如空标注层)加入后自动继承当前视图 CRS;绘制工具产出的几何为画布 CRS。
|
|
203
|
+
- 在线切片/WMS 图层原生多为 **EPSG:3857**;瓦片异步加载并带内存缓存。
|
|
204
|
+
- 栅格按视口读取并缩放到控件尺寸,避免整图进内存。
|
|
205
|
+
|
|
206
|
+
## 与 QGIS / GeoD 的关系
|
|
207
|
+
|
|
208
|
+
- **不是** QGIS 替代品:无工程文件、无复杂符号、无拓扑规则。
|
|
209
|
+
- 可预览常见在线底图与 OGC/REST 地图服务,但**不是** GeoD 那种批量网图下载器。
|
|
210
|
+
- 目标:多个内部小工具共用同一套画布/图层树,安装包体积远小于捆绑 `qgis_runtime`。
|
|
211
|
+
|
|
212
|
+
## 发布到 PyPI
|
|
213
|
+
|
|
214
|
+
推荐使用脚本(Windows PowerShell):
|
|
215
|
+
|
|
216
|
+
```powershell
|
|
217
|
+
# 首次:复制并填写 token(不要提交 publish.local.ps1)
|
|
218
|
+
copy scripts\publish.local.ps1.example scripts\publish.local.ps1
|
|
219
|
+
# 编辑 scripts\publish.local.ps1,将 $PyPiToken 设为 pypi-... 开头的 API token
|
|
220
|
+
|
|
221
|
+
.\scripts\publish.ps1
|
|
222
|
+
# 或:.\scripts\publish.ps1 -Token "pypi-你的完整token"
|
|
223
|
+
# 测试库:.\scripts\publish.ps1 -TestPyPI
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
也可手动:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
pip install -e ".[dev]"
|
|
230
|
+
python -m build
|
|
231
|
+
# Username=__token__ Password=pypi-...
|
|
232
|
+
twine upload dist/*
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
请先在 [PyPI](https://pypi.org) 确认包名 `ymgis` 可用;若冲突可改为 `yima-gis` 并同步修改 `pyproject.toml` 的 `name`。若已上传过同一版本,需先升高 `pyproject.toml` 的 `version`。
|
|
236
|
+
|
|
237
|
+
## 目录
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
ymgis/
|
|
241
|
+
├── pyproject.toml
|
|
242
|
+
├── README.md
|
|
243
|
+
├── LICENSE
|
|
244
|
+
├── examples/
|
|
245
|
+
│ ├── basic_viewer.py
|
|
246
|
+
│ └── online_basemap.py
|
|
247
|
+
├── src/ymgis/
|
|
248
|
+
│ ├── crs.py # 动态重投影
|
|
249
|
+
│ ├── canvas/
|
|
250
|
+
│ ├── layers/
|
|
251
|
+
│ │ └── services/ # XYZ / WMS / ArcGIS / 超图 / 厂商预设
|
|
252
|
+
│ ├── tools/
|
|
253
|
+
│ └── widgets/
|
|
254
|
+
└── tests/
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT
|
ymgis-0.1.0/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# ymgis
|
|
2
|
+
|
|
3
|
+
面向桌面 GIS 小工具的轻量组件库:**画布 + 图层管理 + 图层树 + 基础绘制 + 在线地图服务预览 + 动态重投影**。
|
|
4
|
+
|
|
5
|
+
不依赖 QGIS。基于 **PyQt6 + rasterio + Shapely + pyproj**,可发布到 PyPI,供 QuickAnno、下载器、质检工具等复用。
|
|
6
|
+
|
|
7
|
+
## 能力概览
|
|
8
|
+
|
|
9
|
+
| 能力 | 说明 |
|
|
10
|
+
|------|------|
|
|
11
|
+
| 地图画布 | 平移、滚轮缩放、图层栈渲染、地图工具切换 |
|
|
12
|
+
| 动态重投影 | 可叠加不同坐标系数据;**视图 CRS 由第一个带 CRS 的图层决定** |
|
|
13
|
+
| 本地栅格 | GeoTIFF(rasterio)按视口窗口读取;跨 CRS 时 WarpedVRT 重投影 |
|
|
14
|
+
| 矢量标注 | 内存 Shapely 点/线/面 + 简单样式;渲染时按需重投影 |
|
|
15
|
+
| 图层树 | 勾选显隐、图层列表 |
|
|
16
|
+
| 绘制工具 | 平移 / 点 / 矩形 / 多边形 |
|
|
17
|
+
| 切片底图 | 通用 XYZ/TMS;预设天地图、高德、百度、谷歌 |
|
|
18
|
+
| OGC WMS | `GetMap` 视口图像预览 |
|
|
19
|
+
| ArcGIS REST | MapServer / ImageServer `export`;亦可 XYZ `tile/{z}/{y}/{x}` |
|
|
20
|
+
| 超图 iServer | 地图 `image.png` 视口预览;亦可 `zxyTileImage` 瓦片 |
|
|
21
|
+
|
|
22
|
+
在线服务图层自身为 **EPSG:3857**,叠到其它视图 CRS 时会动态重投影(AABB 近似)。高德/百度存在国内加密坐标偏移,适合底图预览,不宜与精密测绘数据直接叠合量测。
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 开发态(本仓库)
|
|
28
|
+
pip install -e .
|
|
29
|
+
|
|
30
|
+
# 发布后
|
|
31
|
+
pip install ymgis
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 快速开始
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from PyQt6.QtWidgets import QApplication, QMainWindow, QSplitter
|
|
38
|
+
from ymgis import MapCanvas, RasterLayer, VectorLayer, LayerTreeWidget, PanTool, DrawPolygonTool
|
|
39
|
+
|
|
40
|
+
app = QApplication([])
|
|
41
|
+
win = QMainWindow()
|
|
42
|
+
canvas = MapCanvas()
|
|
43
|
+
tree = LayerTreeWidget(canvas.layer_manager)
|
|
44
|
+
|
|
45
|
+
# 第一个带 CRS 的图层决定视图坐标系(无需手动 set_crs)
|
|
46
|
+
raster = RasterLayer(r"D:\data\demo.tif")
|
|
47
|
+
canvas.layer_manager.add_layer(raster)
|
|
48
|
+
canvas.zoom_to_full_extent()
|
|
49
|
+
|
|
50
|
+
# 标注层自动继承视图 CRS;绘制坐标为画布 CRS
|
|
51
|
+
annos = VectorLayer("标注")
|
|
52
|
+
canvas.layer_manager.add_layer(annos)
|
|
53
|
+
|
|
54
|
+
tool = DrawPolygonTool()
|
|
55
|
+
tool.polygon_drawn.connect(
|
|
56
|
+
lambda g: annos.add_feature(g, {"class": "demo"}, source_crs=canvas.crs)
|
|
57
|
+
)
|
|
58
|
+
canvas.set_map_tool(tool)
|
|
59
|
+
|
|
60
|
+
split = QSplitter()
|
|
61
|
+
split.addWidget(tree)
|
|
62
|
+
split.addWidget(canvas)
|
|
63
|
+
win.setCentralWidget(split)
|
|
64
|
+
win.resize(1200, 800)
|
|
65
|
+
win.show()
|
|
66
|
+
app.exec()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 多坐标系叠加
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from ymgis import MapCanvas, RasterLayer, VectorLayer, create_amap
|
|
73
|
+
from shapely.geometry import Point
|
|
74
|
+
|
|
75
|
+
canvas = MapCanvas()
|
|
76
|
+
|
|
77
|
+
# 先加载 CGCS2000 / 任意投影影像 → 视图 CRS = 该影像 CRS
|
|
78
|
+
canvas.layer_manager.add_layer(RasterLayer(r"D:\data\cgcs2000.tif"))
|
|
79
|
+
|
|
80
|
+
# 再叠加 WGS84 矢量 → 渲染时自动重投影到视图 CRS
|
|
81
|
+
pts = VectorLayer("点", crs="EPSG:4326")
|
|
82
|
+
pts.add_feature(Point(116.4, 39.9))
|
|
83
|
+
canvas.layer_manager.add_layer(pts)
|
|
84
|
+
|
|
85
|
+
# 在线底图(EPSG:3857)也会重投影到当前视图
|
|
86
|
+
canvas.layer_manager.add_layer(create_amap(), to_top=False)
|
|
87
|
+
|
|
88
|
+
canvas.zoom_to_full_extent()
|
|
89
|
+
# 如需强制指定视图 CRS(并锁定,不再被首层覆盖):
|
|
90
|
+
# canvas.set_crs("EPSG:3857")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 在线底图 / 地图服务预览
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from ymgis import (
|
|
97
|
+
MapCanvas,
|
|
98
|
+
create_amap,
|
|
99
|
+
create_baidu,
|
|
100
|
+
create_google,
|
|
101
|
+
create_tianditu,
|
|
102
|
+
WmsLayer,
|
|
103
|
+
ArcGisRestLayer,
|
|
104
|
+
SuperMapRestLayer,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
canvas = MapCanvas()
|
|
108
|
+
|
|
109
|
+
canvas.layer_manager.add_layer(create_amap(), to_top=False)
|
|
110
|
+
# canvas.layer_manager.add_layer(create_baidu(), to_top=False)
|
|
111
|
+
# canvas.layer_manager.add_layer(create_google(), to_top=False)
|
|
112
|
+
# canvas.layer_manager.add_layer(create_tianditu("你的天地图tk"), to_top=False)
|
|
113
|
+
|
|
114
|
+
# WMS / ArcGIS / 超图
|
|
115
|
+
# canvas.layer_manager.add_layer(WmsLayer("https://host/wms", "workspace:layer"))
|
|
116
|
+
# canvas.layer_manager.add_layer(
|
|
117
|
+
# ArcGisRestLayer("https://host/arcgis/rest/services/Demo/MapServer")
|
|
118
|
+
# )
|
|
119
|
+
# canvas.layer_manager.add_layer(
|
|
120
|
+
# SuperMapRestLayer("http://host:8090/iserver/services/map-demo/rest/maps/Demo")
|
|
121
|
+
# )
|
|
122
|
+
|
|
123
|
+
canvas.zoom_to_full_extent()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
自定义 XYZ:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from ymgis import XyzTileLayer
|
|
130
|
+
|
|
131
|
+
layer = XyzTileLayer(
|
|
132
|
+
"https://tile.example.com/{z}/{x}/{y}.png",
|
|
133
|
+
name="自定义瓦片",
|
|
134
|
+
subdomains=["a", "b", "c"], # 对应模板 {s}
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
运行示例:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install -e .
|
|
142
|
+
python examples/basic_viewer.py
|
|
143
|
+
# 可选传入 GeoTIFF:
|
|
144
|
+
python examples/basic_viewer.py D:\data\demo.tif
|
|
145
|
+
|
|
146
|
+
# 在线底图 / WMS / ArcGIS / 超图预览:
|
|
147
|
+
python examples/online_basemap.py
|
|
148
|
+
# 天地图也可:set TIANDITU_TOKEN=你的tk
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 模块
|
|
152
|
+
|
|
153
|
+
| 模块 | 说明 |
|
|
154
|
+
|------|------|
|
|
155
|
+
| `MapCanvas` | 地图画布;首层定 CRS、动态重投影渲染 |
|
|
156
|
+
| `LayerManager` | 图层栈;`combined_extent(target_crs)` |
|
|
157
|
+
| `RasterLayer` | GeoTIFF 窗口读取 + WarpedVRT 重投影 |
|
|
158
|
+
| `VectorLayer` | 内存 Shapely 要素;可指定 `crs=` |
|
|
159
|
+
| `ymgis.crs` | `transform_xy` / `transform_extent` / `transform_geometry` |
|
|
160
|
+
| `XyzTileLayer` | 通用 XYZ/TMS 异步瓦片底图 |
|
|
161
|
+
| `BaiduTileLayer` / `create_*` | 百度及天地图/高德/谷歌预设 |
|
|
162
|
+
| `WmsLayer` | OGC WMS GetMap 视口预览 |
|
|
163
|
+
| `ArcGisRestLayer` | ArcGIS REST export 预览 |
|
|
164
|
+
| `SuperMapRestLayer` | 超图 iServer 地图预览 |
|
|
165
|
+
| `LayerTreeWidget` | 勾选显隐、图层列表 |
|
|
166
|
+
| `PanTool` / `DrawPointTool` / `DrawRectTool` / `DrawPolygonTool` | 交互工具 |
|
|
167
|
+
|
|
168
|
+
## 设计约定
|
|
169
|
+
|
|
170
|
+
- **视图 CRS**:由**第一个加载且带有 CRS 的图层**自动设定;`canvas.set_crs(...)` 可显式指定并锁定。
|
|
171
|
+
- **动态重投影**:后续图层 CRS 不同时,在渲染/`combined_extent` 时转换到视图 CRS(依赖 pyproj;栅格用 WarpedVRT)。
|
|
172
|
+
- 无 CRS 的图层(如空标注层)加入后自动继承当前视图 CRS;绘制工具产出的几何为画布 CRS。
|
|
173
|
+
- 在线切片/WMS 图层原生多为 **EPSG:3857**;瓦片异步加载并带内存缓存。
|
|
174
|
+
- 栅格按视口读取并缩放到控件尺寸,避免整图进内存。
|
|
175
|
+
|
|
176
|
+
## 与 QGIS / GeoD 的关系
|
|
177
|
+
|
|
178
|
+
- **不是** QGIS 替代品:无工程文件、无复杂符号、无拓扑规则。
|
|
179
|
+
- 可预览常见在线底图与 OGC/REST 地图服务,但**不是** GeoD 那种批量网图下载器。
|
|
180
|
+
- 目标:多个内部小工具共用同一套画布/图层树,安装包体积远小于捆绑 `qgis_runtime`。
|
|
181
|
+
|
|
182
|
+
## 发布到 PyPI
|
|
183
|
+
|
|
184
|
+
推荐使用脚本(Windows PowerShell):
|
|
185
|
+
|
|
186
|
+
```powershell
|
|
187
|
+
# 首次:复制并填写 token(不要提交 publish.local.ps1)
|
|
188
|
+
copy scripts\publish.local.ps1.example scripts\publish.local.ps1
|
|
189
|
+
# 编辑 scripts\publish.local.ps1,将 $PyPiToken 设为 pypi-... 开头的 API token
|
|
190
|
+
|
|
191
|
+
.\scripts\publish.ps1
|
|
192
|
+
# 或:.\scripts\publish.ps1 -Token "pypi-你的完整token"
|
|
193
|
+
# 测试库:.\scripts\publish.ps1 -TestPyPI
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
也可手动:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
pip install -e ".[dev]"
|
|
200
|
+
python -m build
|
|
201
|
+
# Username=__token__ Password=pypi-...
|
|
202
|
+
twine upload dist/*
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
请先在 [PyPI](https://pypi.org) 确认包名 `ymgis` 可用;若冲突可改为 `yima-gis` 并同步修改 `pyproject.toml` 的 `name`。若已上传过同一版本,需先升高 `pyproject.toml` 的 `version`。
|
|
206
|
+
|
|
207
|
+
## 目录
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
ymgis/
|
|
211
|
+
├── pyproject.toml
|
|
212
|
+
├── README.md
|
|
213
|
+
├── LICENSE
|
|
214
|
+
├── examples/
|
|
215
|
+
│ ├── basic_viewer.py
|
|
216
|
+
│ └── online_basemap.py
|
|
217
|
+
├── src/ymgis/
|
|
218
|
+
│ ├── crs.py # 动态重投影
|
|
219
|
+
│ ├── canvas/
|
|
220
|
+
│ ├── layers/
|
|
221
|
+
│ │ └── services/ # XYZ / WMS / ArcGIS / 超图 / 厂商预设
|
|
222
|
+
│ ├── tools/
|
|
223
|
+
│ └── widgets/
|
|
224
|
+
└── tests/
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Minimal ymgis viewer: layer tree + canvas + pan / draw polygon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtGui import QAction, QActionGroup
|
|
9
|
+
from PyQt6.QtWidgets import (
|
|
10
|
+
QApplication,
|
|
11
|
+
QLabel,
|
|
12
|
+
QMainWindow,
|
|
13
|
+
QMessageBox,
|
|
14
|
+
QSplitter,
|
|
15
|
+
QStatusBar,
|
|
16
|
+
QToolBar,
|
|
17
|
+
QVBoxLayout,
|
|
18
|
+
QWidget,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Allow running without install: add ../src
|
|
22
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
23
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
24
|
+
|
|
25
|
+
from ymgis import ( # noqa: E402
|
|
26
|
+
DrawPolygonTool,
|
|
27
|
+
DrawRectTool,
|
|
28
|
+
LayerTreeWidget,
|
|
29
|
+
MapCanvas,
|
|
30
|
+
PanTool,
|
|
31
|
+
RasterLayer,
|
|
32
|
+
VectorLayer,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MainWindow(QMainWindow):
|
|
37
|
+
def __init__(self, raster_path: str | None = None) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.setWindowTitle("ymgis basic viewer")
|
|
40
|
+
self.resize(1280, 800)
|
|
41
|
+
|
|
42
|
+
self.canvas = MapCanvas()
|
|
43
|
+
self.tree = LayerTreeWidget(self.canvas.layer_manager)
|
|
44
|
+
self.annos = VectorLayer("标注")
|
|
45
|
+
self.canvas.layer_manager.add_layer(self.annos)
|
|
46
|
+
|
|
47
|
+
split = QSplitter()
|
|
48
|
+
left = QWidget()
|
|
49
|
+
left_layout = QVBoxLayout(left)
|
|
50
|
+
left_layout.setContentsMargins(4, 4, 4, 4)
|
|
51
|
+
left_layout.addWidget(QLabel("图层"))
|
|
52
|
+
left_layout.addWidget(self.tree)
|
|
53
|
+
split.addWidget(left)
|
|
54
|
+
split.addWidget(self.canvas)
|
|
55
|
+
split.setStretchFactor(1, 1)
|
|
56
|
+
self.setCentralWidget(split)
|
|
57
|
+
|
|
58
|
+
self._init_toolbar()
|
|
59
|
+
self._status = QStatusBar()
|
|
60
|
+
self.setStatusBar(self._status)
|
|
61
|
+
self.canvas.cursor_map_moved.connect(
|
|
62
|
+
lambda x, y: self._status.showMessage(f"X={x:.3f} Y={y:.3f}")
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if raster_path:
|
|
66
|
+
self._load_raster(raster_path)
|
|
67
|
+
|
|
68
|
+
def _init_toolbar(self) -> None:
|
|
69
|
+
tb = QToolBar("工具")
|
|
70
|
+
self.addToolBar(tb)
|
|
71
|
+
group = QActionGroup(self)
|
|
72
|
+
group.setExclusive(True)
|
|
73
|
+
|
|
74
|
+
self._pan = PanTool(self)
|
|
75
|
+
self._rect = DrawRectTool(self)
|
|
76
|
+
self._poly = DrawPolygonTool(self)
|
|
77
|
+
self._rect.rect_drawn.connect(self._on_geom)
|
|
78
|
+
self._poly.polygon_drawn.connect(self._on_geom)
|
|
79
|
+
|
|
80
|
+
def add_tool(text: str, tool, checked: bool = False) -> None:
|
|
81
|
+
act = QAction(text, self)
|
|
82
|
+
act.setCheckable(True)
|
|
83
|
+
act.setChecked(checked)
|
|
84
|
+
group.addAction(act)
|
|
85
|
+
tb.addAction(act)
|
|
86
|
+
act.triggered.connect(lambda _=False, t=tool: self.canvas.set_map_tool(t))
|
|
87
|
+
|
|
88
|
+
add_tool("平移", self._pan, True)
|
|
89
|
+
add_tool("矩形", self._rect)
|
|
90
|
+
add_tool("多边形", self._poly)
|
|
91
|
+
self.canvas.set_map_tool(self._pan)
|
|
92
|
+
|
|
93
|
+
act_full = QAction("全图", self)
|
|
94
|
+
act_full.triggered.connect(self.canvas.zoom_to_full_extent)
|
|
95
|
+
tb.addAction(act_full)
|
|
96
|
+
|
|
97
|
+
def _on_geom(self, geom) -> None:
|
|
98
|
+
self.annos.add_feature(geom, {"source": "draw"})
|
|
99
|
+
self._status.showMessage(f"已添加要素,共 {self.annos.feature_count()} 个", 3000)
|
|
100
|
+
|
|
101
|
+
def _load_raster(self, path: str) -> None:
|
|
102
|
+
try:
|
|
103
|
+
layer = RasterLayer(path)
|
|
104
|
+
except Exception as ex:
|
|
105
|
+
QMessageBox.warning(self, "打开失败", str(ex))
|
|
106
|
+
return
|
|
107
|
+
self.canvas.layer_manager.add_layer(layer, to_top=False)
|
|
108
|
+
# 视图 CRS 由第一个带坐标系的图层自动确定;也可 canvas.set_crs(...) 锁定
|
|
109
|
+
self.canvas.zoom_to_full_extent()
|
|
110
|
+
self._status.showMessage(f"已加载: {path}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main() -> None:
|
|
114
|
+
app = QApplication(sys.argv)
|
|
115
|
+
path = sys.argv[1] if len(sys.argv) > 1 else None
|
|
116
|
+
win = MainWindow(path)
|
|
117
|
+
win.show()
|
|
118
|
+
if path is None:
|
|
119
|
+
win.statusBar().showMessage("未指定影像:可传入 GeoTIFF 路径,或仅试用矢量绘制")
|
|
120
|
+
sys.exit(app.exec())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|