从纬度、经度转换为x、y

50
我想将GPS位置(纬度,经度)转换成x,y坐标。我找到了很多关于这个主题的链接并进行了应用,但是它没有给我正确的答案! 我遵循以下步骤测试答案: (1)首先,我取两个位置并使用地图计算它们之间的距离。 (2)然后将这两个位置转换为x,y坐标。 (3)然后再次计算在x,y坐标中两点之间的距离,并查看它是否给出与第1点相同的结果。 我发现了其中一种解决方案,但它没有给我正确的答案!
latitude = Math.PI * latitude / 180;
longitude = Math.PI * longitude / 180;

// adjust position by radians
latitude -= 1.570795765134; // subtract 90 degrees (in radians)

// and switch z and y 
xPos = (app.radius) * Math.sin(latitude) * Math.cos(longitude);
zPos = (app.radius) * Math.sin(latitude) * Math.sin(longitude);
yPos = (app.radius) * Math.cos(latitude);

我也尝试了这个链接,但是对我来说仍然不起作用!

请问如何将(纬度,经度)转换为(x,y)?

谢谢。


为什么你要减去90度? - Pedro77
6个回答

74

无确切解决方案

从球体到平面没有等距映射。当你将球体上的纬度/经度坐标转换成平面上的x/y坐标时,你不能指望这个操作会保持所有长度不变。你必须接受某种形式的变形。存在许多不同的地图投影,它们可以在保持长度、角度和面积之间达到不同的折衷。对于地球表面的小部分,横向墨卡托投影是相当常见的。你可能听说过UTM。但还有更多

你引用的公式计算x/y/z,即3D空间中的一个点。但即使在那里,你也不会自动获得正确的距离。球面上两点之间的最短距离将通过该球体,而地球上的距离大多是沿着表面的测地线长度。因此它们会更长。

对于小区域的近似

如果你想绘制的地球表面部分相对较小,那么你可以使用一个非常简单的近似。你可以简单地使用水平轴 x 表示经度λ,垂直轴 y 表示纬度φ。这两者之间的比率不应该是1:1, 而是应该使用cos(φ0)作为长宽比,其中φ0表示你地图中心附近的纬度。此外,要从角度(以弧度表示)转换为长度,你需要乘以地球的半径(在这个模型中假设为球体)。

  • x = r λ cos(φ0)
  • y = r φ

这是简单的等经纬投影。在大多数情况下,您只需计算cos(φ0)一次,这使得后续计算大量点变得非常便宜。


谢谢@MvG,我不涉及这些话题,我只需要在地图上绘制点,这些点之间的距离不超过50米,有什么简单的方法吗? - userInThisWorld
@alsadi90:我更新了我的回答,描述了一个简单的适用于你的情况的近似方法。 - MvG
谢谢,但我有这些问题:这里的λ和φ是弧度吗?关于φ0,如果我的区域接近50mx50m,你建议我把它放在哪里? - userInThisWorld
2
@alsadi90:无论你用度数还是弧度来测量角度,只会影响地图的大小而不会影响地图的形状。你必须确保正确计算余弦值,大多数编程语言都使用弧度。对于φ₀,我建议你只需取纬度下限和上限的平均值即可。但对于这样小的地图,从该区域内任何纬度处选取一个纬度可能已经足够好了。 - MvG
谢谢你的帮助,但我已经按照这个例子应用了步骤:point1(32.49194144785378,35.99031005054712),point2(32.492285325127966,35.99031541496515),它们之间的实际距离= 38.284米,使用此网站http://www.daftlogic.com/projects-google-maps-distance-calculator.htm,然后我应用了假设φ₀ = 32.492113386490873,这是两点纬度的平均值,然后在x,y坐标中,我得到了point1(0.3559,0.5668)和ppoint2(0.3559,0.5668),然后它们之间的距离= 5.9990e-06!这是错误的值! - userInThisWorld
如果你想要以米为单位的结果,那么你必须将弧度乘以地球半径。我已经相应地更新了我的答案。 - MvG

18

我想与您分享我如何解决这个问题。就像@MvG所说的那样,我使用了等经纬投影,但是这种方法会给出与地球(或整个地图)相关的X和Y位置,这意味着您获得全局位置。在我的情况下,我想将坐标转换为一个小区域(约500m²),因此我将投影点与另外两个点联系起来,获取全局位置,并将其与本地(屏幕上的)位置相关联,就像这样:

首先,我选择围绕我想要投影的区域的两个点(左上角和右下角),就像这张图片:

图片描述

一旦我有了纬度和经度的全局参考区域,我也会为屏幕位置做同样的事情。包含这些数据的对象如下所示。

//top-left reference point
var p0 = {
    scrX: 23.69,        // Minimum X position on screen
    scrY: -0.5,         // Minimum Y position on screen
    lat: -22.814895,    // Latitude
    lng: -47.072892     // Longitude
}

//bottom-right reference point
var p1 = {
    scrX: 276,          // Maximum X position on screen
    scrY: 178.9,        // Maximum Y position on screen
    lat: -22.816419,    // Latitude
    lng: -47.070563     // Longitude
}

var radius = 6371;      //Earth Radius in Km

//## Now I can calculate the global X and Y for each reference point ##\\

// This function converts lat and lng coordinates to GLOBAL X and Y positions
function latlngToGlobalXY(lat, lng){
    //Calculates x based on cos of average of the latitudes
    let x = radius*lng*Math.cos((p0.lat + p1.lat)/2);
    //Calculates y based on latitude
    let y = radius*lat;
    return {x: x, y: y}
}

// Calculate global X and Y for top-left reference point
p0.pos = latlngToGlobalXY(p0.lat, p0.lng);
// Calculate global X and Y for bottom-right reference point
p1.pos = latlngToGlobalXY(p1.lat, p1.lng);

/*
* This gives me the X and Y in relation to map for the 2 reference points.
* Now we have the global AND screen areas and then we can relate both for the projection point.
*/

// This function converts lat and lng coordinates to SCREEN X and Y positions
function latlngToScreenXY(lat, lng){
    //Calculate global X and Y for projection point
    let pos = latlngToGlobalXY(lat, lng);
    //Calculate the percentage of Global X position in relation to total global width
    pos.perX = ((pos.x-p0.pos.x)/(p1.pos.x - p0.pos.x));
    //Calculate the percentage of Global Y position in relation to total global height
    pos.perY = ((pos.y-p0.pos.y)/(p1.pos.y - p0.pos.y));

    //Returns the screen position based on reference points
    return {
        x: p0.scrX + (p1.scrX - p0.scrX)*pos.perX,
        y: p0.scrY + (p1.scrY - p0.scrY)*pos.perY
    }
}

//# The usage is like this #\\

var pos = latlngToScreenXY(-22.815319, -47.071718);
$point = $("#point-to-project");
$point.css("left", pos.x+"em");
$point.css("top", pos.y+"em");

你可以看到,我用JavaScript编写了这个程序,但是计算部分可以翻译成任何语言。

附注:我正在将转换后的位置应用到一个id为“point-to-project”的HTML元素上。如果您想在项目中使用此代码,请创建此元素(样式为绝对定位),或更改“使用”块。


2
出现了一个错误:地球的半径是6371公里,而不是6.371(在JS中略大于6)。当然,在这么小的范围内它并没有太大的变化,但未来的读者应该注意这一点。 - Giulio Muscarello
我正在尝试使用上述的JS代码来做和你一样的事情:我有左上角和右下角的纬度/经度坐标,以及与WA、OR和CA地图相关的x/y值。我正在通过绘制纬度48、46、44、42和40,经度为-122的代码进行测试。我原本期望我的绘图点会在这些坐标的十字线上,但它们只是沿着一条直线向下延伸(https://i.imgur.com/GuCP0iz.png)。它们没有遵循经度线的“曲线”。你的地图(正如你所提到的)很小,而我的地图不是。这是代码的限制吗?还是说这不是解决我的问题的代码?谢谢 - undefined

4

由于我在谷歌搜索同样的问题时这个页面出现在了最顶端,所以我想提供一个更实用的答案。MVG的答案是正确的,但比较理论化。

我用 JavaScript 为 Fitbit Ionic 制作了一个轨迹绘制应用程序。下面的代码就是我解决问题的方法。

//LOCATION PROVIDER
index.js
var gpsFix = false;
var circumferenceAtLat = 0;
function locationSuccess(pos){
  if(!gpsFix){
    gpsFix = true;
    circumferenceAtLat = Math.cos(pos.coords.latitude*0.01745329251)*111305;
  }
  pos.x:Math.round(pos.coords.longitude*circumferenceAtLat),
  pos.y:Math.round(pos.coords.latitude*110919), 
  plotTrack(pos);
}

plotting.js

plotTrack(position){

let x = Math.round((this.segments[i].start.x - this.bounds.minX)*this.scale);
let y = Math.round(this.bounds.maxY - this.segments[i].start.y)*this.scale; //heights needs to be inverted

//redraw?
let redraw = false;

//x or y bounds?
 if(position.x>this.bounds.maxX){
   this.bounds.maxX = (position.x-this.bounds.minX)*1.1+this.bounds.minX; //increase by 10%
   redraw = true;
 }
 if(position.x<this.bounds.minX){
   this.bounds.minX = this.bounds.maxX-(this.bounds.maxX-position.x)*1.1;
    redraw = true;
 };
 if(position.y>this.bounds.maxY){
   this.bounds.maxY = (position.y-this.bounds.minY)*1.1+this.bounds.minY; //increase by 10%
    redraw = true;
 }
 if(position.y<this.bounds.minY){
   this.bounds.minY = this.bounds.maxY-(this.bounds.maxY-position.y)*1.1;
    redraw = true;
 }
 if(redraw){
   reDraw();
 }
}


function reDraw(){

let xScale = device.screen.width / (this.bounds.maxX-this.bounds.minX);
let yScale = device.screen.height / (this.bounds.maxY-this.bounds.minY); 
if(xScale<yScale) this.scale = xScale; 
else this.scale = yScale;

//Loop trough your object to redraw all of them
}

你是要转换成国际单位制(公里)还是英制单位制(英里)? - Cloud Cho
1
嗨 @CloudCho。我像任何一种编程语言一样使用公制单位。在我的代码最后,我仅出于视觉原因进行换算。111305是1纬度之间的米数差异。 - Pieter Oskam

3

为了完整起见,我想添加一下我的Python适配版 @allexrm 代码,它非常有效。再次感谢!

radius = 6371    #Earth Radius in KM

class referencePoint:
    def __init__(self, scrX, scrY, lat, lng):
        self.scrX = scrX
        self.scrY = scrY
        self.lat = lat
        self.lng = lng


# Calculate global X and Y for top-left reference point        
p0 = referencePoint(0, 0, 52.526470, 13.403215)
# Calculate global X and Y for bottom-right reference point
p1 = referencePoint(2244, 2060, 52.525035, 13.405809) 


# This function converts lat and lng coordinates to GLOBAL X and Y positions
def latlngToGlobalXY(lat, lng):
    # Calculates x based on cos of average of the latitudes
    x = radius*lng*math.cos((p0.lat + p1.lat)/2)
    # Calculates y based on latitude
    y = radius*lat
    return {'x': x, 'y': y}


# This function converts lat and lng coordinates to SCREEN X and Y positions
def latlngToScreenXY(lat, lng):
    # Calculate global X and Y for projection point
    pos = latlngToGlobalXY(lat, lng)
    # Calculate the percentage of Global X position in relation to total global width
    perX = ((pos['x']-p0.pos['x'])/(p1.pos['x'] - p0.pos['x']))
    # Calculate the percentage of Global Y position in relation to total global height
    perY = ((pos['y']-p0.pos['y'])/(p1.pos['y'] - p0.pos['y']))

    # Returns the screen position based on reference points
    return {
        'x': p0.scrX + (p1.scrX - p0.scrX)*perX,
        'y': p0.scrY + (p1.scrY - p0.scrY)*perY
    }


pos = latlngToScreenXY(52.525607, 13.404572);

pos ['x]和pos ['y]包含经度和纬度(52.525607, 13.404572)的翻译后的X和Y坐标。

我希望这对于像我一样正在寻找将经纬度转换为本地参考坐标系的正确解决方案的人有所帮助。

最好的祝福


1
纬度和经度以度为单位,但 math.cos 的参数是弧度。latlngToGlobalXY 函数正确吗? - Christian Lindig

2

最好将其转换为UTM坐标,并将其视为x和y。

import utm
u = utm.from_latlon(12.917091, 77.573586)

结果将是(779260.623156606, 1429369.8665238516, 43, 'P')。前两个数可以视为x和y坐标,43P是UTM区域,对于小范围(宽度最多为668公里)可以忽略。

1
我认为仅仅将球面坐标转换为UTM然后忽略区域可能会导致跨越区域边界的区域出现问题。你会得到来自不同区域的结果,这些结果无法拼合在一起。应该有一种方法强制所有点使用相同的区域,这样它们就应该更好地拟合。 - MvG

0

最简单的方法是使用墨卡托投影。请注意,极地周围可能会有一些拉伸。

参考代码(Kotlin):

fun latLngToXY(point: LatLng): Pair<Double, Double> {
    // Converts to radians.
    val latRad = Math.toRadians(point.latitude)
    val longRad = Math.toRadians(point.longitude)
    
    // Keep the x axis.
    val x = longRad

    // Mercator
    val y = ln(tan(latRad) - (1 / cos(latRad)))

    return Pair(x, y)
}

请注意,它返回的值在[0;pi/2]范围内,因此您可能需要进行缩放。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接