import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class HeatMap {
public static void main(String[] args) throws Exception {
int IMAGE_WIDTH_IN_PX = 900;
// CHANGE THIS: image height in pixel
int IMAGE_HEIGHT_IN_PX = 500;
double QUARTERPI = Math.PI / 4.0;
int MINIMUM_IMAGE_PADDING_IN_PX = 50;
String IMAGE_FILE_PATH = "heatmap.png";
BufferedImage bufferedImage = ImageUtils.TransparencyToZero(IMAGE_WIDTH_IN_PX, IMAGE_HEIGHT_IN_PX);
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<FeatureExt> countyBoundaries = new ArrayList<FeatureExt>();
JSONArray features = getFeatures("/Users/xxxxxx/Documents/WorkSpaces/src/main/resources/ym1.json");
// for every county, convert the longitude/latitude to X/Y using Mercator projection formula
for (int i = 0; i < features.size(); i++) {
FeatureExt featureExt = new FeatureExt();
JSONObject feature = features.getJSONObject(i);
JSONArray coordinates = feature.getJSONObject("geometry").getJSONArray("coordinates");
JSONObject properties = feature.getJSONObject("properties");
Collection<Point2D.Double> lonLat = new ArrayList<Point2D.Double>();
for (int j = 0; j < coordinates.getJSONArray(0).size(); j++) {
JSONArray coordinateXY = coordinates.getJSONArray(0).getJSONArray(j);
double longitude = coordinateXY.getDouble(0) * Math.PI / 180;
double latitude = coordinateXY.getDouble(1) * 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);
}
featureExt.setProperties(properties);
featureExt.setCoordinates(lonLat);
countyBoundaries.add(featureExt);
}
// readjust coordinate to ensure there are no negative values
for (FeatureExt featureExt : countyBoundaries) {
Collection<Point2D.Double> points = featureExt.getCoordinates();
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 (FeatureExt featureExt : countyBoundaries) {
Collection<Point2D.Double> points = featureExt.getCoordinates();
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);
}
// 定义填充颜色
Color fillColor = getColor(featureExt.getProperties().getDouble("grid_code"));
// 设置填充颜色
g.setColor(fillColor);
// 填充多边形
g.fillPolygon(polygon);
g.drawPolygon(polygon);
}
// create the image file
ImageIO.write(bufferedImage, "PNG", new File(IMAGE_FILE_PATH));
g.dispose();
}
private static JSONArray
getFeatures(String filePath) {
File file = new File(filePath);
try (InputStream inputStream = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String json = stringBuilder.toString();
return JSON.parseObject(json).getJSONArray("features");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static Color
getColor(Double gridCode) {
if (gridCode <= 0) {
return Color.decode("#000000");
} else if (gridCode <= 0.5) {
return Color.decode("#0000FF");
} else if (gridCode <= 1) {
return Color.decode("#00FFFF");
} else if (gridCode <= 1.5) {
return Color.decode("#00FF00");
} else if (gridCode <= 2) {
return Color.decode("#FFFF00");
} else if (gridCode <= 3) {
return Color.decode("#FFA500");
} else if (gridCode > 3) {
return Color.decode("#FF0000");
}
return null;
}
}
import java.awt.*;
import java.awt.image.BufferedImage;
public class ImageUtils {
/**
* 顺时针旋转90度(通过交换图像的整数像素RGB 值)
*
* @param bi
* @return
*/
public static BufferedImage rotateClockwise90(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(height - 1 - j, width - 1 - i, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 逆时针旋转90度(通过交换图像的整数像素RGB 值)
*
* @param bi
* @return
*/
public static BufferedImage rotateCounterclockwise90(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(j, i, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 旋转180度(通过交换图像的整数像素RGB 值)
*
* @param bi
* @return
*/
public static BufferedImage rotate180(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(width, height, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(width - i - 1, height - j - 1, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 水平翻转,以Y轴为中心翻转
*
* @param bi
* @return
*/
public static BufferedImage rotateHorizon(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(width, height, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(width - i - 1, j, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 垂直翻转,以X轴为中心翻转
*
* @param bi
* @return
*/
public static BufferedImage rotateVertical(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(width, height, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(i, height - 1 - j, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 返回指定长宽的透明背景的BufferedImage对象
*
* @param width
* @param height
* @return BufferedImage
*/
public static BufferedImage TransparencyToZero(int width, int height) {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D gd = bi.createGraphics();
bi = gd.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
gd = bi.createGraphics();
return bi;
}
}
参考文章:
java - Converting longitude/latitude to X/Y coordinate - Stack Overflow
其他生成方式:
cesium-heatmap
heatmap.js