文章目录
新版本介绍 v5.0 文章链接
一、Matplot3D for JAVA V4.0 概述
Matplot3D for JAVA 是一个基于JAVA SE 1.8环境开发的数学科学数据三维可视化组件。 这是一个纯JAVA实现的类似matplotlib可视化三维库(内含纯java实现的3维软引擎),封装为一个jar包,jar文件大小只有200多KB,直接引用即可。无需再安装和配置第三方库或者环境,无需依赖OpenGL、DriectX、JAVA 3D或JAVAFX等等。
下载地址
码云: https://gitee.com/tanling8334/Matplot3D-for-Java
Github: https://github.com/tanling8334/Matplot3D-for-Java
二、什么是处理器
Matplot3D for JAVA 的处理器(Processor)是一类对象,它们的作用是将数据转换成对应图表的三维模型,负责数据处理和建模,无需使用者直接编写具体模型的代码,只需要将数据加入到处理器中,再通过Matplot3D4JMgr对象将图表直接在绘制到界面上或者输出为图像文件即可。一个Matplot3D4JMgr对象可以“持有”多个不同类型的处理器对象。
三、DataGridProcessor处理器
DataGridProcessor是数据网格处理器,可以绘制连续的曲面。数据网格是指一个在x,y方向都均匀排列的点阵列。使用一个Double[][]表示数据,数组中的值为z(高度)值。x,y方向由于均匀分布,在指定了各自的取值范围后即可换算为空间坐标的x,y坐标。需要注意的是Double[][]中存放的为数据,和显示的分段数不一定相等(譬如说数据数组是100*100的,但3维曲面模型的分段数可以使90*80)。
四、基本使用方法
DataGridProcessor的编程模式如下:
DataGridProcessor processor = new DataGridProcessor(); //创建一个数据处理器
Matplot3D4JMgr mgr=new Matplot3D4JMgr(processor); //创建图形管理器
Double[][] datas=new Double[20][20]; //数据阵列
//......准备数据
//向pricessor对象加入数据
processor.addData(datas, "Name", new Range(0, 300), new Range(0, 300), 100, 100, cs, 1f);
mgr.show();
其中datas表示用户的数据,上面的代码中并没有数据。 现生成一些演示数据如下:
DataGridProcessor processor = new DataGridProcessor();
Matplot3D4JMgr mgr=new Matplot3D4JMgr(processor);
//----------以下生成一些演示数据
Double[][] datas=new Double[20][20];
Random random=new Random();
for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[0].length;j++) {
if(i+j<20)
datas[i][j]=(double)i+j+random.nextDouble();
else
datas[i][j]=40d-i-j-random.nextDouble();
}
}
processor.setClose3DObject(true); //设置是否是封闭三维对象
mgr.setScaleZ(1.2); //z方向伸拉至原始的1.2倍
mgr.setTitle("Demo 数据阵列");
processor.setShowGrid(false);//不显示辅助网格线
//创建一个颜色风格
ColorStyle cs = new TopBottomColorStyle(ColorStyle.DEFAULT_COLORS_ARRAY);
processor.addData(datas, "", new Range(0, 100), new Range(100, 200), 20, 20, cs, 1f);
mgr.show();
运行效果如下:
主要方法介绍:
public void setClose3DObject(boolean flag)
设置是否封闭三维对象,如果为false则只显示数据曲面,如果为true则将曲面、和自适应的水平的矩形平面和四周的垂直面将三维图形围蔽。
public void addData(Double[][] data, String name, Color color, Range rangeX, Range rangeY, int stepX, int stepY, float alpha)
加入一组数据阵列数据,并指定x,y范围,显示分段,颜色和不透明度。参数说明如下:
1、data:数据阵列二维数组,第一个下标代表X方向,第二个下标代表Y方向;
2、name:名字;
3、color:颜色;
4、rangeX:数据在X方向的分布范围;
5、rangeY:数据在Y方向的分布范围;
6、stepX :X方向的显示分段;
7、stepY:Y方向的显示分段;
8、alpha:不透明度(0为完全透明,1.0为完全不透明)。
public void addData(Double[][] data, String name, Range rangeX, Range rangeY, int stepX, int stepY, ColorStyle cs, float alpha)
此方法与前一个方法类似,只是使用ColorStyle 最为参填充而不是用一种纯色填充。注意参数的顺序是和上面的方法不一样的。
五、举个栗子:绘制高程地形图
现在我们来写一个稍微高级一点点的栗子:绘制高程地形图。就像下面这样:
是不是看起来还挺酷的,实际上DataGridProcessord的使用方法和上面简单的栗子并没有多少差别,差别在于数据。同时根据数据的分布密度设置合理的分段数进行建模和绘制。
需要特别指出的是,由于这是一个JAVA纯软件实现的三维引擎,完全依靠JVM和CPU运算以及靠内存存储数据,当模型数据过多时会占用较多内存,“刷新时”也会占用较多CPU时间。所以根据自己电脑的实际配置和性能来决定模型的精细度。根据我的4年前的破电脑的使用经验,如果是需要实时刷新我觉得最好把模型的片面数控制在10W个以内,例如分段数设定为300*300时,模型的片面数就会超过9W个。大家可以根据自己电脑的实际配置测试一下在可接受的刷新效率下的极限精细度。
关于性能方面的底层问题,我还在还在持续改进中,看看使用CPU计算时JAVA的极限能达到什么程度。
言归正传,地形数据怎么生成。首先从互联网上找到高程数据,高程数据一般都是测绘卫星测绘获得的,有多种格式,大家有兴趣可以研究研究。我是从下面这个网站下载高程数据的:
http://viewfinderpanoramas.org/Coverage%20map%20viewfinderpanoramas_org3.htm
选择某一个方格可以下载那一区域的高程数据,下载后解压可以得到一堆后缀是 .hgt的文件。文件是以一个方格区域的右上角的经纬度命名的,每个文件代表的方格跨度分别是经度的1度和纬度的1度。例如N08E036.hgt文件包含的就是北纬8度到北纬9度,东经36度到东经37度这一区域的地形高程数据。每个文件的大小是一致的,共包含1442401个采样点的高程数据,可以看到每个文件的大小是2884802字节,就是说每个采样点数据占用了两个字节。
同时可以按照地球的半径换算:,结果大约是90m,也就是说这些hgt文件的数据精度是90米。每一个文件涵盖的区域长宽大概是108km,而我们知道地球最高的海拔也只有8800多米,所以整块绘制体现不出地形高度起伏,可以只提取其中的一个子区域绘制。
例如提取文件“ N27E086.hgt ” 文件其中300*300个点表示的区域进行绘制。这个区域位于喜马拉雅山脉,因此地形起伏会比较明显。
hgt文件数据提取代码如下:
/**
* @param fileName 文件名
* @param offsetU 提取区域右上角的经度偏移量
* @param offsetV 提取区域右上角的维度偏移量
* @param countU 经度提取点数量
* @param countV 维度提取点数量
* @return 高程数据数组
*/
Double[][] readHgtFile(String fileName,int offsetU,int offsetV,int countU,int countV) throws Exception{
File file = new File(fileName);
DataInputStream dio = new DataInputStream(new FileInputStream(file));
int x=0,y=0;
Double[][] datas=new Double[countU][countV];
byte[] buf=new byte[2402];
for(int i=0;i<1201;i++) {
dio.read(buf);
if(i<offsetU||i>=offsetU+countU)
continue;
for(int k=0;k<buf.length;k+=2) {
if(k/2<offsetV||k/2>=offsetV+countV)
continue;
datas[x][y]=(double)((buf[k] << 8) | (buf[k+1] & 0xff));
y++;
}
y=0;
x++;
}
dio.close();
return datas;
}
接下来是主程序,因为我们需要取纵横300个点,按照点间90米计算,跨度大约为27公里,因此绘图范围的跨度应该设置为27000,分段数可随意,这里设置为270,使片面数不至于很大。主程序如下:
public static void main(String[] args) throws Exception {
final Double[][] datas = readHgtFile("./demo_data/N27E086.hgt",139,100,301,300);
DataGridProcessor processor = new DataGridProcessor();
Matplot3D4JMgr mgr=new Matplot3D4JMgr(processor);
mgr.setTitle("高程地形");
mgr.setShowReferencePlanes(false); //不显示坐标系
processor.setShowGrid(false); //不显示网格线
processor.addData(datas, "",Color.LIGHT_GRAY, new Range(0, 27000), new Range(0, 27000), 270, 270, 1f);
processor.setClose3DObject(true);
mgr.show();
}
执行程序显示如图:
是不是已经有点意思了。但是颜色只有灰色,我们希望可以根据高度显示不同的颜色,就是高度用颜色也能体现出来。 还有高光感看起来感觉有一点像金属表面。那么就做一下细节调整。用颜色数组创建ColorStyle对象,并在添加数据时设置,同时关闭高光渲染。调整代码调整如下:
ColorStyle cs = new TopBottomColorStyle(new Color[] {Color.BLUE,Color.GREEN,Color.YELLOW,Color.RED.brighter(),Color.RED.darker()});
processor.addData(datas, "", new Range(0, 27000), new Range(0, 27000), 270, 270, cs, 1f);
processor.setHighlight(false);
注意使用ColorStyle的 addData 方法的参数顺序和使用Color作为参数的方法是不一样的,效果如下:
关闭高光渲染时效果如下:
是不是挺酷