基于定位的热力图展示,后端实现
- 首先将实际坐标转换成像素坐标
- 操作图片像素 不断的增加像素RGB值
- 扩大像素点范围,根据聚集程度设置色阶,最后高斯模糊形成热力图
- 这里是根据实际场景自动生成对应等比例大小的透明贴图,当然也可以直接在指定的图片上画热力图
1.坐标转换以前有发过一个工具类任意坐标系转换可以去看看
这里取的4个点分别为图片左下角,图片右上角 对应的实际位置坐标左下角和右上角
使用工具类获取到偏移量,旋转值等基本参数
double rotation = Math.toRadians(Math.abs(coordinateUtil.getAngle(imgPoint1, imgPoint2) - coordinateUtil.getAngle(mapPoint1, mapPoint2)));
double scale = coordinateUtil.getScale(mapPoint1, imgPoint1, mapPoint2, imgPoint2);
double tx = coordinateUtil.getXTranslation(mapPoint1, imgPoint1, rotation, scale);
double ty = coordinateUtil.getYTranslation(mapPoint1, imgPoint1, rotation, scale);
将实际点转换成像素点
Point boePoint = coordinateUtil.transformBoePoint(new Point(o.getX(), o.getZ(), 0.0), rotation, scale, tx, ty);
//去吹掉超出图片范围的点
//若转换的坐标超出了图片范围舍弃
Double x = boePoint.getX();
Double y = boePoint.getY();
if (!(x < 0 || y < 0 || x > imageWidth || y > imageHeight)){
heatMapData.setX(x)
.setY(y);
heatMapDataList.add(heatMapData);
}
渲染图片 这里说明下,因为像素点的坐标系原点是左上角的点,与我们最开始设置的对应坐标系不同,所有转换出来的像素点的y值需要用图片高度减去他剩下的就是实际的像素点,因为像素点很小,在设置时count我是默认将该点周围8个点作为一个大的像素点,这样在设置颜色时就会很明显,这个参数根据需求设置
/**
* 渲染图片
* @param image 图片流
* @param x 操作的像素点x
* @param y 操作的像素点y
* @param count 渲染的像素点
*/
public static void renderPictures(BufferedImage image, Double x, Double y, int count){
//获取画笔对象
int xValue = x.intValue();
int yValue = y.intValue();
int pixel = image.getRGB(xValue, yValue);
//从pixel中获取rgba的值
int b = (0xff & pixel);
int g = (0xff & (pixel >> 8));
int r = (0xff & (pixel >> 16));
//α通道值
int alpha = (0xff & (pixel >> 24));
//颜色解析
if (r == 0){
if (g < 255){
g = 255;
}
}
int rgb = b + (g << 8) + (r << 16) + (255 << 24);
int vy = image.getHeight() - yValue;
// image.setRGB(xValue, vy, rgb);
// image.setRGB(xValue, vy, rgb);
// image.setRGB(xValue, vy, rgb);
// image.setRGB(xValue, vy, rgb);
for (int i = xValue - count; i< xValue + count; i++) {
for (int j = vy - count; j< vy + count; j++) {
if (i >= 0 && i < image.getWidth()) {
if (j >=0 && j < image.getHeight() ) {
image.setRGB(i, j, rgb);
}
}
}
}
}
最后高斯模糊刚才处理后的图片,根据α通道设置色阶,实现多个目标聚集时颜色加深(这里色阶可根据实际情况调整)
/**
* 高斯模糊 设置色阶
* @param blur
* @param outPat
*/
public static void gaussFuzzy(BufferedImage blur, String outPat) {
try {
blur = HeatMapUtil.blur(blur, 25);
int width = blur.getWidth();
int height = blur.getHeight();
//获取像素点
for (int i = 0; i< width; i++) {
for (int j = 0; j < height; j++) {
int pixel = blur.getRGB(i, j);
//从pixel中获取rgba的值
int a = (pixel >> 24) & 0xff;
int g = (pixel >> 8) & 0xff;
if (g == 255) {
//颜色分级
if (a > 0 && a <= 25){
blur.setRGB(i, j, new Color(0, 0, 255, 15).getRGB());
}
if (a > 25 && a <= 50){
blur.setRGB(i, j, new Color(0, 255, 0, 160).getRGB());
}
if (a > 50 && a <= 100){
blur.setRGB(i, j, new Color(255, 255, 0, 185).getRGB());
}
if (a > 100 && a <= 125){
blur.setRGB(i, j, new Color(255, 213, 0, 200).getRGB());
}
if (a > 125 && a <= 150){
blur.setRGB(i, j, new Color(255, 171, 0, 215).getRGB());
}
if (a > 125 && a <= 150){
blur.setRGB(i, j, new Color(255, 129, 0, 225).getRGB());
}
if (a > 150 && a <= 175){
blur.setRGB(i, j, new Color(255, 87, 0, 235).getRGB());
}
if (a > 175 && a <= 200){
blur.setRGB(i, j, new Color(255, 42, 0, 245).getRGB());
}
if (a > 200){
blur.setRGB(i, j, new Color(255, 0, 0, 255).getRGB());
}
}
}
}
blur = HeatMapUtil.blur(blur, 10);
//输出
ImageIO.write(blur,"png",new File(outPat));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 高斯模糊
* @param source 数据源
* @param radius 模糊半径
* @return
*/
public static BufferedImage blur(BufferedImage source, int radius) {
BufferedImage img = new BufferedImage(source.getWidth() + radius
* 2, source.getHeight() + radius * 2,
BufferedImage.TRANSLUCENT);
Graphics2D g2 = (Graphics2D) img.getGraphics();
g2.setColor(new Color(0,0,0,0));
g2.fillRect(0, 0, source.getWidth() + radius * 2,
source.getHeight() + radius * 2);
g2.drawImage(source, radius, radius, null);
g2.dispose();
int square = radius * radius;
float sum = 0;
float[] matrix = new float[square];
for (int i = 0; i < square; i++) {
int dx = i % radius - radius / 2;
int dy = i / radius - radius / 2;
matrix[i] = (float) (radius - Math.sqrt(dx * dx + dy * dy));
sum += matrix[i];
}
for (int i = 0; i < square; i++) {
matrix[i] /= sum;
}
BufferedImageOp op = new ConvolveOp(new Kernel(radius, radius,
matrix), ConvolveOp.EDGE_ZERO_FILL, null);
BufferedImage res = op.filter(img, null);
BufferedImage out = new BufferedImage(source.getWidth(),
source.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g3 = (Graphics2D) out.getGraphics();
g3.drawImage(res, -radius, -radius, null);
g3.dispose();
return out;
}
使用方法:
获取到你要渲染的点坐标集合,通过上述坐标转换获取到的实际图片点
遍历调用renderPictures将图片流渲染
将渲染后的图片流
gaussFuzzy(BufferedImage, 输出地址)
获取到二次处理后的图片,此时获取到的就是热力图红点图
注:这里使用我的源坐标点z值永远为0,所以坐标转换写死了,若有实际xyz则正常传入转换封装数据
/**
* 热力图
* @param imageWidth 图片宽度
* @param imageHeight 图片高度
* @param max 源坐标系对应右上角坐标x,y,z
* @param min 源坐标系对应左下角坐标x,y,z
* @param locationList 源数据坐标点集合
* @param outPath 输出成品图片路径
* @return
*/
public String getHeatMapRealTime(Double imageWidth, Double imageHeight, String max, String min, List<PositionTagLocation> locationList, String outPath) {
//渲染图片
CoordinateSystemVo point = getPoint(min, max, imageWidth, imageHeight);
//生成透明图片
BufferedImage img = HeatMapUtil.createTransparentImg(imageWidth.intValue(), imageHeight.intValue());
if (locationList.size() > 0){
List<HeatMapData> heatMapData = getHeatMapData(locationList, imageWidth, imageHeight, point);
//渲染图片
heatMapData.forEach(x-> HeatMapUtil.renderPictures(img, x.getX(), x.getY(), 8));
//上色,高斯模糊
//二次处理图片导出
HeatMapUtil.gaussFuzzy(img, outPath);
}else {
try {
ImageIO.write(img,"png", new File(outPath));
} catch (IOException e) {
e.printStackTrace();
}
}
return outPath;
}
/**
* 将数据点迹封装成热力图数据
* @param tagLocationList 源数据点几个
* @return
*/
private List<HeatMapData> getHeatMapData(List<PositionTagLocation> tagLocationList, Double imageWidth,
Double imageHeight, CoordinateSystemVo point) {
List<HeatMapData> heatMapDataList = Lists.newArrayList();
double rotation = point.getRotation();
double scale = point.getScale();
double tx = point.getTx();
double ty = point.getTy();
//封装集合
tagLocationList.forEach(o->{
Point boePoint = coordinateUtil.transformBoePoint(new Point(o.getX(), o.getZ(), 0.0), rotation, scale, tx, ty);
HeatMapData heatMapData = new HeatMapData();
//若转换的坐标超出了图片范围舍弃
Double x = boePoint.getX();
Double y = boePoint.getY();
if (!(x < 0 || y < 0 || x > imageWidth || y > imageHeight)){
heatMapData.setX(x)
.setY(y);
heatMapDataList.add(heatMapData);
}
});
return heatMapDataList;
}
/**
* 初始化坐标转换
* @return
*/
private CoordinateSystemVo getPoint(String min, String max, Double imageWidth, Double imageHeight) {
//获取基础转换坐标的4个点
String[] strings1 = StringUtil.stringSplit(min);
String[] strings2 = StringUtil.stringSplit(max);
Point imgPoint1 = new Point(0.0, 0.0, 0.0);
Point imgPoint2 = new Point(imageWidth, imageHeight, 0.0);
Point mapPoint1 = new Point(Double.valueOf(strings1[0]), Double.valueOf(strings1[2]), 0.0);
Point mapPoint2 = new Point(Double.valueOf(strings2[0]), Double.valueOf(strings2[2]), 0.0);
//初始化4点
double rotation = Math.toRadians(Math.abs(coordinateUtil.getAngle(imgPoint1, imgPoint2) - coordinateUtil.getAngle(mapPoint1, mapPoint2)));
double scale = coordinateUtil.getScale(mapPoint1, imgPoint1, mapPoint2, imgPoint2);
double tx = coordinateUtil.getXTranslation(mapPoint1, imgPoint1, rotation, scale);
double ty = coordinateUtil.getYTranslation(mapPoint1, imgPoint1, rotation, scale);
CoordinateSystemVo coordinateSystem = new CoordinateSystemVo();
coordinateSystem.setRotation(rotation)
.setScale(scale)
.setTx(tx)
.setTy(ty);
return coordinateSystem;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author zf
*/
@Data
@Accessors(chain = true)
public class CoordinateSystemVo {
private double rotation = 0.6386242345924739;
private double scale = 1.0553303144165487;
private double tx = -68.66969875118602;
private double ty = -91.43752692911154;
private double tz = 0.0;
}
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 位置数据
* @author zf
* @date 2018/12/19
*/
@Data
@ToString
@Accessors(chain = true)
public class PositionTagLocation {
@ApiParam(hidden = true)
private String uniqueKey;
@ApiParam(hidden = true)
private LocalDateTime gmtCreate;
@ApiParam(hidden = true)
private LocalDateTime gmtModified;
@ApiParam(value = "X坐标")
private Double x;
@ApiParam(value = "Y坐标")
private Double y;
@ApiParam(value = "Z坐标")
private Double z;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author zf
*/
@Data
@Accessors(chain = true)
public class HeatMapData {
private Double x;
private Double y;
private Double z;
}
2022-05-10更新 缺少的代码:
import java.awt.image.BufferedImage;
/**
* 生成透明图片
* @param width 高度
* @param height 宽度
*/
public static BufferedImage createTransparentImg(int width, int height) {
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = buffImg.createGraphics();
// ---------- 增加下面的代码使得背景透明 -----------------
buffImg = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
//释放对象
g2d.dispose();
return buffImg;
}