Android(安卓)OpenGL ES 绘图 -- 热力图
16lz
2021-01-26
使用高斯核密度估计算法绘制热力图
绘图数据是一个一维的图形数组:
- 宽:50
- 高:60
一、OpenGL 绘图准备配置
在Render中
onSurfaceCreated方法:
// 黑色背景 gl.glClearColor(0, 0, 0, 0); // 设置深度缓存 gl.glClearDepthf(1.0f); // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 所作深度测试的类型 gl.glDepthFunc(GL10.GL_LEQUAL); // 设置点大小 gl.glPointSize(1.5f); // 设置所画的点为圆点 gl.glEnable(GL10.GL_POINT_SMOOTH);
onSurfaceChanged方法:
//设置可显示区域 gl.glViewport(0, 0, width, height); //设备正投影 gl.glMatrixMode(GL10.GL_PROJECTION); gl.glMatrixMode(GL10.GL_MODELVIEW); //初始化数据宽高与View的宽高 this.mWidth = 50 * 5; this.mHeight = 60 * 5; this.mViewWidth = width; this.mViewHeight = height; //重设投影模型 gl.glLoadIdentity(); // 校准显示内容区域 gl.glTranslatef(-0.15f, 0.15f, 0f); gl.glRotatef(-90f, 0, 0, 1); gl.glScalef(0.8f, 0.8f, 1f); //初始化数据缓冲区 ByteBuffer colorTmp = ByteBuffer.allocateDirect(mHeight * mWidth * 4 * 4); colorTmp.order(ByteOrder.nativeOrder()); colorBuff = colorTmp.asFloatBuffer(); ByteBuffer quateTmp = ByteBuffer.allocateDirect(mHeight * mWidth * 2 * 4); quateTmp.order(ByteOrder.nativeOrder()); vertexBuff = quateTmp.asFloatBuffer();
二、 图形数组处理
扩大原数组并转化为一个二维数组
double[][] intensity = new double[mWidth + 2 * mRadius][mHeight + 2 * mRadius]; int n; //数组索引位置 double value; //数据的值 mMaxIntensity = 0; for (int i = 0; i < 50; i++) { for (int j = 0; j < 60; j++) { n = i * 60 + j; value = data[n]; if (value > 0) { //根据原有的数组,填充到一个更大的数组 int bucketX = (int) (clamp((float) i / 50 , 0.0f, mWidth)); int bucketY = (int) (clamp((float) j / 60, 0.0f, mHeight)); if (bucketX < mWidth && bucketX >= 0 && bucketY < mHeight && bucketY >= 0) { intensity[bucketX][bucketY] = value; } //获取数据中的最大值 if (value > mMaxIntensity) { mMaxIntensity = value; } } } }
计算核的大小
private double[] mKernel = generateKernel(mRadius, mRadius / 3.0); static private double[] generateKernel(int radius, double sd) { double[] kernel = new double[radius * 2 + 1]; for (int i = -radius; i <= radius; i++) { kernel[i + radius] = (Math.exp(-i * i / (2 * sd * sd))); } return kernel; }
使用高斯核密度算法处理数组
double[][] convolved = convolve(intensity, mKernel); static private double[][] convolve(double[][] grid, double[] kernel) { int radius = mRadius; int dimOldW = grid.length; int dimOldH = grid[0].length; int dimW = dimOldW - 2 * radius; int dimH = dimOldH - 2 * radius; int lowerLimit = radius; int upperLimitW = radius + dimW - 1; int upperLimitH = radius + dimH - 1; double[][] intermediate = new double[dimOldW][dimOldH]; int x, y, x2, xUpperLimit, initial; double val; for (x = 0; x < dimOldW; x++) { for (y = 0; y < dimOldH; y++) { val = grid[x][y]; if (val != 0) { xUpperLimit = ((upperLimitW < x + radius) ? upperLimitW : x + radius) + 1; initial = (lowerLimit > x - radius) ? lowerLimit : x - radius; for (x2 = initial; x2 < xUpperLimit; x2++) { intermediate[x2][y] += val * kernel[x2 - (x - radius)]; } } } } double[][] outputGrid = new double[dimW][dimH]; int y2, yUpperLimit; for (x = lowerLimit; x < upperLimitW + 1; x++) { for (y = 0; y < dimOldH; y++) { val = intermediate[x][y]; if (val != 0) { yUpperLimit = ((upperLimitH < y + radius) ? upperLimitH : y + radius) + 1; initial = (lowerLimit > y - radius) ? lowerLimit : y - radius; for (y2 = initial; y2 < yUpperLimit; y2++) { outputGrid[x - radius][y2 - radius] += val * kernel[y2 - (y - radius)]; } } } } return outputGrid; }
convolved为处理好的数组
三、根据数组,添加点坐标与颜色
int dimw = d.length; int dimh = d[0].length; //根据OpenGL坐标系统,坐标由-1到+1,范围为2,实际坐标需影射到OpenGL坐标系(0:-1, Max:1) //每个点坐标包含两个浮点数:X、Y float[] ver = new float[dimw * dimh * 2]; //每个颜色数据包令4个浮点数:R、G、B float[] colorDBuff = new float[dimw * dimh * 3]; //点颜色的RGB数组 float[] colorArr; //每点的宽高 float z = (float) mViewWidth / (float) dimw; float Ysplit = (2f / (float) mViewHeight) * z; float Xsplit = (2f / (float) mViewWidth) * z; int k = 0;//点坐标的序号 int c = 0;//点颜色的序号 int i, j; double val; indexNum = 0; for (i = 0; i < dimw; i++) { for (j = 0; j < dimh; j++) { val = d[i][j]; if (val != 0) { colorArr = getColor((float) (val / mMaxIntensity)); //point and color ver[k++] = -1f + i * Ysplit; ver[k++] = -1f + j * Xsplit; colorDBuff[c++] = colorArr[0]; colorDBuff[c++] = colorArr[1]; colorDBuff[c++] = colorArr[2]; indexNum++; } } } colorBuff.put(colorDBuff); colorBuff.position(0); vertexBuff.put(ver); vertexBuff.position(0); // 需重置数组位置,否则不能顶点数据访问为空,无图形输出
能量色条值获取方法
/** * 能量色条值获取 * @param value 实际数据值 * @return 对应能量色条颜色数组[r, g, b] */ protected float[] getColor(float value) { int sr1 = 0x00, sg1 = 0x00, sb1 = 0x00; int sr2 = 0x46, sg2 = 0xb7, sb2 = 0xec; int sr3 = 0x22, sg3 = 0xfe, sb3 = 0x2c; int sr4 = 0xff, sg4 = 0xea, sb4 = 0x00; int sr5 = 0xff, sg5 = 0x4e, sb5 = 0x00; double r = 0, g = 0, b = 0; if( value < 0.15 ) { r = sr1 + (sr2 - sr1) * (value / 0.15); g = sg1 + (sg2 - sg1) * (value / 0.15); b = sb1 + (sb2 - sb1) * (value / 0.15); } else if(value < 0.5){ r = sr2 + (sr3 - sr2) * ((value - 0.15) / 0.35); g = sg2 + (sg3 - sg2) * ((value - 0.15) / 0.35); b = sb2 + (sb3 - sb2) * ((value - 0.15) / 0.35); } else if(value < 0.75){ r = sr3 + (sr4 - sr3) * ((value - 0.5) / 0.25); g = sg3 + (sg4 - sg3) * ((value - 0.5) / 0.25); b = sb3 + (sb4 - sb3) * ((value - 0.5) / 0.25); } else { r = sr4 + (sr5 - sr4) * ((value - 0.75) / 0.25); g = sg4 + (sg5 - sg4) * ((value - 0.75) / 0.25); b = sb4 + (sb5 - sb4) * ((value - 0.75) / 0.25); } return new float[]{(float) (r / 0xFF), (float) (g / 0xFF), (float) (b / 0xFF)}; }
四、绘制
onDrawFrame方法:
//启动相关管道 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // 开启顶点数组画图模式,否则不能使用顶点数组画图 gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // 打开着色数组,启用顶点着色功能 //绘图 gl.glVertexPointer(2, GL10.GL_FLOAT, 0, vertexBuff); gl.glColorPointer(3, GL10.GL_FLOAT, 0, colorBuff); gl.glDrawArrays(GL10.GL_POINTS, 0, indexNum); //关闭相关管道 gl.glDisableClientState(GL10.GL_COLOR_ARRAY); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
效果图:
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- Android(安卓)中自定义ContentProvider与ContentObserver的使用
- Android后台推送摄像头/屏幕数据
- Android之Intent附加数据的两种实现方法
- Intent传递集合数据
- android arcgis(100.0.0)ArcGISMapImageLayer 图片图层点击查询要
- Android(安卓)app security安全问题总结
- 如何使用SQLiteOpenHelper