将经度/纬度转换为X / Y坐标
我使用Google Maps API创build了一个地图,突出显示所有明尼苏达州的县。 基本上,我用一组经度/纬度坐标创build了县的多边形。 以下是生成的地图的屏幕截图:
用户需求之一是能够将图像与图像进行比较,以便将其embedded到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(...); ... }
生成的地图与Google Maps API使用相同的一组经度/纬度生成的地图类似。 然而,它似乎有点倾斜,它看起来有点closures,我不知道如何解决这个问题。
如何让县的形状看起来就像上面的Google Maps API生成的那样?
非常感谢。
最终的解决scheme
我终于find了解决scheme,感谢@QuantumMechanic和@Anon。
墨卡托投影真的在这里做的伎俩。 我正在使用Java Map Projection Library来执行墨卡托投影的计算。
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 + dx) * 6000); int magnifiedY = (int) Math.round((10 + dy) * 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从左上angular开始。 所以,我需要通过减去图像高度的值来反转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); } ...
这是生成的地图:
这十分完美!
更新01-25-2013
以下是根据宽度和高度(以像素为单位)创build图像映射的代码。 在这种情况下,我不是依靠Java Map项目库,而是提取出相关公式并将其embedded到我的代码中。 与以上代码示例(依靠任意缩放值(上面的示例使用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)); } }
结果:图片宽度= 600px,图片高度= 600px,图片填充= 50px
结果:图片宽度= 300px,图片高度= 500px,图片填充= 50px
绘制地图的大问题是,地球的球面不能方便地转化为平面表示。 有一堆不同的预testing图解决这个问题。
墨卡托是最简单的一个:它假定等纬线是平行的水平线,而等于经线的线是平行的垂直线。 这对纬度是有效的(无论您身处何处,1纬度大约等于111公里),但对经度无效(经度的表面距离与纬度的余弦成比例)。
然而,只要你低于大约45度(明尼苏达州大部分地区),墨卡托投影就能很好地工作,并且创造出大多数人从他们的小学地图上可以识别的forms。 而且非常简单:只需将点视为绝对坐标,然后缩放到您要绘制的任何空间。不需要任何三angular。
请记住,地图的外观是用于渲染地图的投影的函数。 谷歌地图似乎使用墨卡托投影(或类似的东西)。 你的algorithm等同于什么投影? 如果你想让你的二维表示看起来像谷歌的,你需要使用相同的投影。
要将纬度/经度/纬度(北纬度,纬度东度,米以米为单位)转换为以地球为中心的固定坐标(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);