36

我使用Google Maps API创建了一张地图,突出显示了所有的明尼苏达州郡。基本上,我使用一组经度/纬度坐标创建了县多边形。下面是生成的地图的截图: -将经度/纬度转换为X/Y坐标

enter image description here

其中一个用户的要求是要能够有一个类似的地图为图像,使他们可以在他们的PowerPoint /演讲的幻灯片中嵌入它。我找不到任何有用的Google Maps API,它允许我以自己的方式保存自定义地图(如果您知道某种方式,请告诉我),所以我想我应该使用Java中的Graphics2D来绘制它。

阅读有关公式经度/纬度X/Y坐标转换后,我结束了下面的代码: -

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

生成的地图类似于使用一个由谷歌地图API生成同一组经度/纬度。然而,它似乎有点倾斜,它看起来有点关闭,我不知道如何解决这个问题。

enter image description here

如何使各县的形状看起来就像通过上述谷歌地图API产生的呢?

非常感谢。

最终解决

我终于找到了解决办法感谢@QuantumMechanic和@Anon。

墨卡托投影真的在这里做的伎俩。我使用来执行墨卡托投影的计算。

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

... 

通过使用生成的XY坐标,地图看起来反转了,这是因为我相信graphics2D的0,0从左上角开始。所以,我需要从图像高度中减去价值,像这样反转Y: -

... 

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

... 

下面是生成的地图: -

enter image description here

它是完美的!

UPDATE 2013年1月25日

下面是创建基于宽度和高度(以像素)图像映射的代码。在这种情况下,我并不依赖Java Map项目库,而是提取出相关的公式并将其嵌入到我的代码中。与上述依赖任意缩放值的代码示例(上述示例使用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)); 
    } 
} 

RESULT:图像宽度= 600px的,图像高度= 600像素,图象填补= 50像素

enter image description here

RESULT:图片宽度= 300像素,图像高度= 500像素,图象填补= 50像素

enter image description here

+0

不Google Maps API提供了几种实用方法来帮助在视图/布局投影之间使用仿射变换矩阵进行投影变换转换?有谁知道他们?如果它只是一个静态图像,计算投影变换将很容易。但是,Google Map可以处理在缩放时变化的动态图像块,这非常棘手。 – eee 2011-05-13 01:36:48

+0

limc,我已经实现了MercatorProjection。它正在完美工作,但我需要使用动态经纬度值来实现这一点。在运行时有没有什么方法可以计算比例因子(在上面的例子中是6000)? – OMK 2013-01-03 06:55:53

+0

一个办法就是找出“6000”为您提供了宽度和高度在像素方面,则增加为“7000”,然后是“8000”等,并记下所有的值。从那里,你可以粗略地计算出比率。这当然不完美。我在我的最后做的是我有一个小图像的数字和一个大图像的数字,因为我需要确保图像是否适合我的网站或pdf文件。 – limc 2013-01-03 13:37:11

回答

11

最大的问题与绘制的地图是地球的球形表面不能方便地转换成平面表示。有很多不同的预测试图解决这个问题。

Mercator是最简单的一种:它假定等纬线是平行水平线,而等经度线是平行垂直线。这是有效的纬度(1度纬度约等于111公里无论你在哪里),但不是有效的经度(度经度的表面距离成正比latitutude的余弦)。

但是,只要你是低于约45度(其中大部分明尼苏达的是),一个墨卡托投影效果非常好,并创建一个大多数人会从他们小学的地图识别形式。而且非常简单:只需将点视为绝对坐标,然后根据您绘制的空间进行缩放。不需要任何三角。

+0

+1:谢谢。我发布了上面的最终解决方案。 – limc 2011-05-13 13:29:19

6

请记住,地图看起来是用于呈现地图的投影的函数。谷歌地图似乎使用墨卡托投影(或类似的东西)。你的算法等同于什么投影?如果你想让你的二维表示看起来像谷歌,你需要使用相同的投影。

+0

+1:谢谢。我发布了上面的最终解决方案。 – limc 2011-05-13 13:27:55

4

要转换/纬度/ ALT(LAT在度以北,经度以度为东,ALT米)接地居中固定的坐标(X,Y,Z),请执行下列操作:

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