I created a map using Google Maps API that highlights all Minnesota counties. Basically, I created the county polygons using a set of longitudes/latitudes coordinates. Here's a screenshot of the generated map:-
我用谷歌地图API创建了一个地图,它突出了所有明尼苏达州的县。基本上,我用一组经纬度坐标创建了县多边形。这是一张生成的地图的截图:-。
One of the user requirements is to be able to have a similar map as an image so that they can embed it in their PowerPoint/keynote slides. I couldn't find any useful Google Maps API that allows me to save my custom map the way it is (if you know a way, let me know), so I figure I should just draw it with Graphics2D in Java.
其中一个用户要求是能够有一个类似的地图,这样他们就可以把它嵌入到他们的PowerPoint/keynote幻灯片中。我找不到任何有用的谷歌地图API,它允许我保存我的自定义映射(如果你知道一种方法,让我知道),所以我想我应该在Java中使用Graphics2D来绘制它。
After reading about the formulas to convert the longitude/latitude to X/Y coordinate, I end up with the following code:-
在阅读了将经度/纬度转换为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(...);
...
}
The generated map is similar the one generated by Google Maps API using the same set of longitudes/latitudes. However, it seems a little bit tilted and it looks a little off, and I'm not sure how to fix this.
生成的映射与使用相同的经度/纬度的谷歌映射API生成的映射相似。然而,它看起来有点倾斜,看起来有点偏离,我不确定如何修复它。
How do I make the shape of the counties to look just like the one generated by Google Maps API above?
我如何使这些县的形状看起来就像上面的谷歌地图API所生成的那样?
Thanks much.
谢谢。
FINAL SOLUTION
最终的解决方案
I finally found the solution thanks to @QuantumMechanic and @Anon.
我终于找到了解决方案,感谢@QuantumMechanic和@Anon。
The Mercator projection really does the trick here. I'm using Java Map Projection Library to perform the calculation for Mercator projection.
墨卡托投影在这里真的很有用。我使用Java Map投影库来执行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;
}
...
By using the generated XY coordinate, the map seems inverted, and that's because I believe the graphics2D's 0,0 starts at top left. So, I need to invert the Y by subtracting the value from the image height, something like this:-
通过使用生成的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);
}
...
Here's the generated map:-
生成的地图:-
IT IS PERFECT!
它是完美的!
UPDATE 01-25-2013
更新01-25-2013
Here's the code to create the image map based on the width and height (in pixel). In this case, I'm not relying on the Java Map Project Library, instead, I extracted out the pertinent formula and embed it in my code. This gives you a greater control of the map generation, compared to the above code example that relies on an arbitrary scaling value (the example above uses 6000).
下面是创建基于宽度和高度(像素)的图像映射的代码。在这种情况下,我不依赖于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: Image width = 600px, Image height = 600px, Image padding = 50px
结果:图像宽度= 600px,图像高度= 600px,图像填充= 50px。
RESULT: Image width = 300px, Image height = 500px, Image padding = 50px
结果:图像宽度= 300px,图像高度= 500px,图像填充= 50px。
3 个解决方案
#1
11
The big issue with plotting maps is that the spherical surface of the Earth cannot be conveniently converted into a flat representation. There are a bunch of different projections that attempt to resolve this.
绘制地图的一个大问题是,地球的球面不能被方便地转换成平面表示。有很多不同的预测试图解决这个问题。
Mercator is one of the simplest: it assumes that lines of equal latitude are parallel horizontals, while lines of equal longitude are parallel verticals. This is valid for latitude (1 degree of latitude approximately equals 111 km no matter where you are), but not valid for longitude (the surface distance of a degree of longitude is proportional to the cosine of the latitutude).
Mercator是最简单的一种:它假定相等纬度的线是平行的水平,而相等的经线是平行的垂直方向。这是有效的纬度(1度的纬度大约等于111公里,无论你在哪里),但对经度不有效(经度的表面距离与纬度的余弦成正比)。
However, as long as you're below about 45 degrees (which most of Minnesota is), a Mercator projection works very well, and creates the forms that most people will recognize from their grade school maps. And it's very simple: just treat the points as absolute coordinates, and scale to whatever space you're drawing them in. No trig necessary.
然而,只要你的温度低于45度(这是明尼苏达州的大部分),墨卡托投影就能很好地发挥作用,并创造出大多数人从他们的小学地图上辨认出来的形式。这很简单:把这些点当作绝对坐标,然后缩放到你画的任何空间。没有必要三角。
#2
6
Remember that how a map looks is a function of the projection used to render the map. Google Maps appears to use a Mercator projection (or something very similar to it). What projection does your algorithm equate to? If you want your 2D representation to look just like Google's you need to use an identical projection.
记住,地图的外观是用来渲染地图的投影功能。谷歌映射似乎使用了一个Mercator投影(或者类似的东西)。你的算法是什么样的投影?如果你想让2D表示看起来像谷歌,你需要使用一个相同的投影。
#3
4
To convert lat/lon/alt (lat in degrees North, lon in degrees East, alt in meters) to earth centered fixed coordinates (x,y,z), do the following:
将lat/lon/alt(在北纬度,东,东,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);
#1
11
The big issue with plotting maps is that the spherical surface of the Earth cannot be conveniently converted into a flat representation. There are a bunch of different projections that attempt to resolve this.
绘制地图的一个大问题是,地球的球面不能被方便地转换成平面表示。有很多不同的预测试图解决这个问题。
Mercator is one of the simplest: it assumes that lines of equal latitude are parallel horizontals, while lines of equal longitude are parallel verticals. This is valid for latitude (1 degree of latitude approximately equals 111 km no matter where you are), but not valid for longitude (the surface distance of a degree of longitude is proportional to the cosine of the latitutude).
Mercator是最简单的一种:它假定相等纬度的线是平行的水平,而相等的经线是平行的垂直方向。这是有效的纬度(1度的纬度大约等于111公里,无论你在哪里),但对经度不有效(经度的表面距离与纬度的余弦成正比)。
However, as long as you're below about 45 degrees (which most of Minnesota is), a Mercator projection works very well, and creates the forms that most people will recognize from their grade school maps. And it's very simple: just treat the points as absolute coordinates, and scale to whatever space you're drawing them in. No trig necessary.
然而,只要你的温度低于45度(这是明尼苏达州的大部分),墨卡托投影就能很好地发挥作用,并创造出大多数人从他们的小学地图上辨认出来的形式。这很简单:把这些点当作绝对坐标,然后缩放到你画的任何空间。没有必要三角。
#2
6
Remember that how a map looks is a function of the projection used to render the map. Google Maps appears to use a Mercator projection (or something very similar to it). What projection does your algorithm equate to? If you want your 2D representation to look just like Google's you need to use an identical projection.
记住,地图的外观是用来渲染地图的投影功能。谷歌映射似乎使用了一个Mercator投影(或者类似的东西)。你的算法是什么样的投影?如果你想让2D表示看起来像谷歌,你需要使用一个相同的投影。
#3
4
To convert lat/lon/alt (lat in degrees North, lon in degrees East, alt in meters) to earth centered fixed coordinates (x,y,z), do the following:
将lat/lon/alt(在北纬度,东,东,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);