71 votes

Convertir un point de latitude/longitude en un pixel (x,y) sur la projection Mercator.

J'essaie de convertir un point lat/long en un point 2d afin de pouvoir l'afficher sur une image du monde - qui est une projection mercator.

J'ai vu plusieurs façons de procéder et quelques questions sur stack overflow. J'ai essayé les différents extraits de code et bien que j'obtienne la longitude correcte en pixel, la latitude est toujours erronée - cela semble toutefois devenir plus raisonnable.

J'ai besoin que la formule prenne en compte la taille de l'image, sa largeur, etc.

J'ai essayé ce bout de code :

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

La latitude semble être décalée d'environ 30px dans l'exemple que j'essaie. Une aide ou un conseil ?

Mise à jour

En se basant sur cette question : Lat/lon à xy

J'ai essayé d'utiliser le code fourni mais j'ai toujours des problèmes avec la conversion de la latitude, la longitude est bonne.

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

Lorsque j'utilise le code original, si la valeur de la latitude est positive, il renvoie un point négatif. Je l'ai donc légèrement modifié et testé avec les latitudes extrêmes, qui devraient être le point 0 et le point 766, et cela fonctionne bien. Cependant, lorsque j'essaie une autre valeur de latitude, par exemple 58.07 (juste au nord du Royaume-Uni), il affiche le nord de l'Espagne.

1 votes

Vos formules ne sont qu'une interpolation linéaire, ce qui implique effectivement que vous faites une projection équirectangulaire plutôt qu'une projection Mercator.

0 votes

J'ai mis à jour le code, mais j'ai toujours des problèmes avec la latitude.

1 votes

Comme l'a mentionné @Drew, si votre carte est une projection de Mercator, vous devrez convertir la lat/lng en x/y en utilisant une projection de Mercator. Vérifiez si votre carte est une Mercator transverse ou une Mercator sphérique, puis nous passerons aux formalités...

138voto

Michel Feldheim Points 7222

La projection cartographique de Mercator est un cas limite spécial de la projection cartographique conforme à la conique de Lambert avec l'équateur comme unique parallèle standard. Tous les autres parallèles de latitude sont des lignes droites et les méridiens sont également des lignes droites à angle droit par rapport à l'équateur, également espacées. C'est la base pour les formes transversales et obliques de la projection. Elle est peu utilisée pour la cartographie terrestre mais elle est d'un usage quasi universel pour les cartes de navigation. En plus d'être conforme, elle a la propriété particulière que les lignes droites tracées sur elle sont lignes de relèvement constant. Ainsi, les navigateurs peuvent dériver leur route à partir de l'angle que la ligne droite de parcours avec les méridiens. [1.]

Mercator projection

Les formules pour dériver les coordonnées projetées Easting et Northing à partir de la latitude φ et de la longitude λ sphériques. sont :

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

où λ O est la longitude d'origine naturelle et FE et FN sont la fausse abscisse et la fausse ordonnée. Dans le système Mercator sphérique, ces valeurs ne sont en fait pas utilisées, de sorte que vous pouvez simplifier la formule comme suit

derivation of the mercator projection (wikipedia)

Exemple de pseudo-code, qui peut donc être adapté à tout langage de programmation.

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

Sources :

  1. Comité géomatique de l'OGP, Note d'orientation numéro 7, partie 2 : Conversions et transformations de coordonnées
  2. Dérivation de la projection de Mercator
  3. Atlas national : Projections cartographiques
  4. Projection de la carte de Mercator

EDIT Création d'un exemple fonctionnel en PHP (parce que je suis nul en Java).

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

EDIT2

Belle animation de la projection de Mercator 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

1 votes

SWEET Je cherche cette équation exprimée simplement en pseudo code depuis des lustres.

0 votes

@Michel : Pouvez-vous ajouter un exemple, s'il vous plaît ?

3 votes

Si je veux "zoomer" dans mes points de coordonnées, où puis-je multiplier le zoom dans cet algorithme ?

13voto

limc Points 14557

Vous ne pouvez pas simplement transposer la longitude/latitude en x/y de cette manière, car le monde n'est pas plat. Avez-vous regardé ce message ? Conversion de la longitude/latitude en coordonnées X/Y

MISE À JOUR - 1/18/13

J'ai décidé de tenter l'expérience, et voici comment je m'y prends:-.

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

RÉSULTAT : Largeur de l'image = 600px, Hauteur de l'image = 600px, Rembourrage de l'image = 50px

enter image description here

RÉSULTAT : Largeur de l'image = 300px, Hauteur de l'image = 500px, Rembourrage de l'image = 50px

enter image description here

1 votes

J'ai jeté un coup d'œil, il ne semble pas que le code de l'auteur prenne en compte la taille de l'image de la carte ? J'ai mis à jour ma question avec un code, je l'espère, plus précis, mais toujours pas correct.

0 votes

J'ai mis à jour mon post... ce code prend en compte la largeur et la hauteur de l'image spécifiée.

11voto

nik Points 109

Version Java de l'original API JavaScript de Google Maps v3 Le code java script est le suivant, il fonctionne sans problème.

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

0 votes

Qu'est-ce qui définit la taille des carreaux ? J'essaie d'utiliser quelque chose comme ça dans Unity. Est-ce la taille de l'image ? ou est-ce une constante pour google maps ou la projection mercator sur le web ?

0 votes

La taille de la tuile est la taille de l'image de la tuile de la carte, lorsque vous recevez une image de la tuile de la carte des serveurs de google, il s'agit d'une taille 256x256 du fichier png.

0 votes

Cela fonctionne pour la latitude, mais les valeurs de longitude sont très éloignées. Que manque-t-il ?

3voto

user3143011 Points 11

J'aimerais souligner que le code dans les limites de la procédure devrait être le suivant

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

1 votes

Merci pour cela. J'ai mis à jour la réponse de @nik pour inclure ceci.

-1voto

Farsheed Points 397
 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);
 }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X