在给定图片上将long / lat转换为像素x / y


67

我有莫斯科的城市地图。我们修改了带有某些艺术元素的Google Maps图像,但GPS坐标和像素之间的关系保持不变。

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

理想情况下,我可以使用Javascript执行此操作,但PHP可以。


我知道在小范围内(例如在城市范围内),它就足够简单(有必要了解哪些地理坐标具有图片角之一,然后学习轴上图片中地理坐标中一个像素的“价格”)分别是OX和OY)。

但是在大范围(国家范围)上,一个像素的“价格”将不是一个常数,并且变化很大,因此上述方法无法应用。

如何在国家范围内解决问题?


更新:

我没有使用API​​ Google Maps,我只有:对象的地理坐标(它们来自google maps),我的站点上仍然有一张简单的图片*。gif,我必须在其中绘制一个对应的地理坐标点。


您是否正在使用API​​的v2或v3?
彼得·贝利

2
您所说的“在Google坐标中”到底是什么意思?具体来说,您是说您在墨卡托(Mercator)东边还是北边都有坐标,或者在纬度和经度上都有地理坐标?
丹尼尔·普里登

此外,还可能对你有用:sharpgis.net/post/2007/07/27/...
丹尼尔Pryden

@Daniel Pryden,根据您的评论,我将纠正我的问题。我真的是说地理坐标。
加里宁

1
@Peter Bailey,我不使用API​​。我直接从Google地图获取的地理坐标;网站上以简单的图片* .gif形式显示了“城市地图”。
加里宁

Answers:


70

所有这些的关键是了解地图投影。正如其他人指出的那样,变形的原因是这样的事实,即球形(或更准确地说是椭圆形)的地球被投影到一个平面上。

为了实现您的目标,您首先必须了解有关数据的两件事:

  1. 您的地图所在的投影。如果它们是纯粹从Google Maps派生的,则可能是使用球形墨卡托投影
  2. 您的纬度/经度坐标正在使用的地理坐标系。这可能会有所不同,因为在地球上找到纬度/经度的方法不同。WGS84是在大多数网络映射应用程序和GPS中使用的最常见的GCS 。

我假设您的数据在这些坐标系中。

球形墨卡托投影定义了一个以米为单位的地球表面坐标对。这意味着,对于每个纬度/经度坐标,都有一个匹配的仪表/仪表坐标。这使您可以使用以下过程进行转换:

  1. 找到WGS84图像边角的经度/纬度。
  2. 将WGS纬度/经度转换为球形墨卡托投影。那里有转换工具,我最喜欢的是使用PROJ4项目中的cs2cs工具。
  3. 您可以安全地进行简单的线性变换,以在图像上的点与球形墨卡托投影中的地球上的点之间进行转换,然后再返回。

为了从WGS84点转到图像上的像素,现在需要执行以下步骤:

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

您可以像这样使用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 is not defined。如何解决问题?
加里宁

17

您将必须使用您的语言来实现Google Maps 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 2012年

谢谢@Jader Dias。您的代码为我工作。我已经采取了大小为256 * 256的静态Google地图。
Shailesh Jaiswal,2012年

FromPixelToCoordinates方法不应该返回PointF(Convert.ToSingle(longitude), Convert.ToSingle(latitude))吗?[+1]
TLama 2013年

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 Map显示屏的屏幕抓图,那么这将为您提供经纬度坐标图像上的正确像素坐标。

如果您要捕获图块图像并将其自己缝合在一起,则事情会变得更加棘手,因为完整图块集的区域将位于显示的地图区域之外。

在这种情况下,您需要使用左上角图像图块的左值和上角值作为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



5

您要处理的翻译与Map Projection有关,这就是我们世界的球面如何转换为二维渲染的方式。有多种方法(投影)可以在2D曲面上渲染世界。

如果您的地图仅使用特定的投影(墨卡托比较流行),则应该能够找到方程式,一些示例代码和/或某些库(例如,一个墨卡托解决方案-将纬度/经度转换为X / Y坐标。如果这样做不行,我确定您可以找到其他示例-https: //stackoverflow.com/search?q=mercator。如果您的图像不是使用墨卡托投影的地图,我需要确定它使用什么投影来找到正确的平移方程。

如果您尝试支持多种地图投影(您想支持使用不同投影的许多不同地图),那么您肯定要使用像PROJ.4这样的库,但是我不确定您会从Javascript找到什么或PHP。


2
PROJ.4很棒,但是当您已经在处理要从Google Maps获取的地图时,仅使用它已经提供的坐标转换API会更容易。这可以处理投影转换(Google Maps似乎使用Mercatorhttp://code.google.com/apis/maps/documentation/reference.html#GMercatorProjection),并且可以轻松地将纬度/经度转换为像素。
艾伦·唐纳利

@Donnelly-非常好-她确实说她的地图图像与Google的地图图像匹配。GMercatorProjection本身应该是一个答案-您应该将其添加为单独的答案[或者,我将对此答案进行更新以包括它]。我确实读过她问题的第二部分(该行下面的部分),以便更全面地了解如何将经/纬度坐标匹配到一般地图图像(不一定是Google),因此我尝试给出一个更一般的答案。我可能已经对她的问题读了很多东西。
Bert F



3

要考虑的重要事项之一是投影的“缩放”级别(尤其是对于Google地图)。

正如Google解释的那样:

在缩放级别1时,地图由4个256x256像素的图块组成,因此像素空间为512x512。在缩放级别19时,可以使用0到256 * 2 ^ 19之间的值引用地图上的每个x和y像素

(请参阅https://developers.google.com/maps/documentation/javascript/maptypes?hl=zh-CN#MapCoordinates

为了考虑“缩放”值,我建议以下简单有效的deltaLonPerDeltaXdeltaLatPerDeltaY函数。尽管x像素和经度严格成正比,但对于y像素和纬度却不是这种情况,因为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 2014年


0

为此而苦苦挣扎-同时拥有openstreet地图和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/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.