36

Creé un mapa con la API de Google Maps que resalta todos los condados de Minnesota. Básicamente, creé los polígonos del condado usando un conjunto de coordenadas de longitudes/latitudes. Aquí hay una captura de pantalla del mapa generado: -Conversión de longitud/latitud en coordenadas X/Y

enter image description here

Una de las necesidades de los usuarios es poder tener un mapa similar al de una imagen para que puedan integrar en sus diapositivas de PowerPoint/Keynote. No pude encontrar ninguna API útil de Google Maps que me permitiera guardar mi mapa personalizado tal como está (si conoce una forma, hágamelo saber), por lo que creo que debería dibujarlo con Graphics2D en Java.

Después de leer sobre las fórmulas para convertir la longitud/latitud de coordenadas X/Y, termino con el siguiente código: -

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

El mapa generado es similar al generado por la API de Google Maps a través de el mismo conjunto de longitudes/latitudes. Sin embargo, parece un poco inclinado y se ve un poco apagado, y no estoy seguro de cómo solucionarlo.

enter image description here

¿Cómo hago la forma de los condados para verse como el generado por Google Maps API anterior?

Muchas gracias.

SOLUCIÓN FINAL

finalmente encontré la solución gracias a @QuantumMechanic y @Anon.

La proyección de Mercator realmente hace el truco aquí. Estoy usando para realizar el cálculo para la proyección 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; 
} 

... 

Al utilizar el XY generada coordenada, el mapa parece invertirse, y eso es porque creo 0,0 aperturas de la Graphics2D arriba a la izquierda. Por lo tanto, necesito invertir la Y restando el valor de la altura de la imagen, algo como esto: -

... 

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

... 

Aquí está el mapa generado: -

enter image description here

Es perfecto!

ACTUALIZACIÓN 01-25-2013

Aquí está el código para crear el mapa de imágenes basada en la anchura y la altura (en píxeles). En este caso, no estoy confiando en la Biblioteca de proyectos de mapas de Java, sino que extraje la fórmula pertinente y la incrusto en mi código. Esto le da un mayor control de la generación de mapas, en comparación con el ejemplo de código anterior que se basa en un valor de escala arbitrario (el ejemplo anterior usa 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)); 
    } 
} 

RESULTADO: Ancho de la imagen = 600px, altura de imagen = 600px, relleno de imagen = 50 px

enter image description here

RESULTADO: Ancho de la imagen = 300 px, altura de imagen = 500px, relleno de imagen = 50px

enter image description here

+0

¿El La API de Google Maps proporciona algunos métodos de utilidad para ayudar con las conversiones de transformación de proyección mediante una matriz de transformación afín entre la proyección de vista/diseño. ¿Alguien sabe acerca de ellos? Si solo se trata de una imagen estática, sería fácil calcular la transformación de proyección. Sin embargo, Google Map funciona con mosaicos dinámicos de imágenes que cambian al hacer zoom, lo cual es bastante complicado. – eee

+0

limc, he implementado MercatorProjection como se muestra. Está funcionando perfectamente, pero necesito implementar esto con valores dinámicos de latitud larga. ¿Hay alguna manera de calcular el factor de escala (en el ejemplo anterior es 6000) en tiempo de ejecución? – OMK

+0

una forma es averiguar qué le da "6000" en términos de ancho y alto en píxeles, luego aumente a "7000", luego "8000" y así sucesivamente, y anote todos los valores. A partir de ahí, puede calcular aproximadamente la relación. No es perfecto seguro. Lo que hice en mi extremo es que tengo un número para la imagen pequeña y un número para la imagen grande, ya que necesito asegurarme de que la imagen se ajuste a mi sitio web o archivo pdf. – limc

Respuesta

11

El gran problema con los mapas de trazado es que la superficie esférica de la Tierra no se puede convertir convenientemente en una representación plana. Hay un montón de proyecciones diferentes que intentan resolver esto.

Mercator es uno de los más simples: asume que las líneas de igual latitud son horizontales paralelas, mientras que las líneas de igual longitud son verticales paralelas. Esto es válido para la latitud (1 grado de latitud aproximadamente igual a 111 km sin importar dónde se encuentre), pero no válido para la longitud (la distancia de superficie de un grado de longitud es proporcional al coseno del latitutude).

Sin embargo, siempre que esté por debajo de 45 grados (que es la mayoría de Minnesota), una proyección de Mercator funciona muy bien y crea los formularios que la mayoría de la gente reconocerá en sus mapas de la escuela primaria. Y es muy simple: simplemente trate los puntos como coordenadas absolutas, y escale a cualquier espacio en el que los dibuje. No es necesario ningún trig.

+0

+1: gracias. Publiqué mi solución final más arriba. – limc

6

Recuerde que la apariencia de un mapa es una función de la proyección utilizada para representar el mapa. Google Maps parece usar una proyección de Mercator (o algo muy similar). ¿Qué proyección iguala su algoritmo? Si desea que su representación en 2D se vea como la de Google, debe usar una proyección idéntica.

+0

+1: gracias. Publiqué mi solución final más arriba. – limc

4

Convertir latitud/longitud/alt (lat en grados Norte, lon en grados Este, alt en metros) las coordenadas a la tierra centrales fijos (x, y, z), haga lo siguiente:

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