【问题标题】:Convert latitude/longitude point to a pixels (x,y) on mercator projection将纬度/经度点转换为墨卡托投影上的像素 (x,y)
【发布时间】:2012-12-29 01:50:32
【问题描述】:

我正在尝试将 lat/long 点转换为 2d 点,以便可以将其显示在世界图像上——这是一个墨卡托投影。

我已经看到了执行此操作的各种方法以及有关堆栈溢出的一些问题-我尝试了不同的代码 sn-ps,虽然我得到了正确的像素经度,但纬度似乎总是偏离不过越来越合理了。

我需要公式来考虑图像大小、宽度等。

我试过这段代码:

double minLat = -85.05112878;
double minLong = -180;
double maxLat = 85.05112878;
double maxLong = 180;

// Map image size (in points)
double mapHeight = 768.0;
double mapWidth = 991.0;

// Determine the map scale (points per degree)
double xScale = mapWidth/ (maxLong - minLong);
double yScale = mapHeight / (maxLat - minLat);

// position of map image for point
double x = (lon - minLong) * xScale;
double y = - (lat + minLat) * yScale;

System.out.println("final coords: " + x + " " + y);

在我正在尝试的示例中,纬度似乎偏离了大约 30 像素。有什么帮助或建议吗?

更新

基于这个问题:Lat/lon to xy

我已经尝试使用提供的代码,但我仍然遇到一些纬度转换问题,经度很好。

int mapWidth = 991;
int mapHeight = 768;

double mapLonLeft = -180;
double mapLonRight = 180;
double mapLonDelta = mapLonRight - mapLonLeft;

double mapLatBottom = -85.05112878;
double mapLatBottomDegree = mapLatBottom * Math.PI / 180;
double worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
double mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));

double x = (lon - mapLonLeft) * (mapWidth / mapLonDelta);
double y = 0.1;
if (lat < 0) {
    lat = lat * Math.PI / 180;
    y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
} else if (lat > 0) {
    lat = lat * Math.PI / 180;
    lat = lat * -1;
    y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
    System.out.println("y before minus: " + y);
    y = mapHeight - y;
} else {
    y = mapHeight / 2;
}
System.out.println(x);
System.out.println(y);

使用原始代码时,如果纬度值为正,则返回负点,因此我对其稍作修改并使用极纬度进行了测试——应该是点 0 和点 766,它工作正常。但是,当我尝试不同的纬度值例如:58.07(就在英国北部)时,它显示为西班牙北部。

【问题讨论】:

  • 你的公式只是线性插值,这实际上意味着你正在做一个等角投影而不是墨卡托。
  • 我已经更新了代码,但纬度仍然存在问题
  • 正如@Drew 提到的,如果您的地图是 Marcator 投影,您需要使用 Mercator 投影将 lat/lng 转换为 x/y。检查您的地图是横向墨卡托还是球形墨卡托,然后我们将使用公式...
  • 这是一个球形墨卡托投影

标签: java math maps 2d mercator


【解决方案1】:

墨卡托地图投影是兰伯特圆锥保形地图投影的一个特殊限制情况 赤道为单一标准平行线。纬度的所有其他平行线都是直线和经线 也是与赤道成直角的直线,等距。它是横向和 投影的倾斜形式。它很少用于土地测绘目的,但几乎普遍用于 导航图表。除了保形外,它还具有绘制在其上的直线的特殊属性 恒定轴承线。因此,导航员可以从直线航向线的角度得出他们的航向 使经络。 [1.]

从球面纬度 φ 和经度 λ 推导出东向和北向投影坐标的公式 是:

E = FE + R (λ – λₒ)
N = FN + R ln[tan(π/4 + φ/2)]   

其中 λO 是自然起源的经度,FE 和 FN 是东偏和北偏。 在球形墨卡托中,实际上并没有使用这些值,因此您可以将公式简化为

伪代码示例,因此可以适应每种编程语言。

latitude    = 41.145556; // (φ)
longitude   = -73.995;   // (λ)

mapWidth    = 200;
mapHeight   = 100;

// get x value
x = (longitude+180)*(mapWidth/360)

// convert from degrees to radians
latRad = latitude*PI/180;

// get y value
mercN = ln(tan((PI/4)+(latRad/2)));
y     = (mapHeight/2)-(mapWidth*mercN/(2*PI));

来源:

  1. OGP 测绘委员会,指导说明第 7 号,第 2 部分:坐标转换和转换
  2. Derivation of the Mercator projection
  3. National Atlas: Map Projections
  4. Mercator Map projection

编辑 用 PHP 创建了一个工作示例(因为我不擅长 Java)

https://github.com/mfeldheim/mapStuff.git

EDIT2

墨卡托投影的漂亮动画 https://amp-reddit-com.cdn.ampproject.org/v/s/amp.reddit.com/r/educationalgifs/comments/5lhk8y/how_the_mercator_projection_distorts_the_poles/?usqp=mq331AQJCAEoAVgBgAEB&amp_js_v=0.1

【讨论】:

  • SWEET iv 一直在寻找这个等式,简单地用 aaaages ty 的伪代码表示
  • 如果我想在我的坐标点上“缩放”,我可以在哪里乘以这个算法的缩放?
  • 是否故意使 y 也取决于 mapWidth 值 - 在您的伪代码中它说: y = (mapHeight/2)-(mapWidthmercN/(2*PI)); - 不应该是:y = (mapHeight/2)-(mapHeightmercN/(2*PI)); ?
  • 我们需要做些什么才能使用裁剪的地图 - 其边界不是完整的世界地图?
  • @Juicy 请试试这个修改后的公式y = (mapHeight/2)-(mapHeight*mercN/(2*PI));。我认为@Quasimondo 实际上是正确的。该公式在我的示例中确实有效,因为我使用了具有相同宽度和高度的地图
【解决方案2】:

你不能仅仅像这样从经度/纬度转换到 x/y,因为世界并不平坦。你看过这个帖子吗? Converting longitude/latitude to X/Y coordinate

更新 - 2013 年 1 月 18 日

我决定试一试,我是这样做的:-

public class MapService {
    // CHANGE THIS: the output path of the image to be created
    private static final String IMAGE_FILE_PATH = "/some/user/path/map.png";

    // CHANGE THIS: image width in pixel
    private static final int IMAGE_WIDTH_IN_PX = 300;

    // CHANGE THIS: image height in pixel
    private static final int IMAGE_HEIGHT_IN_PX = 500;

    // CHANGE THIS: minimum padding in pixel
    private static final int MINIMUM_IMAGE_PADDING_IN_PX = 50;

    // formula for quarter PI
    private final static double QUARTERPI = Math.PI / 4.0;

    // some service that provides the county boundaries data in longitude and latitude
    private CountyService countyService;

    public void run() throws Exception {
        // configuring the buffered image and graphics to draw the map
        BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDTH_IN_PX,
                                                        IMAGE_HEIGHT_IN_PX,
                                                        BufferedImage.TYPE_INT_RGB);

        Graphics2D g = bufferedImage.createGraphics();
        Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
        map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        RenderingHints renderHints = new RenderingHints(map);
        g.setRenderingHints(renderHints);

        // min and max coordinates, used in the computation below
        Point2D.Double minXY = new Point2D.Double(-1, -1);
        Point2D.Double maxXY = new Point2D.Double(-1, -1);

        // a list of counties where each county contains a list of coordinates that form the county boundary
        Collection<Collection<Point2D.Double>> countyBoundaries = new ArrayList<Collection<Point2D.Double>>();

        // for every county, convert the longitude/latitude to X/Y using Mercator projection formula
        for (County county : countyService.getAllCounties()) {
            Collection<Point2D.Double> lonLat = new ArrayList<Point2D.Double>();

            for (CountyBoundary countyBoundary : county.getCountyBoundaries()) {
                // convert to radian
                double longitude = countyBoundary.getLongitude() * Math.PI / 180;
                double latitude = countyBoundary.getLatitude() * Math.PI / 180;

                Point2D.Double xy = new Point2D.Double();
                xy.x = longitude;
                xy.y = Math.log(Math.tan(QUARTERPI + 0.5 * latitude));

                // The reason we need to determine the min X and Y values is because in order to draw the map,
                // we need to offset the position so that there will be no negative X and Y values
                minXY.x = (minXY.x == -1) ? xy.x : Math.min(minXY.x, xy.x);
                minXY.y = (minXY.y == -1) ? xy.y : Math.min(minXY.y, xy.y);

                lonLat.add(xy);
            }

            countyBoundaries.add(lonLat);
        }

        // readjust coordinate to ensure there are no negative values
        for (Collection<Point2D.Double> points : countyBoundaries) {
            for (Point2D.Double point : points) {
                point.x = point.x - minXY.x;
                point.y = point.y - minXY.y;

                // now, we need to keep track the max X and Y values
                maxXY.x = (maxXY.x == -1) ? point.x : Math.max(maxXY.x, point.x);
                maxXY.y = (maxXY.y == -1) ? point.y : Math.max(maxXY.y, point.y);
            }
        }

        int paddingBothSides = MINIMUM_IMAGE_PADDING_IN_PX * 2;

        // the actual drawing space for the map on the image
        int mapWidth = IMAGE_WIDTH_IN_PX - paddingBothSides;
        int mapHeight = IMAGE_HEIGHT_IN_PX - paddingBothSides;

        // determine the width and height ratio because we need to magnify the map to fit into the given image dimension
        double mapWidthRatio = mapWidth / maxXY.x;
        double mapHeightRatio = mapHeight / maxXY.y;

        // using different ratios for width and height will cause the map to be stretched. So, we have to determine
        // the global ratio that will perfectly fit into the given image dimension
        double globalRatio = Math.min(mapWidthRatio, mapHeightRatio);

        // now we need to readjust the padding to ensure the map is always drawn on the center of the given image dimension
        double heightPadding = (IMAGE_HEIGHT_IN_PX - (globalRatio * maxXY.y)) / 2;
        double widthPadding = (IMAGE_WIDTH_IN_PX - (globalRatio * maxXY.x)) / 2;

        // for each country, draw the boundary using polygon
        for (Collection<Point2D.Double> points : countyBoundaries) {
            Polygon polygon = new Polygon();

            for (Point2D.Double point : points) {
                int adjustedX = (int) (widthPadding + (point.getX() * globalRatio));

                // need to invert the Y since 0,0 starts at top left
                int adjustedY = (int) (IMAGE_HEIGHT_IN_PX - heightPadding - (point.getY() * globalRatio));

                polygon.addPoint(adjustedX, adjustedY);
            }

            g.drawPolygon(polygon);
        }

        // create the image file
        ImageIO.write(bufferedImage, "PNG", new File(IMAGE_FILE_PATH));
    }
}

结果:图片宽度 = 600 像素,图片高度 = 600 像素,图片内边距 = 50 像素

结果:图片宽度 = 300 像素,图片高度 = 500 像素,图片内边距 = 50 像素

【讨论】:

  • 我看了一下,作者的代码好像没有考虑到地图图片的大小?我已经用希望更准确的代码更新了我的问题,尽管仍然不正确。
  • 我更新了我的帖子...此代码考虑了指定的图像宽度和高度。
【解决方案3】:

Java版原Google Maps JavaScript API v3java脚本代码如下,可以正常运行

public final class GoogleMapsProjection2 
{
    private final int TILE_SIZE = 256;
    private PointF _pixelOrigin;
    private double _pixelsPerLonDegree;
    private double _pixelsPerLonRadian;

    public GoogleMapsProjection2()
    {
        this._pixelOrigin = new PointF(TILE_SIZE / 2.0,TILE_SIZE / 2.0);
        this._pixelsPerLonDegree = TILE_SIZE / 360.0;
        this._pixelsPerLonRadian = TILE_SIZE / (2 * Math.PI);
    }

    double bound(double val, double valMin, double valMax)
    {
        double res;
        res = Math.max(val, valMin);
        res = Math.min(res, valMax);
        return res;
    }

    double degreesToRadians(double deg) 
    {
        return deg * (Math.PI / 180);
    }

    double radiansToDegrees(double rad) 
    {
        return rad / (Math.PI / 180);
    }

    PointF fromLatLngToPoint(double lat, double lng, int zoom)
    {
        PointF point = new PointF(0, 0);

        point.x = _pixelOrigin.x + lng * _pixelsPerLonDegree;       

        // Truncating to 0.9999 effectively limits latitude to 89.189. This is
        // about a third of a tile past the edge of the world tile.
        double siny = bound(Math.sin(degreesToRadians(lat)), -0.9999,0.9999);
        point.y = _pixelOrigin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) *- _pixelsPerLonRadian;

        int numTiles = 1 << zoom;
        point.x = point.x * numTiles;
        point.y = point.y * numTiles;
        return point;
     }

    PointF fromPointToLatLng(PointF point, int zoom)
    {
        int numTiles = 1 << zoom;
        point.x = point.x / numTiles;
        point.y = point.y / numTiles;       

        double lng = (point.x - _pixelOrigin.x) / _pixelsPerLonDegree;
        double latRadians = (point.y - _pixelOrigin.y) / - _pixelsPerLonRadian;
        double lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
        return new PointF(lat, lng);
    }

    public static void main(String []args) 
    {
        GoogleMapsProjection2 gmap2 = new GoogleMapsProjection2();

        PointF point1 = gmap2.fromLatLngToPoint(41.850033, -87.6500523, 15);
        System.out.println(point1.x+"   "+point1.y);
        PointF point2 = gmap2.fromPointToLatLng(point1,15);
        System.out.println(point2.x+"   "+point2.y);
    }
}

public final class PointF 
{
    public double x;
    public double y;

    public PointF(double x, double y)
    {
        this.x = x;
        this.y = y;
    }
}

【讨论】:

  • 平铺大小由什么定义?我正在尝试在 Unity 中使用类似的东西。是图片大小吗?或者它是谷歌地图或网络上的墨卡托投影的常数?
  • 瓦片大小是地图瓦片的图像大小,当您从谷歌服务器收到地图瓦片图像时,它是 256x256 大小的 png 文件
  • 这适用于纬度,但经度值相差甚远。缺少什么?
【解决方案4】:

我想指出程序边界中的代码应该是

double bound(double val, double valMin, double valMax)
{
    double res;
    res = Math.max(val, valMin);
    res = Math.min(res, valMax);
    return res;
}

【讨论】:

  • 谢谢。我更新了@nik 的答案以包含此内容。
【解决方案5】:

仅限 JAVA?

Python 代码在这里!参考Convert latitude/longitude point to a pixels (x,y) on mercator projection

import math
from numpy import log as ln

# Define the size of map
mapWidth    = 200
mapHeight   = 100


def convert(latitude, longitude):
    # get x value
    x = (longitude + 180) * (mapWidth / 360)

    # convert from degrees to radians
    latRad = (latitude * math.pi) / 180

    # get y value
    mercN = ln(math.tan((math.pi / 4) + (latRad / 2)))
    y     = (mapHeight / 2) - (mapWidth * mercN / (2 * math.pi))
    
    return x, y

print(convert(41.145556, 121.2322))

答案:

(167.35122222222225, 24.877939817552335)

【讨论】:

    【解决方案6】:

    我是新来的,只是为了写作,因为我多年来一直关注社区。我很高兴能够做出贡献。

    好吧,我几乎花了一天的时间来寻找那个,你的问题鼓励我继续寻找。

    我得到了以下功能,它有效!本文致谢:https://towardsdatascience.com/geotiff-coordinate-querying-with-javascript-5e6caaaf88cf

    var bbox = [minLong, minLat, maxLong, maxLat];
    var pixelWidth = mapWidth;
    var pixelHeight = mapHeight;
    var bboxWidth = bbox[2] - bbox[0];
    var bboxHeight = bbox[3] - bbox[1];
    
    var convertToXY = function(latitude, longitude) {
        var widthPct = ( longitude - bbox[0] ) / bboxWidth;
        var heightPct = ( latitude - bbox[1] ) / bboxHeight;
        var x = Math.floor( pixelWidth * widthPct );
        var y = Math.floor( pixelHeight * ( 1 - heightPct ) );
        return { x, y };
    }
    

    【讨论】:

      【解决方案7】:
       public static String getTileNumber(final double lat, final double lon, final int zoom) {
       int xtile = (int)Math.floor( (lon + 180) / 360 * (1<<zoom) ) ;
       int ytile = (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 /  Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom) ) ;
      if (xtile < 0)
       xtile=0;
      if (xtile >= (1<<zoom))
       xtile=((1<<zoom)-1);
      if (ytile < 0)
       ytile=0;
      if (ytile >= (1<<zoom))
       ytile=((1<<zoom)-1);
      return("" + zoom + "/" + xtile + "/" + ytile);
       }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-06
        • 1970-01-01
        • 2020-09-02
        • 1970-01-01
        相关资源
        最近更新 更多