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}