taigeotrans 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.
- taigeotrans-0.1.0/LICENSE +21 -0
- taigeotrans-0.1.0/PKG-INFO +181 -0
- taigeotrans-0.1.0/README.md +128 -0
- taigeotrans-0.1.0/pyproject.toml +102 -0
- taigeotrans-0.1.0/setup.cfg +4 -0
- taigeotrans-0.1.0/src/taigeotrans/__init__.py +13 -0
- taigeotrans-0.1.0/src/taigeotrans/_cli_app.py +182 -0
- taigeotrans-0.1.0/src/taigeotrans/cli.py +36 -0
- taigeotrans-0.1.0/src/taigeotrans/core.py +327 -0
- taigeotrans-0.1.0/src/taigeotrans/models.py +117 -0
- taigeotrans-0.1.0/src/taigeotrans/providers/__init__.py +15 -0
- taigeotrans-0.1.0/src/taigeotrans/providers/tgos.py +268 -0
- taigeotrans-0.1.0/src/taigeotrans/py.typed +0 -0
- taigeotrans-0.1.0/src/taigeotrans/utils/__init__.py +5 -0
- taigeotrans-0.1.0/src/taigeotrans/utils/projection.py +150 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/PKG-INFO +181 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/SOURCES.txt +21 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/dependency_links.txt +1 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/entry_points.txt +2 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/requires.txt +35 -0
- taigeotrans-0.1.0/src/taigeotrans.egg-info/top_level.txt +1 -0
- taigeotrans-0.1.0/tests/test_tgos.py +74 -0
- taigeotrans-0.1.0/tests/test_transformer.py +255 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chien-Hsun Chang & TaiGeotrans Contributors
|
|
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,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taigeotrans
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Taiwan Geospatial Transformer - 台灣地址與座標轉換工具
|
|
5
|
+
Author: TaiGeotrans Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/KageRyo/TaiGeotrans
|
|
8
|
+
Project-URL: Repository, https://github.com/KageRyo/TaiGeotrans
|
|
9
|
+
Project-URL: Issues, https://github.com/KageRyo/TaiGeotrans/issues
|
|
10
|
+
Keywords: taiwan,geospatial,coordinate,transform,TWD97,WGS84,geocoding
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pyproj>=3.6.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Provides-Extra: geocoding
|
|
26
|
+
Requires-Dist: httpx>=0.27.0; extra == "geocoding"
|
|
27
|
+
Provides-Extra: dataframe
|
|
28
|
+
Requires-Dist: pandas>=2.0.0; extra == "dataframe"
|
|
29
|
+
Provides-Extra: progress
|
|
30
|
+
Requires-Dist: tqdm>=4.66.0; extra == "progress"
|
|
31
|
+
Provides-Extra: cli
|
|
32
|
+
Requires-Dist: httpx>=0.27.0; extra == "cli"
|
|
33
|
+
Requires-Dist: pandas>=2.0.0; extra == "cli"
|
|
34
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "cli"
|
|
35
|
+
Requires-Dist: rich>=13.0.0; extra == "cli"
|
|
36
|
+
Requires-Dist: tqdm>=4.66.0; extra == "cli"
|
|
37
|
+
Requires-Dist: typer>=0.12.0; extra == "cli"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: httpx>=0.27.0; extra == "all"
|
|
40
|
+
Requires-Dist: pandas>=2.0.0; extra == "all"
|
|
41
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "all"
|
|
42
|
+
Requires-Dist: rich>=13.0.0; extra == "all"
|
|
43
|
+
Requires-Dist: tqdm>=4.66.0; extra == "all"
|
|
44
|
+
Requires-Dist: typer>=0.12.0; extra == "all"
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
48
|
+
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
49
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
50
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
51
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
# TaiGeotrans
|
|
55
|
+
|
|
56
|
+
[](https://github.com/KageRyo/TaiGeotrans/actions/workflows/ci.yml)
|
|
57
|
+
[](https://pypi.org/project/taigeotrans/)
|
|
58
|
+
[](https://www.python.org/)
|
|
59
|
+
[](LICENSE)
|
|
60
|
+
|
|
61
|
+
TaiGeotrans 是一個台灣地址與座標轉換工具,支援:
|
|
62
|
+
|
|
63
|
+
- WGS84(EPSG:4326)與 TWD97(EPSG:3826)雙向轉換
|
|
64
|
+
- 透過 TGOS 將台灣地址轉成座標
|
|
65
|
+
- 批量處理、pandas DataFrame 與 GeoJSON
|
|
66
|
+
- 命令列操作
|
|
67
|
+
|
|
68
|
+
## 安裝
|
|
69
|
+
|
|
70
|
+
只使用座標轉換時:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m pip install taigeotrans
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
需要地址定位、DataFrame 或 CLI 時,再安裝對應功能:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m pip install "taigeotrans[geocoding]"
|
|
80
|
+
python -m pip install "taigeotrans[dataframe]"
|
|
81
|
+
python -m pip install "taigeotrans[cli]"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
也可以一次安裝全部功能:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m pip install "taigeotrans[all]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Python API
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from taigeotrans import TaiGeotrans
|
|
94
|
+
|
|
95
|
+
tg = TaiGeotrans()
|
|
96
|
+
|
|
97
|
+
# WGS84 -> TWD97
|
|
98
|
+
result = tg.transform_lonlat(121.5654, 25.0330)
|
|
99
|
+
print(result.twd97_x, result.twd97_y)
|
|
100
|
+
|
|
101
|
+
# TWD97 -> WGS84
|
|
102
|
+
result = tg.transform_twd97(303891.54, 2773226.67)
|
|
103
|
+
print(result.wgs84_lon, result.wgs84_lat)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 地址定位
|
|
107
|
+
|
|
108
|
+
地址定位使用 TGOS,需要 AppID 與 APIKey。請先在 TGOS 申請這兩項資料,
|
|
109
|
+
再透過環境變數提供:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
export TGOS_APP_ID="your-app-id"
|
|
113
|
+
export TGOS_API_KEY="your-api-key"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
或直接傳給 `TaiGeotrans`:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
tg = TaiGeotrans(
|
|
120
|
+
tgos_app_id="your-app-id",
|
|
121
|
+
tgos_api_key="your-api-key",
|
|
122
|
+
)
|
|
123
|
+
result = tg.geocode("臺北市中山區松江路469巷4號")
|
|
124
|
+
print(result.matched_address, result.twd97_x, result.twd97_y)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Python API 不會自動讀取 `.env`。CLI 會讀取目前工作目錄的 `.env`,可以複製
|
|
128
|
+
`.env.example` 作為範本;實際 key 不要提交到 Git。
|
|
129
|
+
|
|
130
|
+
### 批量處理
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
addresses = ["嘉義縣民雄鄉中樂路55號", "臺北市信義區市府路1號"]
|
|
134
|
+
coords = [(120.4278, 23.5521), (121.5654, 25.0330)]
|
|
135
|
+
|
|
136
|
+
address_results = tg.batch_geocode(addresses, show_progress=False)
|
|
137
|
+
coordinate_results = tg.batch_transform_lonlat(coords, show_progress=False)
|
|
138
|
+
df = tg.batch_transform_lonlat_to_dataframe(coords, show_progress=False)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
DataFrame 方法需要 `taigeotrans[dataframe]`。進度列使用 `tqdm`;沒有安裝時,
|
|
142
|
+
批量方法仍可執行,只是不顯示進度列。
|
|
143
|
+
|
|
144
|
+
## CLI
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python -m pip install "taigeotrans[cli]"
|
|
148
|
+
|
|
149
|
+
taigeotrans transform 121.5654 25.0330
|
|
150
|
+
taigeotrans transform 121.5654 25.0330 --format json
|
|
151
|
+
taigeotrans geocode "臺北市信義區市府路1號"
|
|
152
|
+
taigeotrans batch-geocode addresses.txt -o results.csv
|
|
153
|
+
taigeotrans batch-transform coords.csv -o results.csv
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`addresses.txt` 每行一個地址;`coords.csv` 需要包含 `lon,lat` 欄位。執行
|
|
157
|
+
`taigeotrans --help` 查看完整指令。
|
|
158
|
+
|
|
159
|
+
## 結果
|
|
160
|
+
|
|
161
|
+
每個操作都回傳 `GeocodeResult`,包含輸入值、WGS84/TWD97 座標、來源與狀態。
|
|
162
|
+
狀態可能是 `success`、`out_of_bounds`、`invalid` 或 `failed`。
|
|
163
|
+
|
|
164
|
+
## 開發
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python -m pip install -e ".[dev,all]"
|
|
168
|
+
python -m pytest
|
|
169
|
+
python -m ruff check src tests
|
|
170
|
+
python -m ruff format --check src tests
|
|
171
|
+
python -m mypy src
|
|
172
|
+
python -m build
|
|
173
|
+
python -m twine check dist/*
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
CI 會測試 Python 3.11–3.13。推送 `vMAJOR.MINOR.PATCH` tag 後,release workflow
|
|
177
|
+
會建置套件、發佈到 PyPI,並建立 GitHub Release。
|
|
178
|
+
|
|
179
|
+
## 授權
|
|
180
|
+
|
|
181
|
+
MIT License,請參考 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# TaiGeotrans
|
|
2
|
+
|
|
3
|
+
[](https://github.com/KageRyo/TaiGeotrans/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/taigeotrans/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
TaiGeotrans 是一個台灣地址與座標轉換工具,支援:
|
|
9
|
+
|
|
10
|
+
- WGS84(EPSG:4326)與 TWD97(EPSG:3826)雙向轉換
|
|
11
|
+
- 透過 TGOS 將台灣地址轉成座標
|
|
12
|
+
- 批量處理、pandas DataFrame 與 GeoJSON
|
|
13
|
+
- 命令列操作
|
|
14
|
+
|
|
15
|
+
## 安裝
|
|
16
|
+
|
|
17
|
+
只使用座標轉換時:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python -m pip install taigeotrans
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
需要地址定位、DataFrame 或 CLI 時,再安裝對應功能:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m pip install "taigeotrans[geocoding]"
|
|
27
|
+
python -m pip install "taigeotrans[dataframe]"
|
|
28
|
+
python -m pip install "taigeotrans[cli]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
也可以一次安裝全部功能:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m pip install "taigeotrans[all]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Python API
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from taigeotrans import TaiGeotrans
|
|
41
|
+
|
|
42
|
+
tg = TaiGeotrans()
|
|
43
|
+
|
|
44
|
+
# WGS84 -> TWD97
|
|
45
|
+
result = tg.transform_lonlat(121.5654, 25.0330)
|
|
46
|
+
print(result.twd97_x, result.twd97_y)
|
|
47
|
+
|
|
48
|
+
# TWD97 -> WGS84
|
|
49
|
+
result = tg.transform_twd97(303891.54, 2773226.67)
|
|
50
|
+
print(result.wgs84_lon, result.wgs84_lat)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 地址定位
|
|
54
|
+
|
|
55
|
+
地址定位使用 TGOS,需要 AppID 與 APIKey。請先在 TGOS 申請這兩項資料,
|
|
56
|
+
再透過環境變數提供:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
export TGOS_APP_ID="your-app-id"
|
|
60
|
+
export TGOS_API_KEY="your-api-key"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
或直接傳給 `TaiGeotrans`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
tg = TaiGeotrans(
|
|
67
|
+
tgos_app_id="your-app-id",
|
|
68
|
+
tgos_api_key="your-api-key",
|
|
69
|
+
)
|
|
70
|
+
result = tg.geocode("臺北市中山區松江路469巷4號")
|
|
71
|
+
print(result.matched_address, result.twd97_x, result.twd97_y)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Python API 不會自動讀取 `.env`。CLI 會讀取目前工作目錄的 `.env`,可以複製
|
|
75
|
+
`.env.example` 作為範本;實際 key 不要提交到 Git。
|
|
76
|
+
|
|
77
|
+
### 批量處理
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
addresses = ["嘉義縣民雄鄉中樂路55號", "臺北市信義區市府路1號"]
|
|
81
|
+
coords = [(120.4278, 23.5521), (121.5654, 25.0330)]
|
|
82
|
+
|
|
83
|
+
address_results = tg.batch_geocode(addresses, show_progress=False)
|
|
84
|
+
coordinate_results = tg.batch_transform_lonlat(coords, show_progress=False)
|
|
85
|
+
df = tg.batch_transform_lonlat_to_dataframe(coords, show_progress=False)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
DataFrame 方法需要 `taigeotrans[dataframe]`。進度列使用 `tqdm`;沒有安裝時,
|
|
89
|
+
批量方法仍可執行,只是不顯示進度列。
|
|
90
|
+
|
|
91
|
+
## CLI
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m pip install "taigeotrans[cli]"
|
|
95
|
+
|
|
96
|
+
taigeotrans transform 121.5654 25.0330
|
|
97
|
+
taigeotrans transform 121.5654 25.0330 --format json
|
|
98
|
+
taigeotrans geocode "臺北市信義區市府路1號"
|
|
99
|
+
taigeotrans batch-geocode addresses.txt -o results.csv
|
|
100
|
+
taigeotrans batch-transform coords.csv -o results.csv
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`addresses.txt` 每行一個地址;`coords.csv` 需要包含 `lon,lat` 欄位。執行
|
|
104
|
+
`taigeotrans --help` 查看完整指令。
|
|
105
|
+
|
|
106
|
+
## 結果
|
|
107
|
+
|
|
108
|
+
每個操作都回傳 `GeocodeResult`,包含輸入值、WGS84/TWD97 座標、來源與狀態。
|
|
109
|
+
狀態可能是 `success`、`out_of_bounds`、`invalid` 或 `failed`。
|
|
110
|
+
|
|
111
|
+
## 開發
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python -m pip install -e ".[dev,all]"
|
|
115
|
+
python -m pytest
|
|
116
|
+
python -m ruff check src tests
|
|
117
|
+
python -m ruff format --check src tests
|
|
118
|
+
python -m mypy src
|
|
119
|
+
python -m build
|
|
120
|
+
python -m twine check dist/*
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
CI 會測試 Python 3.11–3.13。推送 `vMAJOR.MINOR.PATCH` tag 後,release workflow
|
|
124
|
+
會建置套件、發佈到 PyPI,並建立 GitHub Release。
|
|
125
|
+
|
|
126
|
+
## 授權
|
|
127
|
+
|
|
128
|
+
MIT License,請參考 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "taigeotrans"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Taiwan Geospatial Transformer - 台灣地址與座標轉換工具"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "TaiGeotrans Contributors"}
|
|
15
|
+
]
|
|
16
|
+
keywords = ["taiwan", "geospatial", "coordinate", "transform", "TWD97", "WGS84", "geocoding"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"pyproj>=3.6.0",
|
|
30
|
+
"pydantic>=2.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
geocoding = [
|
|
35
|
+
"httpx>=0.27.0",
|
|
36
|
+
]
|
|
37
|
+
dataframe = [
|
|
38
|
+
"pandas>=2.0.0",
|
|
39
|
+
]
|
|
40
|
+
progress = [
|
|
41
|
+
"tqdm>=4.66.0",
|
|
42
|
+
]
|
|
43
|
+
cli = [
|
|
44
|
+
"httpx>=0.27.0",
|
|
45
|
+
"pandas>=2.0.0",
|
|
46
|
+
"python-dotenv>=1.0.0",
|
|
47
|
+
"rich>=13.0.0",
|
|
48
|
+
"tqdm>=4.66.0",
|
|
49
|
+
"typer>=0.12.0",
|
|
50
|
+
]
|
|
51
|
+
all = [
|
|
52
|
+
"httpx>=0.27.0",
|
|
53
|
+
"pandas>=2.0.0",
|
|
54
|
+
"python-dotenv>=1.0.0",
|
|
55
|
+
"rich>=13.0.0",
|
|
56
|
+
"tqdm>=4.66.0",
|
|
57
|
+
"typer>=0.12.0",
|
|
58
|
+
]
|
|
59
|
+
dev = [
|
|
60
|
+
"pytest>=8.0.0",
|
|
61
|
+
"pytest-cov>=4.1.0",
|
|
62
|
+
"build>=1.2.0",
|
|
63
|
+
"twine>=5.0.0",
|
|
64
|
+
"ruff>=0.3.0",
|
|
65
|
+
"mypy>=1.8.0",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.scripts]
|
|
69
|
+
taigeotrans = "taigeotrans.cli:main"
|
|
70
|
+
|
|
71
|
+
[project.urls]
|
|
72
|
+
Homepage = "https://github.com/KageRyo/TaiGeotrans"
|
|
73
|
+
Repository = "https://github.com/KageRyo/TaiGeotrans"
|
|
74
|
+
Issues = "https://github.com/KageRyo/TaiGeotrans/issues"
|
|
75
|
+
|
|
76
|
+
[tool.setuptools.packages.find]
|
|
77
|
+
where = ["src"]
|
|
78
|
+
|
|
79
|
+
[tool.setuptools.package-data]
|
|
80
|
+
taigeotrans = ["py.typed"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 100
|
|
84
|
+
target-version = "py311"
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
88
|
+
ignore = []
|
|
89
|
+
|
|
90
|
+
[tool.mypy]
|
|
91
|
+
python_version = "3.11"
|
|
92
|
+
plugins = ["pydantic.mypy"]
|
|
93
|
+
warn_return_any = true
|
|
94
|
+
warn_unused_configs = true
|
|
95
|
+
ignore_missing_imports = true
|
|
96
|
+
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
testpaths = ["tests"]
|
|
99
|
+
python_files = ["test_*.py"]
|
|
100
|
+
python_classes = ["Test*"]
|
|
101
|
+
python_functions = ["test_*"]
|
|
102
|
+
addopts = "-v --cov=taigeotrans --cov-report=term-missing"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TaiGeotrans - Taiwan Geospatial Transformer
|
|
3
|
+
台灣地址與座標轉換工具
|
|
4
|
+
|
|
5
|
+
基於內政地理資訊圖資雲整合服務平台 (TGOS) 實現的輕量級地址、
|
|
6
|
+
經緯度 (WGS84:EPSG:4326) 及 TWD97 (EPSG:3826) 轉換器。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from taigeotrans.core import TaiGeotrans
|
|
10
|
+
from taigeotrans.models import GeocodeResult, TransformStatus
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = ["TaiGeotrans", "GeocodeResult", "TransformStatus"]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Typer implementation for the optional TaiGeotrans CLI extra."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from taigeotrans.core import TaiGeotrans
|
|
14
|
+
from taigeotrans.models import GeocodeResult, TransformStatus
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="taigeotrans",
|
|
18
|
+
help="TaiGeotrans - Taiwan address and coordinate transformation tool",
|
|
19
|
+
add_completion=False,
|
|
20
|
+
)
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command()
|
|
25
|
+
def geocode(
|
|
26
|
+
address: str = typer.Argument(..., help="Taiwan address to geocode"),
|
|
27
|
+
output_format: str = typer.Option(
|
|
28
|
+
"table",
|
|
29
|
+
"--format",
|
|
30
|
+
"-f",
|
|
31
|
+
help="Output format: table, json, csv",
|
|
32
|
+
),
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Geocode a Taiwan address to TWD97 coordinates."""
|
|
35
|
+
result = TaiGeotrans().geocode(address)
|
|
36
|
+
_print_single_result(result, output_format)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def batch_geocode(
|
|
41
|
+
input_file: Path = typer.Argument(..., help="Input file with one address per line"),
|
|
42
|
+
output_file: Path | None = typer.Option(
|
|
43
|
+
None,
|
|
44
|
+
"--output",
|
|
45
|
+
"-o",
|
|
46
|
+
help="Output file path",
|
|
47
|
+
),
|
|
48
|
+
output_format: str = typer.Option(
|
|
49
|
+
"csv",
|
|
50
|
+
"--format",
|
|
51
|
+
"-f",
|
|
52
|
+
help="Output format: csv, json, geojson",
|
|
53
|
+
),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Geocode multiple Taiwan addresses."""
|
|
56
|
+
if not input_file.exists():
|
|
57
|
+
raise typer.BadParameter(f"File not found: {input_file}")
|
|
58
|
+
|
|
59
|
+
addresses = [
|
|
60
|
+
line.strip() for line in input_file.read_text(encoding="utf-8").splitlines() if line.strip()
|
|
61
|
+
]
|
|
62
|
+
if not addresses:
|
|
63
|
+
console.print("[yellow]No valid addresses in file[/yellow]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
converter = TaiGeotrans()
|
|
67
|
+
results = converter.batch_geocode(addresses, show_progress=True)
|
|
68
|
+
_save_batch_results(results, output_file, output_format, converter)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command()
|
|
72
|
+
def transform(
|
|
73
|
+
lon: float = typer.Argument(..., help="WGS84 longitude"),
|
|
74
|
+
lat: float = typer.Argument(..., help="WGS84 latitude"),
|
|
75
|
+
output_format: str = typer.Option(
|
|
76
|
+
"table",
|
|
77
|
+
"--format",
|
|
78
|
+
"-f",
|
|
79
|
+
help="Output format: table, json, csv",
|
|
80
|
+
),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Transform WGS84 coordinates to TWD97."""
|
|
83
|
+
result = TaiGeotrans().transform_lonlat(lon, lat)
|
|
84
|
+
_print_single_result(result, output_format)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def batch_transform(
|
|
89
|
+
input_file: Path = typer.Argument(..., help="CSV file with lon and lat columns"),
|
|
90
|
+
output_file: Path | None = typer.Option(
|
|
91
|
+
None,
|
|
92
|
+
"--output",
|
|
93
|
+
"-o",
|
|
94
|
+
help="Output file path",
|
|
95
|
+
),
|
|
96
|
+
output_format: str = typer.Option(
|
|
97
|
+
"csv",
|
|
98
|
+
"--format",
|
|
99
|
+
"-f",
|
|
100
|
+
help="Output format: csv, json, geojson",
|
|
101
|
+
),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Transform multiple WGS84 coordinate pairs."""
|
|
104
|
+
if not input_file.exists():
|
|
105
|
+
raise typer.BadParameter(f"File not found: {input_file}")
|
|
106
|
+
|
|
107
|
+
df = pd.read_csv(input_file)
|
|
108
|
+
if "lon" not in df.columns or "lat" not in df.columns:
|
|
109
|
+
raise typer.BadParameter("CSV file must contain 'lon' and 'lat' columns")
|
|
110
|
+
|
|
111
|
+
coords = [(row.lon, row.lat) for row in df.itertuples(index=False)]
|
|
112
|
+
converter = TaiGeotrans()
|
|
113
|
+
results = converter.batch_transform_lonlat(coords, show_progress=True)
|
|
114
|
+
_save_batch_results(results, output_file, output_format, converter)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
def version() -> None:
|
|
119
|
+
"""Display version information."""
|
|
120
|
+
from taigeotrans import __version__
|
|
121
|
+
|
|
122
|
+
console.print(f"TaiGeotrans version {__version__}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _print_single_result(result: GeocodeResult, output_format: str) -> None:
|
|
126
|
+
if output_format == "json":
|
|
127
|
+
console.print_json(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
|
|
128
|
+
elif output_format == "csv":
|
|
129
|
+
frame = pd.DataFrame([result.to_dict()])
|
|
130
|
+
console.print(frame.to_csv(index=False))
|
|
131
|
+
elif output_format == "table":
|
|
132
|
+
table = Table(title="Transformation Result")
|
|
133
|
+
for key, value in result.to_dict().items():
|
|
134
|
+
table.add_row(key, str(value))
|
|
135
|
+
console.print(table)
|
|
136
|
+
_print_status(result)
|
|
137
|
+
else:
|
|
138
|
+
raise typer.BadParameter(f"Unsupported output format: {output_format}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _print_status(result: GeocodeResult) -> None:
|
|
142
|
+
if result.status == TransformStatus.SUCCESS:
|
|
143
|
+
console.print("[green]Success[/green]")
|
|
144
|
+
elif result.status == TransformStatus.OUT_OF_BOUNDS:
|
|
145
|
+
console.print("[yellow]Warning: coordinates are outside Taiwan mainland bounds[/yellow]")
|
|
146
|
+
else:
|
|
147
|
+
console.print(f"[red]Failed: {result.error_message}[/red]")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _save_batch_results(
|
|
151
|
+
results: list[GeocodeResult],
|
|
152
|
+
output_file: Path | None,
|
|
153
|
+
output_format: str,
|
|
154
|
+
converter: TaiGeotrans,
|
|
155
|
+
) -> None:
|
|
156
|
+
if output_file is None:
|
|
157
|
+
for result in results[:10]:
|
|
158
|
+
console.print_json(json.dumps(result.to_dict(), ensure_ascii=False))
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if output_format == "json":
|
|
162
|
+
output_file.write_text(
|
|
163
|
+
json.dumps([result.to_dict() for result in results], ensure_ascii=False, indent=2),
|
|
164
|
+
encoding="utf-8",
|
|
165
|
+
)
|
|
166
|
+
elif output_format == "geojson":
|
|
167
|
+
output_file.write_text(
|
|
168
|
+
json.dumps(
|
|
169
|
+
converter.to_geojson(results, filter_failed=False), ensure_ascii=False, indent=2
|
|
170
|
+
),
|
|
171
|
+
encoding="utf-8",
|
|
172
|
+
)
|
|
173
|
+
elif output_format == "csv":
|
|
174
|
+
pd.DataFrame([result.to_dict() for result in results]).to_csv(
|
|
175
|
+
output_file,
|
|
176
|
+
index=False,
|
|
177
|
+
encoding="utf-8-sig",
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
raise typer.BadParameter(f"Unsupported output format: {output_format}")
|
|
181
|
+
|
|
182
|
+
console.print(f"[green]Saved to {output_file} ({output_format})[/green]")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Command-line entry point with an actionable optional-dependency error."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> int:
|
|
9
|
+
"""Run the Typer CLI and return a process exit code."""
|
|
10
|
+
try:
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
|
|
13
|
+
load_dotenv()
|
|
14
|
+
from taigeotrans._cli_app import app
|
|
15
|
+
except ImportError as exc:
|
|
16
|
+
print(
|
|
17
|
+
"The CLI requires optional dependencies. Install them with "
|
|
18
|
+
"`python -m pip install 'taigeotrans[cli]'`.",
|
|
19
|
+
file=sys.stderr,
|
|
20
|
+
)
|
|
21
|
+
print(f"Missing dependency: {exc}", file=sys.stderr)
|
|
22
|
+
return 1
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
app()
|
|
26
|
+
except KeyboardInterrupt:
|
|
27
|
+
print("\nInterrupted", file=sys.stderr)
|
|
28
|
+
return 130
|
|
29
|
+
except Exception as exc:
|
|
30
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
31
|
+
return 1
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
raise SystemExit(main())
|