38 votes

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. En gros, j'ai créé le comté de polygones à l'aide d'un ensemble de longitudes/latitudes coordonnées. Voici une capture d'écran de la carte générée:-

enter image description here

L'une des exigences de l'utilisateur doit être en mesure d'avoir une carte comme une image, de sorte qu'ils puissent l'intégrer dans leur PowerPoint/keynote diapositives. Je ne pouvais pas trouver utile de l'API Google Maps qui me permet de gagner ma carte personnalisée de la façon dont il est (si vous connaissez un moyen, faites le moi savoir), donc je me dis que je devrais juste dessiner avec Graphics2D en Java.

Après avoir lu les formules pour convertir la longitude/latitude pour 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 celui généré par l'API Google Maps en utilisant le même ensemble de longitudes/latitudes. Cependant, il semble un peu inclinés et ça a l'air un peu hors, et je ne suis pas sûr de la façon de résoudre ce problème.

enter image description here

Comment puis-je faire la forme des comtés pour ressembler à celui généré par l'API Google Maps ci-dessus?

Merci beaucoup.

LA SOLUTION FINALE

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

La projection de Mercator vraiment fait le tour ici. Je suis à l'aide de Java Projection de la Carte de Bibliothèque pour effectuer le calcul de la projection de 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 le générés XY coordonner, la carte semble inversé, et c'est parce que je crois que le graphics2D de 0,0 commence en haut à gauche. Donc, j'ai besoin d'inverser la 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 EST PARFAIT!

Mise à JOUR 01-25-2013

Voici le code pour créer la texture de l'image en fonction de la largeur et de la hauteur (en pixels). Dans ce cas, je ne suis pas en s'appuyant sur le Java Projet de la Carte de Bibliothèque, au lieu de cela, j'ai extrait la pertinente formule et de l'intégrer 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 que repose sur l'arbitraire de la valeur d'échelle (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: la largeur de l'Image = 600px, la hauteur de l'Image = 600px, Image rembourrage = 50px

enter image description here

RÉSULTAT: la largeur de l'Image = 300px, la hauteur de l'Image = 500px, Image rembourrage = 50px

enter image description here

12voto

Anon Points 1412

Le gros problème avec le traçage des cartes, c'est que la surface sphérique de la Terre ne peuvent être facilement converti en un appartement de représentation. Il y a un tas de différentes projections qui tentent de résoudre ce problème.

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

Toutefois, aussi longtemps que vous êtes en-dessous de 45 degrés (qui la plupart du Minnesota, est), une projection de Mercator fonctionne très bien, et crée les formes que la plupart des gens reconnaissent de leur année d'études à l'école des cartes. Et c'est très simple: il suffit de traiter les points de coordonnées absolues, et l'échelle de tout l'espace que vous êtes les dessiner. Pas de trig nécessaire.

6voto

QuantumMechanic Points 7825

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.

4voto

toadaly Points 427

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

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