在给定的图片上将经纬度转换为像素x/y

68

我有一份莫斯科市地图。我们在谷歌地图的图像上添加了一些艺术元素,但GPS坐标和像素之间的相对关系保持不变。

问题:如何将我们拥有的各种数据点的GPS坐标转换为图像中的像素坐标?

理想情况下,我可以用JavaScript来完成,但PHP也可以。


我知道在小尺度(例如城市尺度)上很容易做到这一点(需要了解一个图片角落的地理坐标,然后分别学习在OX和OY轴上图片上一个像素在地理坐标上的“价格”)。

但在大尺度(国家尺度)上,一个像素的“价格”将不是恒定的,并且会变化得足够强烈,因此上述描述的方法无法应用。

如何解决大尺度的问题?


更新:

我没有使用谷歌地图API,我只有:物体的地理坐标(它们来自谷歌地图),我仍然在我的网站上拥有一个简单的*.gif图片,在其中我必须画出对应于地理坐标的点。


你正在使用 API 的 v2 还是 v3 版本? - Peter Bailey
2
你所说的“在谷歌坐标系中”是指什么?具体来说,你是指你拥有墨卡托东线和北线的坐标还是地理坐标系下的纬度和经度? - Daniel Pryden
此外,这可能对您有用:http://www.sharpgis.net/post/2007/07/27/The-Microsoft-Live-Maps-and-Google-Maps-projection.aspx - Daniel Pryden
@Daniel Pryden,根据您的评论,我会修改我的问题。我真正的意思是地理坐标。 - Kalinin
我仍然想知道您正在使用哪个版本的API。 - Peter Bailey
1
@Peter Bailey,我不使用API。地理坐标我直接从谷歌地图获取;"城市地图"以简单的gif图片形式嵌入网站。 - Kalinin
10个回答

73
这一切的关键在于理解地图投影。正如其他人所指出的,扭曲的原因是因为球形(或更准确地说是椭圆形)地球被投射到平面上。
为了实现您的目标,您首先必须了解有关数据的两件事:
1. 您的地图所使用的投影方式。如果它们纯粹是从Google Maps派生而来,则很可能使用球形墨卡托投影。 2. 您的纬度/经度坐标所使用的地理坐标系。这可能会有所不同,因为有不同的方法来定位地球上的纬度/经度。最常见的GCS用于大多数网络地图应用程序和GPS,即WGS84
我假设您的数据采用这些坐标系统。
球形墨卡托投影将地球表面的坐标对定义为米。这意味着,对于每个纬度/经度坐标,都有一个相应的米/米坐标。这使您可以使用以下过程进行转换:
  1. 查找图像角落的WGS84经纬度。
  2. 将WGS经纬度转换为球形墨卡托投影。有许多转换工具,我最喜欢使用PROJ4项目中的cs2cs工具。
  3. 可以安全地进行简单的线性变换,以在球形Mercator投影上转换图像上的点和地球上的点,并返回。

现在,从WGS84点到图像像素的过程如下:

  1. 将经度/纬度投影到球形Mercator。这可以使用proj4js库完成。
  2. 使用上面发现的线性关系将球形Mercator坐标转换为图像像素坐标。

你可以这样使用proj4js库:

// include the library
<script src="lib/proj4js-combined.js"></script>  //adjust the path for your server
                                                 //or else use the compressed version
// creating source and destination Proj4js objects
// once initialized, these may be re-used as often as needed
var source = new Proj4js.Proj('EPSG:4326');    //source coordinates will be in Longitude/Latitude, WGS84
var dest = new Proj4js.Proj('EPSG:3785');     //destination coordinates in meters, global spherical mercators projection, see http://spatialreference.org/ref/epsg/3785/


// transforming point coordinates
var p = new Proj4js.Point(-76.0,45.0);   //any object will do as long as it has 'x' and 'y' properties
Proj4js.transform(source, dest, p);      //do the transformation.  x and y are modified in place

//p.x and p.y are now EPSG:3785 in meters

@fmark,按照您所说的插入了代码,但脚本在Proj4js.Proj = OpenLayers.Class({...})这一行报错,提示“OpenLayers未定义”。该如何解决这个问题? - Kalinin
我该如何找到图像角落的WGS84纬度/经度? - Gurkiran Singh

18

你需要在你的语言中实现Google地图API的投影。我有这个的C#源代码:

public class GoogleMapsAPIProjection
{
    private readonly double PixelTileSize = 256d;
    private readonly double DegreesToRadiansRatio = 180d / Math.PI;
    private readonly double RadiansToDegreesRatio = Math.PI / 180d;
    private readonly PointF PixelGlobeCenter;
    private readonly double XPixelsToDegreesRatio;
    private readonly double YPixelsToRadiansRatio;

    public GoogleMapsAPIProjection(double zoomLevel)
    {
        var pixelGlobeSize = this.PixelTileSize * Math.Pow(2d, zoomLevel);
        this.XPixelsToDegreesRatio = pixelGlobeSize / 360d;
        this.YPixelsToRadiansRatio = pixelGlobeSize / (2d * Math.PI);
        var halfPixelGlobeSize = Convert.ToSingle(pixelGlobeSize / 2d);
        this.PixelGlobeCenter = new PointF(
            halfPixelGlobeSize, halfPixelGlobeSize);
    }

    public PointF FromCoordinatesToPixel(PointF coordinates)
    {
        var x = Math.Round(this.PixelGlobeCenter.X
            + (coordinates.X * this.XPixelsToDegreesRatio));
        var f = Math.Min(
            Math.Max(
                 Math.Sin(coordinates.Y * RadiansToDegreesRatio),
                -0.9999d),
            0.9999d);
        var y = Math.Round(this.PixelGlobeCenter.Y + .5d * 
            Math.Log((1d + f) / (1d - f)) * -this.YPixelsToRadiansRatio);
        return new PointF(Convert.ToSingle(x), Convert.ToSingle(y));
    }

    public PointF FromPixelToCoordinates(PointF pixel)
    {
        var longitude = (pixel.X - this.PixelGlobeCenter.X) /
            this.XPixelsToDegreesRatio;
        var latitude = (2 * Math.Atan(Math.Exp(
            (pixel.Y - this.PixelGlobeCenter.Y) / -this.YPixelsToRadiansRatio))
            - Math.PI / 2) * DegreesToRadiansRatio;
        return new PointF(
            Convert.ToSingle(latitude),
            Convert.ToSingle(longitude));
    }
}

来源:

http://code.google.com/p/geographical-dot-net/source/browse/trunk/GeographicalDotNet/GeographicalDotNet/Projection/GoogleMapsAPIProjection.cs


我仔细检查了一遍,这与上面提到的gheat代码相匹配,该代码是从Google Maps服务器实际提取的JS中提取的。因此,我认为我们可以说:问题解决了。 - olefevre
谢谢@Jader Dias。你的代码对我很有用。我已经获取了一个大小为256*256的静态谷歌地图。 - Shailesh Jaiswal
FromPixelToCoordinates 方法难道不应该返回 PointF(Convert.ToSingle(longitude), Convert.ToSingle(latitude)) 吗?[+1] - TLama

8
所以你想要获取经纬度坐标并找出该位置在图像上的像素坐标吗?
主要的GMap2类提供了从显示地图上的像素到经纬度坐标的转换:
Gmap2.fromLatLngToContainerPixel(latlng)

例如:

var gmap2 = new GMap2(document.getElementById("map_canvas"));
var geocoder = new GClientGeocoder();

geocoder.getLatLng( "1600 Pennsylvania Avenue NW Washington, D.C. 20500",
    function( latlng ) {
        var pixel_coords = gmap2.fromLatLngToContainerPixel(latlng);

        window.alert( "The White House is at pixel coordinates (" + 
            pixel_coodrs.x + ", " + pixel_coords.y + ") on the " +
            "map image shown on this page." );
    }
);

假设您的地图图像是Google地图显示的屏幕截图,那么这将为您提供lat/long坐标在该图像上的正确像素坐标。
如果您正在抓取瓦片图像并自己拼接它们,则情况会更加复杂,因为完整瓦片集的区域将位于显示地图的区域之外。
在这种情况下,您需要使用左上角图像瓦片的左侧和顶部值作为从fromLatLngToContainerPixel(latlng:GLatLng)得到的坐标的偏移量,从x坐标中减去左侧坐标,并从y坐标中减去顶部坐标。因此,如果左上角的图像位于(-50,-122)(左,上),并且fromLatLngToContainerPixel()告诉您纬度/经度在像素坐标(150,320),则在从瓦片拼接而成的图像上,该坐标的真实位置为(150-(-50),320-(-122)),即(200,442)。
还可以使用类似的GMap2坐标转换函数:
GMap2.fromLatLngToDivPixel(latlng:GLatLng)

将给出正确的经纬度到像素转换,适用于拼接瓦片的情况 - 我尚未测试过这一点,也不是从 API 文档中100%清楚。
更多信息请参见此处: http://code.google.com/apis/maps/documentation/reference.html#GMap2.Methods.Coordinate-Transformations

7

您可以查看在gheat上使用的代码(链接),它是从js转换到python的。


链接已离线。 - mcont

4
您所涉及的翻译与地图投影有关,这是将我们世界的球面表面转换为二维渲染的方法。有多种方法(投影)可以在二维表面上呈现世界。
如果您的地图仅使用特定的投影(墨卡托投影很受欢迎),则应该能够找到方程式,一些示例代码和/或某个库(例如一个墨卡托解决方案-将纬度/经度转换为X/Y坐标)。如果这样做不行,我相信您可以找到其他示例-https://stackoverflow.com/search?q=mercator。如果您的图像不是使用墨卡托投影的地图,则需要确定它使用的是哪种投影以找到正确的转换方程式。

如果您想支持多个地图投影(即支持使用不同投影的许多不同地图),那么您绝对需要使用像PROJ.4这样的库,但我不确定您在Javascript或PHP中会找到什么。


2
PROJ.4很好,但是当你已经在处理从Google Maps获取的地图时,直接使用它提供的坐标转换API会更容易。这个API可以处理投影转换(Google Maps似乎使用Mercator http://code.google.com/apis/maps/documentation/reference.html#GMercatorProjection),并且使得从纬度/经度到像素的转换变得容易。 - Alan Donnelly
@Donnelly - 非常好的观点 - 她确实说她的地图图像与谷歌的地图图像相匹配。 GMercatorProjection本身就应该是一个答案 - 你应该将它作为一个单独的答案添加进去[或者,我会更新这个答案并包括它]。我确实阅读了她问题的第二部分(下划线以下的部分),认为这是关于如何将纬度/经度坐标与地图图像匹配的一般性问题(不一定是谷歌地图),所以我试图给出一个更一般的回答。也许我对她的问题理解太多了。 - Bert F

3
考虑到投影的“缩放”级别(特别是对于Google Maps),这是需要考虑的重要事项之一。
正如Google所解释的那样:
在缩放级别1时,地图由4个256x256像素瓦片组成,从而形成512x512的像素空间。在缩放级别19时,地图上的每个x和y像素都可以使用0到256 * 2 ^ 19之间的值进行引用。
(请参见https://developers.google.com/maps/documentation/javascript/maptypes?hl=en#MapCoordinates
为了考虑“缩放”值,我建议使用下面简单有效的deltaLonPerDeltaX和deltaLatPerDeltaY函数。虽然x像素和经度严格成比例,但y像素和纬度不是这种情况,公式需要初始纬度。
// Adapted from : http://blog.cppse.nl/x-y-to-lat-lon-for-google-maps


window.geo = {

    glOffset: Math.pow(2,28), //268435456,
    glRadius:  Math.pow(2,28) / Math.PI,
    a: Math.pow(2,28),
    b: 85445659.4471,
    c: 0.017453292519943,
    d: 0.0000006705522537,
    e: Math.E, //2.7182818284590452353602875,
    p: Math.PI / 180,

    lonToX: function(lon) {
        return Math.round(this.glOffset + this.glRadius * lon * this.p);
    },

    XtoLon: function(x) {
        return -180 + this.d * x;
    },

    latToY: function(lat) {
        return Math.round(this.glOffset - this.glRadius *
                          Math.log((1 + Math.sin(lat * this.p)) /
                          (1 - Math.sin(lat * this.p))) / 2);
    },

    YtoLat: function(y) {
        return Math.asin(Math.pow(this.e,(2*this.a/this.b - 2*y/this.b)) /
                                 (Math.pow(this.e, (2*this.a/this.b - 2*y/this.b))+1) -
                                 1/(Math.pow(this.e, (2*this.a/this.b - 2*y/this.b))+1)
                        ) / this.c;
    },

    deltaLonPerDeltaX: function(deltaX, zoom) {
        // 2^(7+zoom) pixels <---> 180 degrees
        return deltaX * 180 / Math.pow(2, 7+zoom);
    },

    deltaLatPerDeltaY: function(deltaY, zoom, startLat) {
        // more complex because of the curvature, we calculte it by difference
        var startY = this.latToY(startLat),
            endY = startY + deltaY * Math.pow(2, 28-7-zoom),
            endLat = this.YtoLat(endY);

        return ( endLat - startLat ); // = deltaLat
    }
}

你能举个使用的例子吗? - y_nk

3

这与我的问题相去甚远,我不知道是否可以使用。 - Kalinin

3
您需要公式将纬度和经度转换为直角坐标。有很多公式可供选择,每个公式都会以不同的方式扭曲地图。 Wolfram MathWorld有一个很好的收集:http://mathworld.wolfram.com/MapProjection.html,请查看“See Also”链接。

2

0

我遇到了一些困难 - 我同时拥有OpenStreetMap和Google街景地图,并希望投影外部图形图像

var map = new OpenLayers.Map({
            div:"map-id",
            allOverlays: true
    });
    var osm = new OpenLayers.Layer.OSM("OpenStreeMao");
    var gmap = new OpenLayers.Layer.Google("Google Streets", {visibility: false});

    map.addLayers([osm,gmap]);

    var vectorLayer = new OpenLayers.Layer.Vector("IconLayer");


    var lonlatObject = new OpenLayers.LonLat(24.938622,60.170421).transform(
            new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()
    );
    console.log(lonlatObject);

    var point = new OpenLayers.Geometry.Point(lonlatObject.lon, lonlatObject.lat);
    console.log(point);

    var point2 = new OpenLayers.Geometry.Point(lonlatObject.x, lonlatObject.y);
    console.log(point2);

    var feature = new OpenLayers.Feature.Vector(point, null, {
        externalGraphic:  "http://cdn1.iconfinder.com/data/icons/SUPERVISTA/networking/png/72/antenna.png",
        graphicWidth: 72,
        graphicHeight: 72,
        fillOpacity: 1
    });


    vectorLayer.addFeatures(feature);

    map.addLayer(vectorLayer);


    map.setCenter(
            new OpenLayers.LonLat(24.938622,60.170421).transform(
            new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()
            ),
            12);

     map.addControl(new OpenLayers.Control.LayerSwitcher());

http://jsfiddle.net/alexcpn/N9dLN/8/


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