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.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .venv/
6
+ dist/
7
+ build/
8
+ .env
@@ -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
+ [![CI](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml/badge.svg)](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/koreacoord)](https://pypi.org/project/koreacoord/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/koreacoord)](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
+ [![CI](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml/badge.svg)](https://github.com/AngLaboratory/koreacoord/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/koreacoord)](https://pypi.org/project/koreacoord/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/koreacoord)](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)