Metadata-Version: 2.5
Name: ymgis
Version: 0.1.6
Summary: Lightweight PyQt GIS canvas with dynamic CRS reprojection, online basemaps and drawing tools
Project-URL: Homepage, https://github.com/yima/ymgis
Project-URL: Issues, https://github.com/yima/ymgis/issues
Author: Yima
License-Expression: MIT
License-File: LICENSE
Keywords: arcgis,canvas,geotiff,gis,pyqt,raster,shapely,tianditu,wms,xyz
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: pyproj>=3.6
Requires-Dist: pyqt6>=6.6
Requires-Dist: rasterio>=1.3
Requires-Dist: shapely>=2.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# ymgis

面向桌面 GIS 小工具的轻量组件库：**画布 + 图层管理 + 图层树 + 基础绘制 + 在线地图服务预览 + 动态重投影**。

不依赖 QGIS。基于 **PyQt6 + rasterio + Shapely + pyproj**，可发布到 PyPI，供 QuickAnno、下载器、质检工具等复用。

## 能力概览

| 能力 | 说明 |
|------|------|
| 地图画布 | 平移、滚轮锚定缩放（鼠标下点不动）、可配置背景色（默认白色） |
| 动态重投影 | 可叠加不同坐标系数据；**视图 CRS 由第一个带 CRS 的图层决定** |
| 本地栅格 | GeoTIFF（rasterio）按视口窗口读取；跨 CRS 时 WarpedVRT 重投影 |
| 矢量标注 | 内存 Shapely 点/线/面 + 简单样式；渲染时按需重投影 |
| 图层树 | 勾选显隐；展开图例（矢量样式；栅格为 ArcMap 拉伸高/中/低或 RGB 波段；底图符号） |
| 绘制工具 | 平移 / 点 / 线 / 矩形 / 多边形（线、面双击结束，双击点为终点/顶点） |
| 切片底图 | 通用 XYZ/TMS；预设天地图、高德、百度、谷歌 |
| OGC WMS | `GetMap` 视口图像预览 |
| ArcGIS REST | MapServer / ImageServer `export`；亦可 XYZ `tile/{z}/{y}/{x}` |
| 超图 iServer | 地图 `image.png` 视口预览；亦可 `zxyTileImage` 瓦片 |

在线服务图层自身为 **EPSG:3857**，叠到其它视图 CRS 时会动态重投影（AABB 近似）。高德/百度存在国内加密坐标偏移，适合底图预览，不宜与精密测绘数据直接叠合量测。

## 安装

```bash
# 开发态（本仓库）
pip install -e .

# 发布后
pip install ymgis
```

## 快速开始

```python
from PyQt6.QtWidgets import QApplication, QMainWindow, QSplitter
from ymgis import MapCanvas, RasterLayer, VectorLayer, LayerTreeWidget, PanTool, DrawPolygonTool

app = QApplication([])
win = QMainWindow()
canvas = MapCanvas()
tree = LayerTreeWidget(canvas.layer_manager)

# 第一个带 CRS 的图层决定视图坐标系（无需手动 set_crs）
raster = RasterLayer(r"D:\data\demo.tif")
canvas.layer_manager.add_layer(raster)
canvas.zoom_to_full_extent()

# 标注层自动继承视图 CRS；绘制坐标为画布 CRS
annos = VectorLayer("标注")
canvas.layer_manager.add_layer(annos)

tool = DrawPolygonTool()
tool.polygon_drawn.connect(
    lambda g: annos.add_feature(g, {"class": "demo"}, source_crs=canvas.crs)
)
canvas.set_map_tool(tool)

split = QSplitter()
split.addWidget(tree)
split.addWidget(canvas)
win.setCentralWidget(split)
win.resize(1200, 800)
win.show()
app.exec()
```

### 多坐标系叠加

```python
from ymgis import MapCanvas, RasterLayer, VectorLayer, create_amap
from shapely.geometry import Point

canvas = MapCanvas()

# 先加载 CGCS2000 / 任意投影影像 → 视图 CRS = 该影像 CRS
canvas.layer_manager.add_layer(RasterLayer(r"D:\data\cgcs2000.tif"))

# 再叠加 WGS84 矢量 → 渲染时自动重投影到视图 CRS
pts = VectorLayer("点", crs="EPSG:4326")
pts.add_feature(Point(116.4, 39.9))
canvas.layer_manager.add_layer(pts)

# 在线底图（EPSG:3857）也会重投影到当前视图
canvas.layer_manager.add_layer(create_amap(), to_top=False)

canvas.zoom_to_full_extent()
# 如需强制指定视图 CRS（并锁定，不再被首层覆盖）：
# canvas.set_crs("EPSG:3857")
```

### 在线底图 / 地图服务预览

```python
from ymgis import (
    MapCanvas,
    create_amap,
    create_baidu,
    create_google,
    create_tianditu,
    WmsLayer,
    ArcGisRestLayer,
    SuperMapRestLayer,
)

canvas = MapCanvas()

canvas.layer_manager.add_layer(create_amap(), to_top=False)
# canvas.layer_manager.add_layer(create_baidu(), to_top=False)
# canvas.layer_manager.add_layer(create_google(), to_top=False)
# canvas.layer_manager.add_layer(create_tianditu("你的天地图tk"), to_top=False)

# WMS / ArcGIS / 超图
# canvas.layer_manager.add_layer(WmsLayer("https://host/wms", "workspace:layer"))
# canvas.layer_manager.add_layer(
#     ArcGisRestLayer("https://host/arcgis/rest/services/Demo/MapServer")
# )
# canvas.layer_manager.add_layer(
#     SuperMapRestLayer("http://host:8090/iserver/services/map-demo/rest/maps/Demo")
# )

canvas.zoom_to_full_extent()
```

自定义 XYZ：

```python
from ymgis import XyzTileLayer

layer = XyzTileLayer(
    "https://tile.example.com/{z}/{x}/{y}.png",
    name="自定义瓦片",
    subdomains=["a", "b", "c"],  # 对应模板 {s}
)
```

运行示例：

```bash
pip install -e .
python examples/basic_viewer.py
# 可选传入 GeoTIFF：
python examples/basic_viewer.py D:\data\demo.tif

# 在线底图 / WMS / ArcGIS / 超图预览：
python examples/online_basemap.py
# 天地图也可：set TIANDITU_TOKEN=你的tk
```

## 模块

| 模块 | 说明 |
|------|------|
| `MapCanvas` | 地图画布；首层定 CRS、动态重投影渲染 |
| `LayerManager` | 图层栈；`combined_extent(target_crs)` |
| `RasterLayer` | GeoTIFF 窗口读取 + WarpedVRT 重投影 |
| `VectorLayer` | 内存 Shapely 要素；可指定 `crs=` |
| `ymgis.crs` | `transform_xy` / `transform_extent` / `transform_geometry` |
| `XyzTileLayer` | 通用 XYZ/TMS 异步瓦片底图 |
| `BaiduTileLayer` / `create_*` | 百度及天地图/高德/谷歌预设 |
| `WmsLayer` | OGC WMS GetMap 视口预览 |
| `ArcGisRestLayer` | ArcGIS REST export 预览 |
| `SuperMapRestLayer` | 超图 iServer 地图预览 |
| `LayerTreeWidget` | 图层树 + 图例（`show_legend=True`） |
| `LegendItem` / `LegendSymbol` | 图例条目与符号类型 |
| `PanTool` / `DrawPointTool` / `DrawLineTool` / `DrawRectTool` / `DrawPolygonTool` | 交互工具 |

## 设计约定

- **视图 CRS**：由**第一个加载且带有 CRS 的图层**自动设定；`canvas.set_crs(...)` 可显式指定并锁定。
- **动态重投影**：后续图层 CRS 不同时，在渲染/`combined_extent` 时转换到视图 CRS（依赖 pyproj；栅格用 WarpedVRT）。
- 无 CRS 的图层（如空标注层）加入后自动继承当前视图 CRS；绘制工具产出的几何为画布 CRS。
- 图层树默认展开图例：矢量按点/线/面样式，栅格显示缩略图，在线底图/服务显示通用符号；`LayerTreeWidget(show_legend=False)` 可关闭。
- 在线切片/WMS 图层原生多为 **EPSG:3857**；瓦片异步加载并带内存缓存。
- 栅格按视口读取并缩放到控件尺寸，避免整图进内存。

## 与 QGIS / GeoD 的关系

- **不是** QGIS 替代品：无工程文件、无复杂符号、无拓扑规则。
- 可预览常见在线底图与 OGC/REST 地图服务，但**不是** GeoD 那种批量网图下载器。
- 目标：多个内部小工具共用同一套画布/图层树，安装包体积远小于捆绑 `qgis_runtime`。

## 发布到 PyPI

推荐使用脚本（Windows PowerShell）：

```powershell
# 升高版本：编辑 src/ymgis/__about__.py 中的 __version__（例如 "0.1.4"）

# 首次：复制并填写 token（不要提交 publish.local.ps1）
copy scripts\publish.local.ps1.example scripts\publish.local.ps1
# 编辑 scripts\publish.local.ps1，将 $PyPiToken 设为 pypi-... 开头的 API token

.\scripts\publish.ps1
# 或：.\scripts\publish.ps1 -Token "pypi-你的完整token"
# 测试库：.\scripts\publish.ps1 -TestPyPI
```

也可手动：

```bash
pip install -e ".[dev]"
python -m build
# Username=__token__  Password=pypi-...
twine upload dist/*
```

请先在 [PyPI](https://pypi.org) 确认包名 `ymgis` 可用；若冲突可改为 `yima-gis` 并同步修改 `pyproject.toml` 的 `name`。若已上传过同一版本，需先升高版本号（只改 `src/ymgis/__about__.py` 中的 `__version__`）。

## 目录

```
ymgis/
├── pyproject.toml
├── README.md
├── LICENSE
├── examples/
│   ├── basic_viewer.py
│   └── online_basemap.py
├── src/ymgis/
│   ├── crs.py          # 动态重投影
│   ├── canvas/
│   ├── layers/
│   │   └── services/   # XYZ / WMS / ArcGIS / 超图 / 厂商预设
│   ├── tools/
│   └── widgets/
└── tests/
```

## License

MIT
