Konvertieren Sie den Breiten- / Längengradpunkt bei der Mercator-Projektion in Pixel (x, y)

69

Ich versuche, einen Lat / Long-Punkt in einen 2d-Punkt umzuwandeln, damit ich ihn auf einem Bild der Welt anzeigen kann - was eine Mercator-Projektion ist.

Ich habe verschiedene Möglichkeiten gesehen, dies zu tun, und ein paar Fragen zum Stapelüberlauf - ich habe die verschiedenen Code-Schnipsel ausprobiert und obwohl ich den richtigen Längengrad für Pixel erhalte, scheint der Breitengrad immer aus zu sein - scheint jedoch vernünftiger zu werden.

Ich brauche die Formel, um die Bildgröße, -breite usw. zu berücksichtigen.

Ich habe diesen Code ausprobiert:

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);

In dem Beispiel, das ich versuche, scheint der Breitengrad um etwa 30 Pixel versetzt zu sein. Hilfe oder Rat?

Aktualisieren

Basierend auf dieser Frage: Lat / lon bis xy

Ich habe versucht, den bereitgestellten Code zu verwenden, habe aber immer noch Probleme mit der Breitengradkonvertierung. Der Längengrad ist in Ordnung.

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);

Wenn der ursprüngliche Code verwendet wird und der Breitengradwert positiv ist, wird ein negativer Punkt zurückgegeben. Daher habe ich ihn geringfügig geändert und mit den extremen Breitengraden getestet. Diese sollten Punkt 0 und Punkt 766 sein. Er funktioniert einwandfrei. Wenn ich jedoch einen anderen Breitengradwert versuche, z. B. 58.07 (nördlich von Großbritannien), wird dieser als nördlich von Spanien angezeigt.

betrunkener Schlüssel
quelle
Ihre Formeln sind nur lineare Interpolation, was effektiv impliziert, dass Sie eine gleichwinklige Projektion anstelle eines Mercators durchführen.
Drew Hall
Ich habe den Code aktualisiert, obwohl ich immer noch Probleme mit dem Breitengrad habe
betrunkener Schlüssel
Wie bei @Drew erwähnt, müssen Sie, wenn Ihre Karte eine Marcator-Projektion ist, das lat / lng mithilfe einer Mercator-Projektion in x / y konvertieren. Überprüfen Sie, ob Ihre Karte Transverse Mercator oder Spherical Mercator ist, dann kommen wir zu den Formeln ...
Michael
Es ist eine sphärische Mercator-Projektion
betrunkener Schlüssel

Antworten:

131

Die Mercator-Kartenprojektion ist ein spezieller Grenzfall für die Lambert Conic Conformal-Kartenprojektion mit dem Äquator als einzelner Standardparallel. Alle anderen Breitengradparallelen sind gerade Linien, und die Meridiane sind ebenfalls gerade Linien im rechten Winkel zum Äquator mit gleichem Abstand. Es ist die Basis für die Quer- und Schrägformen der Projektion. Es wird wenig für Landkartierungszwecke verwendet, ist jedoch für Navigationskarten nahezu universell einsetzbar. Es ist nicht nur konform, sondern hat auch die besondere Eigenschaft, dass darauf gezeichnete gerade Linien Linien konstanter Peilung sind. So können Navigatoren ihren Kurs aus dem Winkel ableiten, den die gerade Kurslinie mit den Meridianen bildet. [1.]

Mercator-Projektion

Die Formeln zum Ableiten der projizierten Ost- und Nordkoordinaten aus dem sphärischen Breitengrad φ und dem Längengrad λ sind:

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

Dabei ist λ O die Länge des natürlichen Ursprungs und FE und FN sind falscher Osten und falscher Norden. Im sphärischen Mercator werden diese Werte tatsächlich nicht verwendet, sodass Sie die Formel auf vereinfachen können

Ableitung der Mercator-Projektion (Wikipedia)

Pseudocode-Beispiel, so dass dies an jede Programmiersprache angepasst werden kann.

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));

Quellen:

  1. OGP Geomatics Committee, Leitfaden Nr. 7, Teil 2: Koordinatenumwandlungen und -transformationen
  2. Ableitung der Mercator-Projektion
  3. Nationaler Atlas: Kartenprojektionen
  4. Mercator Kartenprojektion

BEARBEITEN Erstellt ein funktionierendes Beispiel in PHP (weil ich an Java lutsche)

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

EDIT2

Nizza Animation der Mercator - Projektion 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&_js_v=0.1

Michel Feldheim
quelle
1
SWEET Ich habe nach dieser Gleichung gesucht, die einfach in Pseudocode für aaaages ty gesetzt wurde
AngryDuck
3
Wenn ich meine Koordinatenpunkte "zoomen" möchte, wo kann ich den Zoom in diesem Algorithmus multiplizieren?
Matheusvmbruno
5
Ist es beabsichtigt, dass y auch vom mapWidth-Wert abhängt - in Ihrem Pseudocode heißt es: y = (mapHeight / 2) - (mapWidth mercN / (2 * PI)); - sollte das nicht sein: y = (mapHeight / 2) - (mapHeight mercN / (2 * PI)); ?
Quasimondo
5
Was müssen wir tun, damit dies mit einer beschnittenen Karte funktioniert - der Karte, deren Grenzen keine vollständige Weltkarte sind?
Gerry
2
@ Juicy bitte versuchen Sie diese modifizierte Formel y = (mapHeight/2)-(mapHeight*mercN/(2*PI));. Ich denke @Quasimondo ist eigentlich richtig. Die Formel hat in meinem Beispiel funktioniert, weil ich eine Karte mit der gleichen Breite und Höhe verwendet habe
Michel Feldheim
13

Sie können nicht einfach so von Längen- / Breitengrad zu x / y transponieren, weil die Welt nicht flach ist. Hast du dir diesen Beitrag angesehen? Konvertieren von Längen- / Breitengrad in X / Y-Koordinate

UPDATE - 18.01.13

Ich habe beschlossen, dies zu versuchen, und so mache ich es:

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));
    }
}

ERGEBNIS: Bildbreite = 600 Pixel, Bildhöhe = 600 Pixel, Bildauffüllung = 50 Pixel

Geben Sie hier die Bildbeschreibung ein

ERGEBNIS: Bildbreite = 300px, Bildhöhe = 500px, Bildauffüllung = 50px

Geben Sie hier die Bildbeschreibung ein

limc
quelle
1
Ich habe es mir angesehen, es scheint nicht so, als würde der Code des Autors die Größe des Kartenbildes berücksichtigen. Ich habe meine Frage mit hoffentlich genauerem Code aktualisiert - obwohl immer noch nicht richtig.
Drunkmonkey
Ich habe meinen Beitrag aktualisiert ... dieser Code berücksichtigt die angegebene Bildbreite und -höhe.
Limc
11

Die Java-Version des ursprünglichen Java Mcript- Skriptcodes der Google Maps JavaScript API v3 lautet wie folgt und funktioniert problemlos

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;
    }
}
nik
quelle
Was definiert die Fliesengröße? Ich versuche so etwas in Unity zu verwenden. Ist es die Bildgröße? oder ist es eine Konstante für Google Maps oder Mercator-Projektion im Web?
John Demetriou
Kachelgröße ist die Bildgröße der Kartenkachel. Wenn Sie ein Kartenkachelbild von Google Servern erhalten, ist es eine 256x256-Größe der PNG-Datei
Nik
Dies funktioniert für Breitengrade, aber Längengrade sind weit entfernt. Was fehlt?
Portfoliobuilder
3

Ich möchte darauf hinweisen, dass der Code in den Prozedurgrenzen gelesen werden sollte

double bound(double val, double valMin, double valMax)
{
    double res;
    res = Math.max(val, valMin);
    res = Math.min(res, valMax);
    return res;
}
Jeremy C.
quelle
1
Danke dafür. Ich habe die Antwort von @ nik aktualisiert, um dies aufzunehmen.
Adam0101
0

Nur JAVA?

Python-Code hier! Siehe Breiten- / Längengradpunkt in Pixel (x, y) in Mercator-Projektion konvertieren

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))

Antworten:

(167.35122222222225, 24.877939817552335)
武 状元
quelle
-1
 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);
 }
}
Farsheed
quelle