web-dev-qa-db-fra.com

Conversion de la longitude / latitude en coordonnées X / Y

J'ai créé une carte à l'aide de l'API Google Maps qui met en évidence tous les comtés du Minnesota. Fondamentalement, j'ai créé les polygones du comté en utilisant un ensemble de coordonnées de longitudes/latitudes. Voici une capture d'écran de la carte générée: -

enter image description here

L'une des exigences des utilisateurs est de pouvoir avoir une carte similaire en tant qu'image afin qu'ils puissent l'intégrer dans leurs diapositives PowerPoint/keynote. Je n'ai trouvé aucune API Google Maps utile qui me permette d'enregistrer ma carte personnalisée telle qu'elle est (si vous connaissez un moyen, faites-le moi savoir), donc je pense que je devrais simplement la dessiner avec Graphics2D en Java.

Après avoir lu les formules pour convertir la longitude/latitude en coordonnées X/Y, je me retrouve avec le code suivant: -

private static final int    EARTH_RADIUS    = 6371;
private static final double FOCAL_LENGTH    = 500;

...

BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();

for (Coordinate coordinate : coordinates) {
    double latitude = Double.valueOf(coordinate.getLatitude());
    double longitude = Double.valueOf(coordinate.getLongitude());

    latitude = latitude * Math.PI / 180;
    longitude = longitude * Math.PI / 180;

    double x = EARTH_RADIUS * Math.sin(latitude) * Math.cos(longitude);
    double y = EARTH_RADIUS * Math.sin(latitude) * Math.sin(longitude);
    double z = EARTH_RADIUS * Math.cos(latitude);

    double projectedX = x * FOCAL_LENGTH / (FOCAL_LENGTH + z);
    double projectedY = y * FOCAL_LENGTH / (FOCAL_LENGTH + z);

    // scale the map bigger
    int magnifiedX = (int) Math.round(projectedX * 5);
    int magnifiedY = (int) Math.round(projectedY * 5);

    ...
    g.drawPolygon(...);
    ...
}

La carte générée est similaire à celle générée par l'API Google Maps en utilisant le même ensemble de longitudes/latitudes. Cependant, il semble un peu incliné et il semble un peu éteint, et je ne sais pas comment résoudre ce problème.

enter image description here

Comment puis-je faire en sorte que la forme des pays ressemble à celle générée par l'API Google Maps ci-dessus?

Merci beaucoup.

SOLUTION FINALE

J'ai finalement trouvé la solution grâce à @QuantumMechanic et @Anon.

La projection Mercator fait vraiment l'affaire ici. J'utilise Java Map Projection Library pour effectuer le calcul de la projection Mercator.

private static final int    IMAGE_WIDTH     = 1000;
private static final int    IMAGE_HEIGHT    = 1000;
private static final int    IMAGE_PADDING   = 50;

...

private List<Point2D.Double> convertToXY(List<Coordinate> coordinates) {
    List<Point2D.Double> xys = new ArrayList<Point2D.Double>();

    MercatorProjection projection = new MercatorProjection();

    for (Coordinate coordinate : coordinates) {
        double latitude = Double.valueOf(coordinate.getLatitude());
        double longitude = Double.valueOf(coordinate.getLongitude());

        // convert to radian
        latitude = latitude * Math.PI / 180;
        longitude = longitude * Math.PI / 180;

        Point2D.Double d = projection.project(longitude, latitude, new Point2D.Double());

        // shift by 10 to remove negative Xs and Ys
        // scaling by 6000 to make the map bigger
        int magnifiedX = (int) Math.round((10 + d.x) * 6000);
        int magnifiedY = (int) Math.round((10 + d.y) * 6000);

        minX = (minX == -1) ? magnifiedX : Math.min(minX, magnifiedX);
        minY = (minY == -1) ? magnifiedY : Math.min(minY, magnifiedY);

        xys.add(new Point2D.Double(magnifiedX, magnifiedY));
    }

    return xys;
}

...

En utilisant la coordonnée XY générée, la carte semble inversée, et c'est parce que je pense que le 0,0 de graphics2D commence en haut à gauche. Donc, je dois inverser le Y en soustrayant la valeur de la hauteur de l'image, quelque chose comme ceci: -

...

Polygon polygon = new Polygon();

for (Point2D.Double point : xys) {
    int adjustedX = (int) (IMAGE_PADDING + (point.getX() - minX));

    // need to invert the Y since 0,0 starts at top left
    int adjustedY = (int) (IMAGE_HEIGHT - IMAGE_PADDING - (point.getY() - minY));

    polygon.addPoint(adjustedX, adjustedY);
}

...

Voici la carte générée: -

enter image description here

IL IS PARFAIT!

MISE À JOUR 01-25-2013

Voici le code pour créer la carte d'image en fonction de la largeur et de la hauteur (en pixels). Dans ce cas, je ne compte pas sur la Java Map Project Library, à la place, j'ai extrait la formule pertinente et l'intègre dans mon code. Cela vous donne un meilleur contrôle de la génération de la carte , par rapport à l'exemple de code ci-dessus qui repose sur une valeur de mise à l'échelle arbitraire (l'exemple ci-dessus utilise 6000).

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 d'image = 600 px, Hauteur d'image = 600 px, Remplissage d'image = 50 px

enter image description here

RÉSULTAT: Largeur d'image = 300 px, Hauteur d'image = 500 px, Remplissage d'image = 50 px

enter image description here

38
limc

Le gros problème avec le traçage des cartes est que la surface sphérique de la Terre ne peut pas être convertie en une représentation plate. Il existe un tas de projections différentes qui tentent de résoudre ce problème.

Mercator est l'un des plus simples: il suppose que les lignes de latitude égale sont des horizontales parallèles, tandis que les lignes de longitude égale sont des verticales parallèles. Ceci est valable pour la latitude (1 degré de latitude équivaut approximativement à 111 km où que vous soyez), mais pas pour la longitude (la distance de surface d'un degré de longitude est proportionnelle au cosinus du latitutude ).

Cependant, tant que vous êtes en dessous d'environ 45 degrés (ce que la plupart du Minnesota est), une projection Mercator fonctionne très bien et crée les formes que la plupart des gens reconnaîtront à partir de leurs cartes scolaires. Et c'est très simple: il suffit de traiter les points comme des coordonnées absolues et de les adapter à l'espace dans lequel vous les dessinez. Aucun trig n'est nécessaire.

12
Anon

N'oubliez pas que l'apparence d'une carte est fonction de la projection utilisée pour rendre la carte. Google Maps semble utiliser une projection Mercator (ou quelque chose de très similaire). À quelle projection votre algorithme correspond-il? Si vous souhaitez que votre représentation 2D ressemble à celle de Google, vous devez utiliser une projection identique.

6
QuantumMechanic

Pour convertir lat/lon/alt (lat en degrés nord, lon en degrés est, alt en mètres) en coordonnées fixes centrées sur la terre (x, y, z), procédez comme suit:

double Re = 6378137;
double Rp = 6356752.31424518;

double latrad = lat/180.0*Math.PI;
double lonrad = lon/180.0*Math.PI;

double coslat = Math.cos(latrad);
double sinlat = Math.sin(latrad);
double coslon = Math.cos(lonrad);
double sinlon = Math.sin(lonrad);

double term1 = (Re*Re*coslat)/
  Math.sqrt(Re*Re*coslat*coslat + Rp*Rp*sinlat*sinlat);

double term2 = alt*coslat + term1;

double x=coslon*term2;
double y=sinlon*term2;
double z = alt*sinlat + (Rp*Rp*sinlat)/
  Math.sqrt(Re*Re*coslat*coslat + Rp*Rp*sinlat*sinlat);
4
toadaly