coordtransform/lib.rs
1//! # coordtransform
2//!
3//! 提供百度坐标系(BD09)、火星坐标系(国测局坐标系、GCJ02)、WGS84坐标系的相互转换,基于 Rust 语言,无特殊依赖。
4//! Provides mutual conversion between Baidu Coordinate System (BD09), Mars Coordinate System (GCJ02), and WGS84 Coordinate System, implemented in Rust with no special dependencies.
5//!
6//! ## 坐标系说明
7//! ## Coordinate System Description
8//!
9//! - **WGS84坐标系**:即地球坐标系,国际上通用的坐标系
10//! - **WGS84 Coordinate System**: The Earth coordinate system, commonly used internationally.
11//! - **GCJ02坐标系**:即火星坐标系,WGS84坐标系经加密后的坐标系。Google Maps,高德在用
12//! - **GCJ02 Coordinate System**: Also known as the Mars coordinate system, an encrypted version of the WGS84 coordinate system. Used by Google Maps and Amap.
13//! - **BD09坐标系**:即百度坐标系,GCJ02坐标系经加密后的坐标系
14//! - **BD09 Coordinate System**: Also known as the Baidu coordinate system, an encrypted version of the GCJ02 coordinate system.
15//! - **EPSG:3857坐标系**:即Web墨卡托投影坐标系,广泛用于Web地图服务
16//! - **EPSG:3857 Coordinate System**: Also known as Web Mercator projection, widely used in web mapping services.
17//!
18//! ## Usage Example 使用示例
19//!
20//! ```rust
21//! use coordtransform::*;
22//!
23//! // bd09百度坐标系 -> gcj02火星坐标系
24//! let (lon, lat) = bd09_to_gcj02(116.404, 39.915);
25//!
26//! // bd09火星坐标系 -> gcj02百度坐标系
27//! let (lon, lat) = gcj02_to_bd09(116.404, 39.915);
28//!
29//! // WGS84坐标系 -> gcj02火星坐标系
30//! let (lon, lat) = wgs84_to_gcj02(116.404, 39.915);
31//!
32//! // gcj02火星坐标系 -> WGS84坐标系
33//! let (lon, lat) = gcj02_to_wgs84(116.404, 39.915);
34//!
35//! // bd09百度坐标系 -> WGS84坐标系
36//! let (lon, lat) = bd09_to_wgs84(116.404, 39.915);
37//!
38//! // WGS84坐标系 -> bd09百度坐标系
39//! let (lon, lat) = wgs84_to_bd09(116.404, 39.915);
40//!
41//! // WGS84坐标系 -> EPSG:3857坐标系
42//! let (x, y) = wgs84_to_epsg3857(116.404, 39.915);
43//!
44//! // EPSG:3857坐标系 -> WGS84坐标系
45//! let (lon, lat) = epsg3857_to_wgs84(12958752.0, 4825923.0);
46//! ```
47
48use std::f64::consts::PI;
49
50/// X_PI constant 常量
51///
52const X_PI: f64 = PI * 3000.0 / 180.0;
53
54/// Offset constant 偏移量常量
55const OFFSET: f64 = 0.00669342162296594323;
56
57/// 地球长半轴
58/// Earth's semi-major axis
59const AXIS: f64 = 6378245.0;
60
61/// EPSG:3857 Web墨卡托投影相关常量
62/// EPSG:3857 Web Mercator projection constants
63
64/// 地球半径 (米)
65/// Earth radius in meters
66const EARTH_RADIUS: f64 = 6378137.0;
67
68/// 最大纬度 (度)
69/// Maximum latitude in degrees
70const MAX_LATITUDE: f64 = 85.0511287798;
71
72/// 百度坐标系 -> 火星坐标系
73/// Baidu Coordinate System -> Mars Coordinate System
74///
75/// # Parameters 参数
76///
77/// * `lon` - 经度 Longitude
78/// * `lat` - 纬度 Latitude
79///
80/// # 返回值 Return Value
81///
82/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
83///
84/// # 示例 Example
85///
86/// ```rust
87/// use coordtransform::bd09_to_gcj02;
88///
89/// let (lon, lat) = bd09_to_gcj02(116.404, 39.915);
90/// ```
91pub fn bd09_to_gcj02(lon: f64, lat: f64) -> (f64, f64) {
92 let x = lon - 0.0065;
93 let y = lat - 0.006;
94
95 let z = (x * x + y * y).sqrt() - 0.00002 * (y * X_PI).sin();
96 let theta = y.atan2(x) - 0.000003 * (x * X_PI).cos();
97
98 let g_lon = z * theta.cos();
99 let g_lat = z * theta.sin();
100
101 (g_lon, g_lat)
102}
103
104/// gcj02火星坐标系 -> bd09百度坐标系
105///
106/// # Parameters 参数
107///
108/// * `lon` - 经度 Longitude
109/// * `lat` - 纬度 Latitude
110///
111/// # 返回值 Return Value
112///
113/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
114///
115/// # 示例 Example
116///
117/// ```rust
118/// use coordtransform::gcj02_to_bd09;
119///
120/// let (lon, lat) = gcj02_to_bd09(116.404, 39.915);
121/// ```
122pub fn gcj02_to_bd09(lon: f64, lat: f64) -> (f64, f64) {
123 let z = (lon * lon + lat * lat).sqrt() + 0.00002 * (lat * X_PI).sin();
124 let theta = lat.atan2(lon) + 0.000003 * (lon * X_PI).cos();
125
126 let bd_lon = z * theta.cos() + 0.0065;
127 let bd_lat = z * theta.sin() + 0.006;
128
129 (bd_lon, bd_lat)
130}
131
132/// WGS84坐标系 -> 火星坐标系
133/// WGS84 Coordinate System -> Mars Coordinate System
134///
135/// # Parameters 参数
136///
137/// * `lon` - 经度 Longitude
138/// * `lat` - 纬度 Latitude
139///
140/// # 返回值 Return Value
141///
142/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
143///
144/// # 示例 Example
145///
146/// ```rust
147/// use coordtransform::wgs84_to_gcj02;
148///
149/// let (lon, lat) = wgs84_to_gcj02(116.404, 39.915);
150/// ```
151pub fn wgs84_to_gcj02(lon: f64, lat: f64) -> (f64, f64) {
152 if is_out_of_china(lon, lat) {
153 return (lon, lat);
154 }
155
156 delta(lon, lat)
157}
158
159/// gcj02火星坐标系 -> WGS84坐标系
160///
161/// # Parameters 参数
162///
163/// * `lon` - 经度 Longitude
164/// * `lat` - 纬度 Latitude
165///
166/// # Return Value 返回值
167///
168/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
169///
170/// # 示例 Example
171///
172/// ```rust
173/// use coordtransform::gcj02_to_wgs84;
174///
175/// let (lon, lat) = gcj02_to_wgs84(116.404, 39.915);
176/// ```
177pub fn gcj02_to_wgs84(lon: f64, lat: f64) -> (f64, f64) {
178 if is_out_of_china(lon, lat) {
179 return (lon, lat);
180 }
181
182 let (mg_lon, mg_lat) = delta(lon, lat);
183
184 (lon * 2.0 - mg_lon, lat * 2.0 - mg_lat)
185}
186
187/// 百度坐标系 -> WGS84坐标系
188/// Baidu Coordinate System -> WGS84 Coordinate System
189///
190/// # Parameters 参数
191///
192/// * `lon` - 经度 Longitude
193/// * `lat` - 纬度 Latitude
194///
195/// # Return Value 返回值
196///
197/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
198///
199/// # 示例 Example
200///
201/// ```rust
202/// use coordtransform::bd09_to_wgs84;
203///
204/// let (lon, lat) = bd09_to_wgs84(116.404, 39.915);
205/// ```
206pub fn bd09_to_wgs84(lon: f64, lat: f64) -> (f64, f64) {
207 let (lon, lat) = bd09_to_gcj02(lon, lat);
208 gcj02_to_wgs84(lon, lat)
209}
210
211/// WGS84坐标系 -> 百度坐标系
212/// WGS84 Coordinate System -> Baidu Coordinate System
213///
214/// # Parameters 参数
215///
216/// * `lon` - 经度 Longitude
217/// * `lat` - 纬度 Latitude
218///
219/// # Return Value 返回值
220///
221/// 返回转换后的 (经度, 纬度) 元组 Returns a tuple of (longitude, latitude) after conversion
222///
223/// # Example 示例
224///
225/// ```rust
226/// use coordtransform::wgs84_to_bd09;
227///
228/// let (lon, lat) = wgs84_to_bd09(116.404, 39.915);
229/// ```
230pub fn wgs84_to_bd09(lon: f64, lat: f64) -> (f64, f64) {
231 let (lon, lat) = wgs84_to_gcj02(lon, lat);
232 gcj02_to_bd09(lon, lat)
233}
234
235/// Calculate coordinate offset 计算坐标偏移量
236fn delta(lon: f64, lat: f64) -> (f64, f64) {
237 let (dlat, dlon) = transform(lon - 105.0, lat - 35.0);
238 let radlat = lat / 180.0 * PI;
239 let magic = radlat.sin();
240 let magic = 1.0 - OFFSET * magic * magic;
241 let sqrtmagic = magic.sqrt();
242
243 let dlat = (dlat * 180.0) / ((AXIS * (1.0 - OFFSET)) / (magic * sqrtmagic) * PI);
244 let dlon = (dlon * 180.0) / (AXIS / sqrtmagic * radlat.cos() * PI);
245
246 let mg_lat = lat + dlat;
247 let mg_lon = lon + dlon;
248
249 (mg_lon, mg_lat)
250}
251
252/// Coordinate transformation function 坐标变换函数
253fn transform(lon: f64, lat: f64) -> (f64, f64) {
254 let lonlat = lon * lat;
255 let abs_x = lon.abs().sqrt();
256 let lon_pi = lon * PI;
257 let lat_pi = lat * PI;
258 let d = 20.0 * (6.0 * lon_pi).sin() + 20.0 * (2.0 * lon_pi).sin();
259
260 let mut x = d;
261 let mut y = d;
262
263 x += 20.0 * lat_pi.sin() + 40.0 * (lat_pi / 3.0).sin();
264 y += 20.0 * lon_pi.sin() + 40.0 * (lon_pi / 3.0).sin();
265 x += 160.0 * (lat_pi / 12.0).sin() + 320.0 * (lat_pi / 30.0).sin();
266 y += 150.0 * (lon_pi / 12.0).sin() + 300.0 * (lon_pi / 30.0).sin();
267
268 x *= 2.0 / 3.0;
269 y *= 2.0 / 3.0;
270
271 x += -100.0 + 2.0 * lon + 3.0 * lat + 0.2 * lat * lat + 0.1 * lonlat + 0.2 * abs_x;
272 y += 300.0 + lon + 2.0 * lat + 0.1 * lon * lon + 0.1 * lonlat + 0.1 * abs_x;
273
274 (x, y)
275}
276
277/// WGS84坐标系 -> EPSG:3857坐标系 (Web墨卡托投影)
278/// WGS84 Coordinate System -> EPSG:3857 Coordinate System (Web Mercator Projection)
279///
280/// # Parameters 参数
281///
282/// * `lon` - 经度 Longitude (度 degrees)
283/// * `lat` - 纬度 Latitude (度 degrees)
284///
285/// # Return Value 返回值
286///
287/// 返回转换后的 (X, Y) 元组 (米) Returns a tuple of (X, Y) in meters after conversion
288///
289/// # Example 示例
290///
291/// ```rust
292/// use coordtransform::wgs84_to_epsg3857;
293///
294/// let (x, y) = wgs84_to_epsg3857(116.404, 39.915);
295/// ```
296pub fn wgs84_to_epsg3857(lon: f64, lat: f64) -> (f64, f64) {
297 // 限制纬度范围
298 // Clamp latitude to valid range
299 let lat = lat.max(-MAX_LATITUDE).min(MAX_LATITUDE);
300
301 let x = lon * PI / 180.0 * EARTH_RADIUS;
302 let y = ((PI / 4.0 + lat * PI / 360.0).tan()).ln() * EARTH_RADIUS;
303
304 (x, y)
305}
306
307/// EPSG:3857坐标系 -> WGS84坐标系 (Web墨卡托投影)
308/// EPSG:3857 Coordinate System -> WGS84 Coordinate System (Web Mercator Projection)
309///
310/// # Parameters 参数
311///
312/// * `x` - X坐标 X coordinate (米 meters)
313/// * `y` - Y坐标 Y coordinate (米 meters)
314///
315/// # Return Value 返回值
316///
317/// 返回转换后的 (经度, 纬度) 元组 (度) Returns a tuple of (longitude, latitude) in degrees after conversion
318///
319/// # Example 示例
320///
321/// ```rust
322/// use coordtransform::epsg3857_to_wgs84;
323///
324/// let (lon, lat) = epsg3857_to_wgs84(12958752.0, 4825923.0);
325/// ```
326pub fn epsg3857_to_wgs84(x: f64, y: f64) -> (f64, f64) {
327 let lon = x / EARTH_RADIUS * 180.0 / PI;
328 let lat = (2.0 * (y / EARTH_RADIUS).exp().atan() - PI / 2.0) * 180.0 / PI;
329
330 (lon, lat)
331}
332
333/// GCJ02坐标系 -> EPSG:3857坐标系
334/// GCJ02 Coordinate System -> EPSG:3857 Coordinate System
335///
336/// # Parameters 参数
337///
338/// * `lon` - 经度 Longitude (度 degrees)
339/// * `lat` - 纬度 Latitude (度 degrees)
340///
341/// # Return Value 返回值
342///
343/// 返回转换后的 (X, Y) 元组 (米) Returns a tuple of (X, Y) in meters after conversion
344///
345/// # Example 示例
346///
347/// ```rust
348/// use coordtransform::gcj02_to_epsg3857;
349///
350/// let (x, y) = gcj02_to_epsg3857(116.404, 39.915);
351/// ```
352pub fn gcj02_to_epsg3857(lon: f64, lat: f64) -> (f64, f64) {
353 let (wgs_lon, wgs_lat) = gcj02_to_wgs84(lon, lat);
354 wgs84_to_epsg3857(wgs_lon, wgs_lat)
355}
356
357/// EPSG:3857坐标系 -> GCJ02坐标系
358/// EPSG:3857 Coordinate System -> GCJ02 Coordinate System
359///
360/// # Parameters 参数
361///
362/// * `x` - X坐标 X coordinate (米 meters)
363/// * `y` - Y坐标 Y coordinate (米 meters)
364///
365/// # Return Value 返回值
366///
367/// 返回转换后的 (经度, 纬度) 元组 (度) Returns a tuple of (longitude, latitude) in degrees after conversion
368///
369/// # Example 示例
370///
371/// ```rust
372/// use coordtransform::epsg3857_to_gcj02;
373///
374/// let (lon, lat) = epsg3857_to_gcj02(12958752.0, 4825923.0);
375/// ```
376pub fn epsg3857_to_gcj02(x: f64, y: f64) -> (f64, f64) {
377 let (wgs_lon, wgs_lat) = epsg3857_to_wgs84(x, y);
378 wgs84_to_gcj02(wgs_lon, wgs_lat)
379}
380
381/// BD09坐标系 -> EPSG:3857坐标系
382/// BD09 Coordinate System -> EPSG:3857 Coordinate System
383///
384/// # Parameters 参数
385///
386/// * `lon` - 经度 Longitude (度 degrees)
387/// * `lat` - 纬度 Latitude (度 degrees)
388///
389/// # Return Value 返回值
390///
391/// 返回转换后的 (X, Y) 元组 (米) Returns a tuple of (X, Y) in meters after conversion
392///
393/// # Example 示例
394///
395/// ```rust
396/// use coordtransform::bd09_to_epsg3857;
397///
398/// let (x, y) = bd09_to_epsg3857(116.404, 39.915);
399/// ```
400pub fn bd09_to_epsg3857(lon: f64, lat: f64) -> (f64, f64) {
401 let (wgs_lon, wgs_lat) = bd09_to_wgs84(lon, lat);
402 wgs84_to_epsg3857(wgs_lon, wgs_lat)
403}
404
405/// EPSG:3857坐标系 -> BD09坐标系
406/// EPSG:3857 Coordinate System -> BD09 Coordinate System
407///
408/// # Parameters 参数
409///
410/// * `x` - X坐标 X coordinate (米 meters)
411/// * `y` - Y坐标 Y coordinate (米 meters)
412///
413/// # Return Value 返回值
414///
415/// 返回转换后的 (经度, 纬度) 元组 (度) Returns a tuple of (longitude, latitude) in degrees after conversion
416///
417/// # Example 示例
418///
419/// ```rust
420/// use coordtransform::epsg3857_to_bd09;
421///
422/// let (lon, lat) = epsg3857_to_bd09(12958752.0, 4825923.0);
423/// ```
424pub fn epsg3857_to_bd09(x: f64, y: f64) -> (f64, f64) {
425 let (wgs_lon, wgs_lat) = epsg3857_to_wgs84(x, y);
426 wgs84_to_bd09(wgs_lon, wgs_lat)
427}
428
429/// Determine whether the coordinates are outside of China 判断坐标是否在中国境外
430fn is_out_of_china(lon: f64, lat: f64) -> bool {
431 !(lon > 72.004 && lon < 135.05 && lat > 3.86 && lat < 53.55)
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_bd09_to_gcj02() {
440 let (lon, lat) = bd09_to_gcj02(116.404, 39.915);
441 assert!((lon - 116.39762729119315).abs() < 1e-10);
442 assert!((lat - 39.90865673957631).abs() < 1e-10);
443 }
444
445 #[test]
446 fn test_gcj02_to_bd09() {
447 let (lon, lat) = gcj02_to_bd09(116.404, 39.915);
448 assert!((lon - 116.41036949371029).abs() < 1e-10);
449 assert!((lat - 39.92133699351022).abs() < 1e-10);
450 }
451
452 #[test]
453 fn test_wgs84_to_gcj02() {
454 let (lon, lat) = wgs84_to_gcj02(116.404, 39.915);
455 assert!((lon - 116.41024449916938).abs() < 1e-10);
456 assert!((lat - 39.91640428150164).abs() < 1e-10);
457 }
458
459 #[test]
460 fn test_gcj02_to_wgs84() {
461 let (lon, lat) = gcj02_to_wgs84(116.404, 39.915);
462 assert!((lon - 116.39775550083061).abs() < 1e-10);
463 assert!((lat - 39.91359571849836).abs() < 1e-10);
464 }
465
466 #[test]
467 fn test_bd09_to_wgs84() {
468 let (lon, lat) = bd09_to_wgs84(116.404, 39.915);
469 assert!((lon - 116.39138369954496).abs() < 1e-10);
470 assert!((lat - 39.90725321448524).abs() < 1e-10);
471 }
472
473 #[test]
474 fn test_wgs84_to_bd09() {
475 let (lon, lat) = wgs84_to_bd09(116.404, 39.915);
476 assert!((lon - 116.41662724378733).abs() < 1e-10);
477 assert!((lat - 39.922699552216216).abs() < 1e-10);
478 }
479
480 #[test]
481 fn test_out_of_china() {
482 // 测试中国境外坐标,应该直接返回原坐标
483 // Test coordinates outside of China, should directly return the original coordinates
484 let (lon, lat) = wgs84_to_gcj02(0.0, 0.0);
485 assert_eq!(lon, 0.0);
486 assert_eq!(lat, 0.0);
487 }
488
489 #[test]
490 fn test_wgs84_to_epsg3857() {
491 let (x, y) = wgs84_to_epsg3857(116.404, 39.915);
492 assert!((x - 12958034.01).abs() < 1.0);
493 assert!((y - 4853597.99).abs() < 1.0);
494 }
495
496 #[test]
497 fn test_epsg3857_to_wgs84() {
498 let (lon, lat) = epsg3857_to_wgs84(12958034.01, 4853597.99);
499 assert!((lon - 116.404).abs() < 1e-6);
500 assert!((lat - 39.915).abs() < 1e-6);
501 }
502
503 #[test]
504 fn test_gcj02_to_epsg3857() {
505 let (x, y) = gcj02_to_epsg3857(116.404, 39.915);
506 // 先转换为WGS84再转换为EPSG3857
507 let (wgs_lon, wgs_lat) = gcj02_to_wgs84(116.404, 39.915);
508 let (expected_x, expected_y) = wgs84_to_epsg3857(wgs_lon, wgs_lat);
509 assert!((x - expected_x).abs() < 1e-10);
510 assert!((y - expected_y).abs() < 1e-10);
511 }
512
513 #[test]
514 fn test_epsg3857_to_gcj02() {
515 let (lon, lat) = epsg3857_to_gcj02(12958752.668618806, 4825923.036067264);
516 // 先转换为WGS84再转换为GCJ02
517 let (wgs_lon, wgs_lat) = epsg3857_to_wgs84(12958752.668618806, 4825923.036067264);
518 let (expected_lon, expected_lat) = wgs84_to_gcj02(wgs_lon, wgs_lat);
519 assert!((lon - expected_lon).abs() < 1e-10);
520 assert!((lat - expected_lat).abs() < 1e-10);
521 }
522
523 #[test]
524 fn test_bd09_to_epsg3857() {
525 let (x, y) = bd09_to_epsg3857(116.404, 39.915);
526 // 先转换为WGS84再转换为EPSG3857
527 let (wgs_lon, wgs_lat) = bd09_to_wgs84(116.404, 39.915);
528 let (expected_x, expected_y) = wgs84_to_epsg3857(wgs_lon, wgs_lat);
529 assert!((x - expected_x).abs() < 1e-10);
530 assert!((y - expected_y).abs() < 1e-10);
531 }
532
533 #[test]
534 fn test_epsg3857_to_bd09() {
535 let (lon, lat) = epsg3857_to_bd09(12958752.668618806, 4825923.036067264);
536 // 先转换为WGS84再转换为BD09
537 let (wgs_lon, wgs_lat) = epsg3857_to_wgs84(12958752.668618806, 4825923.036067264);
538 let (expected_lon, expected_lat) = wgs84_to_bd09(wgs_lon, wgs_lat);
539 assert!((lon - expected_lon).abs() < 1e-10);
540 assert!((lat - expected_lat).abs() < 1e-10);
541 }
542
543 #[test]
544 fn test_epsg3857_edge_cases() {
545 // 测试极限纬度
546 // Test edge case latitudes
547 let (x, y) = wgs84_to_epsg3857(0.0, 85.0511287798);
548 let (lon, lat) = epsg3857_to_wgs84(x, y);
549 assert!((lon - 0.0).abs() < 1e-10);
550 assert!((lat - 85.0511287798).abs() < 1e-6);
551
552 // 测试负极限纬度
553 // Test negative edge case latitude
554 let (x, y) = wgs84_to_epsg3857(0.0, -85.0511287798);
555 let (lon, lat) = epsg3857_to_wgs84(x, y);
556 assert!((lon - 0.0).abs() < 1e-10);
557 assert!((lat - (-85.0511287798)).abs() < 1e-6);
558 }
559}