koreacoord 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.
- koreacoord-0.1.0/.gitignore +8 -0
- koreacoord-0.1.0/LICENSE +21 -0
- koreacoord-0.1.0/PKG-INFO +206 -0
- koreacoord-0.1.0/README.md +179 -0
- koreacoord-0.1.0/pyproject.toml +64 -0
- koreacoord-0.1.0/src/koreacoord/__init__.py +26 -0
- koreacoord-0.1.0/src/koreacoord/_congnamul.py +78 -0
- koreacoord-0.1.0/src/koreacoord/_datum.py +95 -0
- koreacoord-0.1.0/src/koreacoord/_params.py +68 -0
- koreacoord-0.1.0/src/koreacoord/_projection.py +200 -0
- koreacoord-0.1.0/src/koreacoord/core.py +153 -0
- koreacoord-0.1.0/src/koreacoord/py.typed +0 -0
- koreacoord-0.1.0/src/koreacoord/systems.py +70 -0
- koreacoord-0.1.0/tests/test_transform.py +246 -0
koreacoord-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AngLaboratory
|
|
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,206 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: koreacoord
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert between Korean coordinate systems: TM, KTM, UTM 52N, Congnamul, WGS84, Bessel
|
|
5
|
+
Project-URL: Homepage, https://github.com/AngLaboratory/koreacoord
|
|
6
|
+
Project-URL: Source, https://github.com/AngLaboratory/koreacoord
|
|
7
|
+
Project-URL: Issues, https://github.com/AngLaboratory/koreacoord/issues
|
|
8
|
+
Author-email: AngLaboratory <anglaboratory@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bessel,congnamul,coordinate,coordinate-system,geodesy,gis,korea,ktm,tm,utm,wgs84,좌표계,좌표변환
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Natural Language :: Korean
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# koreacoord
|
|
29
|
+
|
|
30
|
+
[](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml)
|
|
31
|
+
[](https://pypi.org/project/koreacoord/)
|
|
32
|
+
[](https://pypi.org/project/koreacoord/)
|
|
33
|
+
|
|
34
|
+
Convert between the coordinate systems used on Korean maps — TM, KTM, UTM 52N,
|
|
35
|
+
Kakao's Congnamul grid, WGS84 and Bessel/Tokyo — with no dependencies.
|
|
36
|
+
|
|
37
|
+
한국 지도에서 쓰이는 좌표계(TM, KTM, UTM 52N, 카카오 콩나물 좌표, WGS84, 베셀)를
|
|
38
|
+
서로 변환합니다. 외부 의존성이 없습니다.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install koreacoord
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from koreacoord import transform
|
|
50
|
+
|
|
51
|
+
# Seoul City Hall, WGS84 -> Kakao's Congnamul grid
|
|
52
|
+
x, y = transform(126.9876757, 37.5611523, "WGS84", "WCONGNAMUL")
|
|
53
|
+
|
|
54
|
+
# Enum members work too, and are what your IDE will autocomplete
|
|
55
|
+
from koreacoord import CoordSystem as CS
|
|
56
|
+
|
|
57
|
+
x, y = transform(126.9876757, 37.5611523, CS.WGS84, CS.UTM)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The axis order is always **x then y** — longitude before latitude for geographic
|
|
61
|
+
systems, easting before northing for projected ones.
|
|
62
|
+
|
|
63
|
+
## Supported systems
|
|
64
|
+
|
|
65
|
+
| Name | Description | Datum |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `WGS84` | Geographic longitude/latitude | WGS84 |
|
|
68
|
+
| `BESSEL` | Geographic longitude/latitude | Bessel 1841 (Tokyo) |
|
|
69
|
+
| `TM` | Transverse Mercator, central belt origin | Bessel 1841 |
|
|
70
|
+
| `WTM` | Transverse Mercator, central belt origin | WGS84 |
|
|
71
|
+
| `KTM` | Transverse Mercator, 128°E origin | Bessel 1841 |
|
|
72
|
+
| `WKTM` | Transverse Mercator, 128°E origin | WGS84 |
|
|
73
|
+
| `UTM` | UTM **zone 52N only** | WGS84 |
|
|
74
|
+
| `CONGNAMUL` | Kakao(Daum) map grid | Bessel 1841 |
|
|
75
|
+
| `WCONGNAMUL` | Kakao(Daum) map grid | WGS84 |
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from koreacoord import supported_systems
|
|
79
|
+
|
|
80
|
+
supported_systems()
|
|
81
|
+
# ['TM', 'WTM', 'CONGNAMUL', 'WCONGNAMUL', 'KTM', 'WKTM', 'UTM', 'WGS84', 'BESSEL']
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Scope
|
|
85
|
+
|
|
86
|
+
**This library is for Korea.** Every projection origin and datum parameter is
|
|
87
|
+
fixed to Korean values:
|
|
88
|
+
|
|
89
|
+
- `UTM` is hard-wired to **zone 52N** (central meridian 129°E). Coordinates from
|
|
90
|
+
another zone will be silently wrong.
|
|
91
|
+
- `BESSEL` uses the Tokyo-datum shift published for the Korean peninsula, so it
|
|
92
|
+
is not a general Bessel 1841 conversion.
|
|
93
|
+
- `CONGNAMUL` relocates several island regions (Jeju, Ulleung, Dokdo) the way
|
|
94
|
+
Kakao's maps draw them. `WCONGNAMUL` does not — it only applies the 2.5×
|
|
95
|
+
scale, matching the reference implementation.
|
|
96
|
+
|
|
97
|
+
Congnamul results are returned as integers; every other system returns floats.
|
|
98
|
+
|
|
99
|
+
## API
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
transform(x, y, from_system, to_system) -> tuple[float, float]
|
|
103
|
+
supported_systems() -> list[str]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`transform` raises `ValueError` for an unknown system name and `TypeError` if
|
|
107
|
+
the coordinates are not numbers.
|
|
108
|
+
|
|
109
|
+
The original keyword-only form is kept as an alias:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from koreacoord import get_trans_coord
|
|
113
|
+
|
|
114
|
+
x, y = get_trans_coord(x=126.9876757, y=37.5611523,
|
|
115
|
+
input="WGS84", output="WCONGNAMUL")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Accuracy
|
|
119
|
+
|
|
120
|
+
Measured by the test suite, across seven points from Mokpo to Dokdo:
|
|
121
|
+
|
|
122
|
+
| | Agreement |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| TM projection vs. [pyproj](https://pyproj4.github.io/pyproj/) | within 0.04 mm |
|
|
125
|
+
| Round trip within one datum | within 0.03 mm |
|
|
126
|
+
| Round trip across datums | a few mm |
|
|
127
|
+
| Congnamul grid | ±0.2 m (the grid is integer) |
|
|
128
|
+
|
|
129
|
+
These are numbers about *self-consistency*, not about surveying truth. The
|
|
130
|
+
datum shift is a 7-parameter Molodensky-Badekas approximation, so against
|
|
131
|
+
official Korean survey data the realistic expectation is **metre-level**
|
|
132
|
+
agreement, not centimetres.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
# 한국어
|
|
137
|
+
|
|
138
|
+
## 설치
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install koreacoord
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 사용법
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from koreacoord import transform
|
|
148
|
+
|
|
149
|
+
# 서울시청, WGS84 -> 카카오 콩나물 좌표
|
|
150
|
+
x, y = transform(126.9876757, 37.5611523, "WGS84", "WCONGNAMUL")
|
|
151
|
+
|
|
152
|
+
# 문자열 대신 열거형을 써도 됩니다 (IDE 자동완성이 됩니다)
|
|
153
|
+
from koreacoord import CoordSystem as CS
|
|
154
|
+
|
|
155
|
+
x, y = transform(126.9876757, 37.5611523, CS.WGS84, CS.UTM)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
축 순서는 항상 **x 먼저, y 나중**입니다. 경위도계는 경도→위도, 평면좌표계는
|
|
159
|
+
동거(easting)→북거(northing) 순서입니다.
|
|
160
|
+
|
|
161
|
+
## 지원 좌표계
|
|
162
|
+
|
|
163
|
+
| 이름 | 설명 | 측지계 |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| `WGS84` | 경위도 | WGS84 |
|
|
166
|
+
| `BESSEL` | 경위도 | 베셀 1841 (도쿄측지계) |
|
|
167
|
+
| `TM` | 중부원점 TM | 베셀 1841 |
|
|
168
|
+
| `WTM` | 중부원점 TM | WGS84 |
|
|
169
|
+
| `KTM` | 경도 128° 원점 TM | 베셀 1841 |
|
|
170
|
+
| `WKTM` | 경도 128° 원점 TM | WGS84 |
|
|
171
|
+
| `UTM` | UTM **52N 존 전용** | WGS84 |
|
|
172
|
+
| `CONGNAMUL` | 카카오(다음) 지도 좌표 | 베셀 1841 |
|
|
173
|
+
| `WCONGNAMUL` | 카카오(다음) 지도 좌표 | WGS84 |
|
|
174
|
+
|
|
175
|
+
## 사용 범위
|
|
176
|
+
|
|
177
|
+
**이 라이브러리는 한국 전용입니다.** 투영 원점과 측지계 파라미터가 전부 한국
|
|
178
|
+
값으로 고정되어 있습니다.
|
|
179
|
+
|
|
180
|
+
- `UTM`은 **52N 존(중앙자오선 129°E)** 으로 고정입니다. 다른 존의 좌표를 넣으면
|
|
181
|
+
오류 없이 틀린 값이 나옵니다.
|
|
182
|
+
- `BESSEL`은 한반도용 도쿄측지계 변환값을 쓰므로 일반적인 베셀 1841 변환이
|
|
183
|
+
아닙니다.
|
|
184
|
+
- `CONGNAMUL`은 카카오 지도가 제주·울릉·독도를 옮겨 그리는 보정을 반영합니다.
|
|
185
|
+
`WCONGNAMUL`은 이 보정 없이 2.5배 배율만 적용합니다 (원 구현과 동일).
|
|
186
|
+
|
|
187
|
+
콩나물 좌표는 정수로, 나머지는 실수로 반환됩니다.
|
|
188
|
+
|
|
189
|
+
## 정확도
|
|
190
|
+
|
|
191
|
+
목포부터 독도까지 7개 지점으로 측정한 값입니다.
|
|
192
|
+
|
|
193
|
+
| | 일치 수준 |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| TM 투영 vs [pyproj](https://pyproj4.github.io/pyproj/) | 0.04mm 이내 |
|
|
196
|
+
| 같은 측지계 내 왕복 변환 | 0.03mm 이내 |
|
|
197
|
+
| 측지계를 넘는 왕복 변환 | 수 mm |
|
|
198
|
+
| 콩나물 좌표 | ±0.2m (격자가 정수) |
|
|
199
|
+
|
|
200
|
+
이건 **자체 정합성** 수치이지 측량 성과와의 일치도가 아닙니다. 측지계 변환이
|
|
201
|
+
7-파라미터 Molodensky-Badekas 근사라서, 국토지리정보원 성과와 비교하면
|
|
202
|
+
센티미터가 아니라 **미터 단위**로 맞는다고 보는 게 현실적입니다.
|
|
203
|
+
|
|
204
|
+
## 라이선스
|
|
205
|
+
|
|
206
|
+
MIT
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# koreacoord
|
|
2
|
+
|
|
3
|
+
[](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/koreacoord/)
|
|
5
|
+
[](https://pypi.org/project/koreacoord/)
|
|
6
|
+
|
|
7
|
+
Convert between the coordinate systems used on Korean maps — TM, KTM, UTM 52N,
|
|
8
|
+
Kakao's Congnamul grid, WGS84 and Bessel/Tokyo — with no dependencies.
|
|
9
|
+
|
|
10
|
+
한국 지도에서 쓰이는 좌표계(TM, KTM, UTM 52N, 카카오 콩나물 좌표, WGS84, 베셀)를
|
|
11
|
+
서로 변환합니다. 외부 의존성이 없습니다.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install koreacoord
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from koreacoord import transform
|
|
23
|
+
|
|
24
|
+
# Seoul City Hall, WGS84 -> Kakao's Congnamul grid
|
|
25
|
+
x, y = transform(126.9876757, 37.5611523, "WGS84", "WCONGNAMUL")
|
|
26
|
+
|
|
27
|
+
# Enum members work too, and are what your IDE will autocomplete
|
|
28
|
+
from koreacoord import CoordSystem as CS
|
|
29
|
+
|
|
30
|
+
x, y = transform(126.9876757, 37.5611523, CS.WGS84, CS.UTM)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The axis order is always **x then y** — longitude before latitude for geographic
|
|
34
|
+
systems, easting before northing for projected ones.
|
|
35
|
+
|
|
36
|
+
## Supported systems
|
|
37
|
+
|
|
38
|
+
| Name | Description | Datum |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `WGS84` | Geographic longitude/latitude | WGS84 |
|
|
41
|
+
| `BESSEL` | Geographic longitude/latitude | Bessel 1841 (Tokyo) |
|
|
42
|
+
| `TM` | Transverse Mercator, central belt origin | Bessel 1841 |
|
|
43
|
+
| `WTM` | Transverse Mercator, central belt origin | WGS84 |
|
|
44
|
+
| `KTM` | Transverse Mercator, 128°E origin | Bessel 1841 |
|
|
45
|
+
| `WKTM` | Transverse Mercator, 128°E origin | WGS84 |
|
|
46
|
+
| `UTM` | UTM **zone 52N only** | WGS84 |
|
|
47
|
+
| `CONGNAMUL` | Kakao(Daum) map grid | Bessel 1841 |
|
|
48
|
+
| `WCONGNAMUL` | Kakao(Daum) map grid | WGS84 |
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from koreacoord import supported_systems
|
|
52
|
+
|
|
53
|
+
supported_systems()
|
|
54
|
+
# ['TM', 'WTM', 'CONGNAMUL', 'WCONGNAMUL', 'KTM', 'WKTM', 'UTM', 'WGS84', 'BESSEL']
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Scope
|
|
58
|
+
|
|
59
|
+
**This library is for Korea.** Every projection origin and datum parameter is
|
|
60
|
+
fixed to Korean values:
|
|
61
|
+
|
|
62
|
+
- `UTM` is hard-wired to **zone 52N** (central meridian 129°E). Coordinates from
|
|
63
|
+
another zone will be silently wrong.
|
|
64
|
+
- `BESSEL` uses the Tokyo-datum shift published for the Korean peninsula, so it
|
|
65
|
+
is not a general Bessel 1841 conversion.
|
|
66
|
+
- `CONGNAMUL` relocates several island regions (Jeju, Ulleung, Dokdo) the way
|
|
67
|
+
Kakao's maps draw them. `WCONGNAMUL` does not — it only applies the 2.5×
|
|
68
|
+
scale, matching the reference implementation.
|
|
69
|
+
|
|
70
|
+
Congnamul results are returned as integers; every other system returns floats.
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
transform(x, y, from_system, to_system) -> tuple[float, float]
|
|
76
|
+
supported_systems() -> list[str]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`transform` raises `ValueError` for an unknown system name and `TypeError` if
|
|
80
|
+
the coordinates are not numbers.
|
|
81
|
+
|
|
82
|
+
The original keyword-only form is kept as an alias:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from koreacoord import get_trans_coord
|
|
86
|
+
|
|
87
|
+
x, y = get_trans_coord(x=126.9876757, y=37.5611523,
|
|
88
|
+
input="WGS84", output="WCONGNAMUL")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Accuracy
|
|
92
|
+
|
|
93
|
+
Measured by the test suite, across seven points from Mokpo to Dokdo:
|
|
94
|
+
|
|
95
|
+
| | Agreement |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| TM projection vs. [pyproj](https://pyproj4.github.io/pyproj/) | within 0.04 mm |
|
|
98
|
+
| Round trip within one datum | within 0.03 mm |
|
|
99
|
+
| Round trip across datums | a few mm |
|
|
100
|
+
| Congnamul grid | ±0.2 m (the grid is integer) |
|
|
101
|
+
|
|
102
|
+
These are numbers about *self-consistency*, not about surveying truth. The
|
|
103
|
+
datum shift is a 7-parameter Molodensky-Badekas approximation, so against
|
|
104
|
+
official Korean survey data the realistic expectation is **metre-level**
|
|
105
|
+
agreement, not centimetres.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
# 한국어
|
|
110
|
+
|
|
111
|
+
## 설치
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install koreacoord
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 사용법
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from koreacoord import transform
|
|
121
|
+
|
|
122
|
+
# 서울시청, WGS84 -> 카카오 콩나물 좌표
|
|
123
|
+
x, y = transform(126.9876757, 37.5611523, "WGS84", "WCONGNAMUL")
|
|
124
|
+
|
|
125
|
+
# 문자열 대신 열거형을 써도 됩니다 (IDE 자동완성이 됩니다)
|
|
126
|
+
from koreacoord import CoordSystem as CS
|
|
127
|
+
|
|
128
|
+
x, y = transform(126.9876757, 37.5611523, CS.WGS84, CS.UTM)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
축 순서는 항상 **x 먼저, y 나중**입니다. 경위도계는 경도→위도, 평면좌표계는
|
|
132
|
+
동거(easting)→북거(northing) 순서입니다.
|
|
133
|
+
|
|
134
|
+
## 지원 좌표계
|
|
135
|
+
|
|
136
|
+
| 이름 | 설명 | 측지계 |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `WGS84` | 경위도 | WGS84 |
|
|
139
|
+
| `BESSEL` | 경위도 | 베셀 1841 (도쿄측지계) |
|
|
140
|
+
| `TM` | 중부원점 TM | 베셀 1841 |
|
|
141
|
+
| `WTM` | 중부원점 TM | WGS84 |
|
|
142
|
+
| `KTM` | 경도 128° 원점 TM | 베셀 1841 |
|
|
143
|
+
| `WKTM` | 경도 128° 원점 TM | WGS84 |
|
|
144
|
+
| `UTM` | UTM **52N 존 전용** | WGS84 |
|
|
145
|
+
| `CONGNAMUL` | 카카오(다음) 지도 좌표 | 베셀 1841 |
|
|
146
|
+
| `WCONGNAMUL` | 카카오(다음) 지도 좌표 | WGS84 |
|
|
147
|
+
|
|
148
|
+
## 사용 범위
|
|
149
|
+
|
|
150
|
+
**이 라이브러리는 한국 전용입니다.** 투영 원점과 측지계 파라미터가 전부 한국
|
|
151
|
+
값으로 고정되어 있습니다.
|
|
152
|
+
|
|
153
|
+
- `UTM`은 **52N 존(중앙자오선 129°E)** 으로 고정입니다. 다른 존의 좌표를 넣으면
|
|
154
|
+
오류 없이 틀린 값이 나옵니다.
|
|
155
|
+
- `BESSEL`은 한반도용 도쿄측지계 변환값을 쓰므로 일반적인 베셀 1841 변환이
|
|
156
|
+
아닙니다.
|
|
157
|
+
- `CONGNAMUL`은 카카오 지도가 제주·울릉·독도를 옮겨 그리는 보정을 반영합니다.
|
|
158
|
+
`WCONGNAMUL`은 이 보정 없이 2.5배 배율만 적용합니다 (원 구현과 동일).
|
|
159
|
+
|
|
160
|
+
콩나물 좌표는 정수로, 나머지는 실수로 반환됩니다.
|
|
161
|
+
|
|
162
|
+
## 정확도
|
|
163
|
+
|
|
164
|
+
목포부터 독도까지 7개 지점으로 측정한 값입니다.
|
|
165
|
+
|
|
166
|
+
| | 일치 수준 |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| TM 투영 vs [pyproj](https://pyproj4.github.io/pyproj/) | 0.04mm 이내 |
|
|
169
|
+
| 같은 측지계 내 왕복 변환 | 0.03mm 이내 |
|
|
170
|
+
| 측지계를 넘는 왕복 변환 | 수 mm |
|
|
171
|
+
| 콩나물 좌표 | ±0.2m (격자가 정수) |
|
|
172
|
+
|
|
173
|
+
이건 **자체 정합성** 수치이지 측량 성과와의 일치도가 아닙니다. 측지계 변환이
|
|
174
|
+
7-파라미터 Molodensky-Badekas 근사라서, 국토지리정보원 성과와 비교하면
|
|
175
|
+
센티미터가 아니라 **미터 단위**로 맞는다고 보는 게 현실적입니다.
|
|
176
|
+
|
|
177
|
+
## 라이선스
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "koreacoord"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Convert between Korean coordinate systems: TM, KTM, UTM 52N, Congnamul, WGS84, Bessel"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "AngLaboratory", email = "anglaboratory@gmail.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"coordinate",
|
|
16
|
+
"coordinate-system",
|
|
17
|
+
"korea",
|
|
18
|
+
"gis",
|
|
19
|
+
"geodesy",
|
|
20
|
+
"tm",
|
|
21
|
+
"ktm",
|
|
22
|
+
"utm",
|
|
23
|
+
"congnamul",
|
|
24
|
+
"wgs84",
|
|
25
|
+
"bessel",
|
|
26
|
+
"좌표계",
|
|
27
|
+
"좌표변환",
|
|
28
|
+
]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Development Status :: 4 - Beta",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"Intended Audience :: Science/Research",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.9",
|
|
36
|
+
"Programming Language :: Python :: 3.10",
|
|
37
|
+
"Programming Language :: Python :: 3.11",
|
|
38
|
+
"Programming Language :: Python :: 3.12",
|
|
39
|
+
"Programming Language :: Python :: 3.13",
|
|
40
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
41
|
+
"Typing :: Typed",
|
|
42
|
+
"Natural Language :: Korean",
|
|
43
|
+
]
|
|
44
|
+
dependencies = []
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/AngLaboratory/koreacoord"
|
|
48
|
+
Source = "https://github.com/AngLaboratory/koreacoord"
|
|
49
|
+
Issues = "https://github.com/AngLaboratory/koreacoord/issues"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8.0",
|
|
54
|
+
"pyproj>=3.6",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/koreacoord"]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.sdist]
|
|
61
|
+
include = ["src/", "tests/", "README.md", "LICENSE"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Convert between Korean coordinate systems. / 한국 좌표계 간 변환.
|
|
2
|
+
|
|
3
|
+
>>> from koreacoord import transform
|
|
4
|
+
>>> transform(126.9876757, 37.5611523, "WGS84", "WCONGNAMUL")
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ._params import BESSEL_1841, WGS84_ELLIPSOID, Ellipsoid, TMParams
|
|
10
|
+
from .core import get_trans_coord, map_type, supported_systems, transform
|
|
11
|
+
from .systems import CoordSystem
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CoordSystem",
|
|
17
|
+
"transform",
|
|
18
|
+
"supported_systems",
|
|
19
|
+
"get_trans_coord",
|
|
20
|
+
"map_type",
|
|
21
|
+
"Ellipsoid",
|
|
22
|
+
"TMParams",
|
|
23
|
+
"BESSEL_1841",
|
|
24
|
+
"WGS84_ELLIPSOID",
|
|
25
|
+
"__version__",
|
|
26
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Congnamul grid used by Kakao(Daum) maps. / 카카오(다음) 지도의 콩나물 좌표."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
SCALE = 2.5
|
|
9
|
+
|
|
10
|
+
# 지도상 별도 위치로 옮겨 그리는 도서 지역(제주·울릉·독도 등)의 TM 영역과 보정량.
|
|
11
|
+
# (x, y, width, height), 단위 m
|
|
12
|
+
_ISLAND_RECTS = (
|
|
13
|
+
(112500.0, -50000.0, 33500.0, 53000.0),
|
|
14
|
+
(146000.0, -50000.0, 54000.0, 58600.0),
|
|
15
|
+
(130000.0, 44000.0, 15000.0, 14000.0),
|
|
16
|
+
(532500.0, 437500.0, 25000.0, 25000.0),
|
|
17
|
+
(625000.0, 412500.0, 25000.0, 25000.0),
|
|
18
|
+
(-12500.0, 462500.0, 17500.0, 50000.0),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_SHIFT_TO_GRID = (
|
|
22
|
+
(0.0, 50000.0),
|
|
23
|
+
(0.0, 50000.0),
|
|
24
|
+
(0.0, 10000.0),
|
|
25
|
+
(-70378.0, -136.0),
|
|
26
|
+
(-144738.0, -2161.0),
|
|
27
|
+
(23510.0, -111.0),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
_SHIFT_TO_TM = tuple((-dx, -dy) for dx, dy in _SHIFT_TO_GRID)
|
|
31
|
+
|
|
32
|
+
# 되돌릴 때는 이미 옮겨진 좌표가 들어오므로, 사각형도 같이 옮겨 놓고 판정한다.
|
|
33
|
+
_ISLAND_RECTS_MOVED = tuple(
|
|
34
|
+
(rx + dx, ry + dy, rw, rh)
|
|
35
|
+
for (rx, ry, rw, rh), (dx, dy) in zip(_ISLAND_RECTS, _SHIFT_TO_GRID)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _round_half_up(value: float) -> int:
|
|
40
|
+
return math.floor(value + 0.5)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _island_offset(
|
|
44
|
+
x: float,
|
|
45
|
+
y: float,
|
|
46
|
+
rects: Tuple[Tuple[float, float, float, float], ...],
|
|
47
|
+
shifts: Tuple[Tuple[float, float], ...],
|
|
48
|
+
) -> Tuple[float, float]:
|
|
49
|
+
for (rx, ry, rw, rh), shift in zip(rects, shifts):
|
|
50
|
+
if rx <= x <= rx + rw and ry <= y <= ry + rh:
|
|
51
|
+
return shift
|
|
52
|
+
return 0.0, 0.0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def tm_to_grid(easting: float, northing: float) -> Tuple[int, int]:
|
|
56
|
+
"""Scale TM meters onto the grid. / TM 평면좌표에 배율만 적용."""
|
|
57
|
+
return _round_half_up(easting * SCALE), _round_half_up(northing * SCALE)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def grid_to_tm(x: float, y: float) -> Tuple[float, float]:
|
|
61
|
+
"""Scale the grid back to TM meters. / 콩나물 좌표를 TM 평면좌표 배율로 되돌림."""
|
|
62
|
+
return x / SCALE, y / SCALE
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def tm_to_grid_shifted(easting: float, northing: float) -> Tuple[int, int]:
|
|
66
|
+
"""As :func:`tm_to_grid`, moving island regions. / 도서 지역 보정을 포함."""
|
|
67
|
+
dx, dy = _island_offset(easting, northing, _ISLAND_RECTS, _SHIFT_TO_GRID)
|
|
68
|
+
return (
|
|
69
|
+
_round_half_up((easting + dx) * SCALE),
|
|
70
|
+
_round_half_up((northing + dy) * SCALE),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def grid_to_tm_shifted(x: float, y: float) -> Tuple[float, float]:
|
|
75
|
+
"""As :func:`grid_to_tm`, undoing the island move. / 도서 지역 보정을 되돌림."""
|
|
76
|
+
easting, northing = grid_to_tm(x, y)
|
|
77
|
+
dx, dy = _island_offset(easting, northing, _ISLAND_RECTS_MOVED, _SHIFT_TO_TM)
|
|
78
|
+
return easting + dx, northing + dy
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Datum shift between Bessel 1841 (Tokyo) and WGS84. / 베셀(도쿄측지계)↔WGS84 측지계 변환."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
from ._params import (
|
|
9
|
+
DEG,
|
|
10
|
+
MOLODENSKY_DS,
|
|
11
|
+
MOLODENSKY_DX,
|
|
12
|
+
MOLODENSKY_DY,
|
|
13
|
+
MOLODENSKY_DZ,
|
|
14
|
+
MOLODENSKY_KAPPA,
|
|
15
|
+
MOLODENSKY_OMEGA,
|
|
16
|
+
MOLODENSKY_PHI,
|
|
17
|
+
BESSEL_1841,
|
|
18
|
+
Ellipsoid,
|
|
19
|
+
WGS84_ELLIPSOID,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_CARTESIAN_ITERATIONS = 31
|
|
23
|
+
_CARTESIAN_TOLERANCE = 1.0e-18
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def geodetic_to_cartesian(lon: float, lat: float, ell: Ellipsoid) -> Tuple[float, float, float]:
|
|
27
|
+
"""Geographic to geocentric cartesian at zero height. / 경위도를 지심 직교좌표로 (높이 0)."""
|
|
28
|
+
lat_r = lat * DEG
|
|
29
|
+
lon_r = lon * DEG
|
|
30
|
+
nu = ell.a / math.sqrt(1 - ell.e2 * math.sin(lat_r) ** 2)
|
|
31
|
+
return (
|
|
32
|
+
nu * math.cos(lat_r) * math.cos(lon_r),
|
|
33
|
+
nu * math.cos(lat_r) * math.sin(lon_r),
|
|
34
|
+
nu * (ell.b**2 / ell.a**2) * math.sin(lat_r),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cartesian_to_geodetic(x: float, y: float, z: float, ell: Ellipsoid) -> Tuple[float, float]:
|
|
39
|
+
"""Geocentric cartesian to geographic. / 지심 직교좌표를 경위도로.
|
|
40
|
+
|
|
41
|
+
Latitude is solved iteratively. / 위도는 반복 계산으로 수렴시킵니다.
|
|
42
|
+
"""
|
|
43
|
+
lon = math.degrees(math.atan2(y, x))
|
|
44
|
+
if lon < 0:
|
|
45
|
+
lon += 360.0
|
|
46
|
+
|
|
47
|
+
p = math.hypot(x, y)
|
|
48
|
+
one_minus_e2 = ell.b**2 / ell.a**2
|
|
49
|
+
|
|
50
|
+
nu = ell.a
|
|
51
|
+
height = 0.0
|
|
52
|
+
lat_r = 0.0
|
|
53
|
+
previous = 0.0
|
|
54
|
+
for _ in range(_CARTESIAN_ITERATIONS):
|
|
55
|
+
lat_r = math.atan(z / math.sqrt((one_minus_e2 * nu + height) ** 2 - z**2))
|
|
56
|
+
if abs(lat_r - previous) < _CARTESIAN_TOLERANCE:
|
|
57
|
+
break
|
|
58
|
+
nu = ell.a / math.sqrt(1 - ell.e2 * math.sin(lat_r) ** 2)
|
|
59
|
+
height = p / math.cos(lat_r) - nu
|
|
60
|
+
previous = lat_r
|
|
61
|
+
|
|
62
|
+
return lon, lat_r / DEG
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _wgs84_frame_to_bessel(x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
66
|
+
scale = 1 + MOLODENSKY_DS
|
|
67
|
+
return (
|
|
68
|
+
x + scale * (MOLODENSKY_KAPPA * y - MOLODENSKY_PHI * z) + MOLODENSKY_DX,
|
|
69
|
+
y + scale * (-MOLODENSKY_KAPPA * x + MOLODENSKY_OMEGA * z) + MOLODENSKY_DY,
|
|
70
|
+
z + scale * (MOLODENSKY_PHI * x - MOLODENSKY_OMEGA * y) + MOLODENSKY_DZ,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _bessel_frame_to_wgs84(x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
75
|
+
scale = 1 + MOLODENSKY_DS
|
|
76
|
+
sx = (x - MOLODENSKY_DX) * scale
|
|
77
|
+
sy = (y - MOLODENSKY_DY) * scale
|
|
78
|
+
sz = (z - MOLODENSKY_DZ) * scale
|
|
79
|
+
return (
|
|
80
|
+
(sx - MOLODENSKY_KAPPA * sy + MOLODENSKY_PHI * sz) / scale,
|
|
81
|
+
(MOLODENSKY_KAPPA * sx + sy - MOLODENSKY_OMEGA * sz) / scale,
|
|
82
|
+
(-MOLODENSKY_PHI * sx + MOLODENSKY_OMEGA * sy + sz) / scale,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def bessel_to_wgs84(lon: float, lat: float) -> Tuple[float, float]:
|
|
87
|
+
"""Bessel/Tokyo geographic to WGS84 geographic. / 베셀 경위도를 WGS84 경위도로."""
|
|
88
|
+
x, y, z = geodetic_to_cartesian(lon, lat, BESSEL_1841)
|
|
89
|
+
return cartesian_to_geodetic(*_bessel_frame_to_wgs84(x, y, z), WGS84_ELLIPSOID)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def wgs84_to_bessel(lon: float, lat: float) -> Tuple[float, float]:
|
|
93
|
+
"""WGS84 geographic to Bessel/Tokyo geographic. / WGS84 경위도를 베셀 경위도로."""
|
|
94
|
+
x, y, z = geodetic_to_cartesian(lon, lat, WGS84_ELLIPSOID)
|
|
95
|
+
return cartesian_to_geodetic(*_wgs84_frame_to_bessel(x, y, z), BESSEL_1841)
|