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
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.
¿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: -
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
RESULTADO: Ancho de la imagen = 300 px, altura de imagen = 500px, relleno de imagen = 50px
¿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
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
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