Python制图+Java数据处理---高效制作雷评报告
1.前言
1.1总体思路
最近接到一个任务,通过使用近11年的雷电数据对给定经纬度站点的雷击情况进行统计分析,并制作相应的统计图。任务很简单,需求也很简单。无非就是数据提取,筛选,计算,并画图,但是就是烦,站点很多,数据很杂,格式不统一。可以做的工具有很多,matlab,python,java,Arcgis等等。善其事,利其器。本次项目,我们使用Python(画图),用Java完成数据的处理与雷评报告的自动生成。其实python可以完成整个操作流程,但是因为一些原因,还是配合使用处理吧。下图是整个工程的流程思路。整个流程很简单。最重要的是预处理环节,涉及到各种工具类的编写。接下来我们针对每一个环节写代码。
1.2工程结构
工程的结构也比较简单。为了优雅一点。我们仍然创建一个maven工程。还是老三样,实体类,任务,以及工具类。后续如果想部署到服务器,并给出相应的Api接口,需要更改项目结构,并引入其他东西。这都是后话了,有空再搞。
2.工具类编写
工具类的使用,可以大幅优化我们的项目结构,减少代码量。完成代码的复用。
2.1距离工具类编写getDistance
为了提取站点周围2km或者3km的闪击点要素。我们需要计算辖区内各个闪击点与站点的距离。给定两个点A和B,已知两点的经纬度值,和地球半径。我们可以很快计算得到两者之间的距离。
public class getDistance {
public static double getDis(double latitude1, double longitude1,
double latitude2, double longitude2) {
// 纬度
double lat1 = Math.toRadians(latitude1);
double lat2 = Math.toRadians(latitude2);
// 经度
double lng1 = Math.toRadians(longitude1);
double lng2 = Math.toRadians(longitude2);
// 纬度之差
double a = lat1 - lat2;
// 经度之差
double b = lng1 - lng2;
// 计算两点距离的公式
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(b / 2), 2)));
// 弧长乘地球半径, 返回单位: 千米
s = s * 6370.856;
return s;
}
}
该工具类返回值就是Double类型的两点间距离。
2.2合格条件站点工具类编写getStation
上面我们已经有了计算两点间距离的工具类。接下来我们开始计算所有符合条件的点。最后将所有符合条件的点放入到一个集合当中,返回,供接下来的任务使用。
我们的闪击数据是存放在excel中的。所以我们需要在pom.xml文件中引入相应的依赖。有了这个依赖,我们可以方便的使用java来对excel表格进行处理。
数据示例
<!--处理excel-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
在计算过程中,为了对计算得到的点进行描述,我们需要创建一个bean类来对其进行描述。这就是面向对象的思想。万物皆对象。我们把这个bean类命名为stanData。其代码很简单,每一个属性都是和原来在excel中的属性相对应,虽然有很多属性在后面都用不上,但是为了保证数据的完整性,这里还是将其写了出来。篇幅有限,相应get,set方法已省略。
public class standData {
private String mapinfo;
private String id;
private String time;
private String hour;
private String minute;
private String second;
private String lat;
private String lon;
private String strength;
private String gradient;
private String error;
private String method;
private String province;
private String country;
private String city;
}
通过上面的准备,我们可以开始编写getStation工具类了。其实这里写的有一点不好,把有的参数写死了。后续可以优化一下。
public class getStation {
/**
*
* @param file 所要处理的excel文件
* @param rule1 想要所在辖区的名字(需要和excel中名字一致)
* @param latitude 站点的纬度
* @param longitude 站点的经度
* @param distance 圆的半径距离(如站点周围2km,则设置为2.0)
* @return 返回的是符合规则的点的对象集合
*/
public static ArrayList<standData> get(String file, String rule1, Double latitude, Double longitude, Double distance){
XSSFWorkbook book = null;
try {
book = new XSSFWorkbook(file);
//获取工作表
XSSFSheet sheet = book.getSheetAt(0);
//获得行数
int rows = sheet.getPhysicalNumberOfRows();
//System.out.println(rows);
ArrayList<standData> ruleList = new ArrayList<standData>();
for (int i = 1; i < rows; i++) {
XSSFRow row = sheet.getRow(i);
String s = row.getCell(13).toString();
if (s.equals(rule1)) {
standData standData = new standData();
standData.setMapinfo(row.getCell(0).toString());
standData.setId(row.getCell(1).toString());
standData.setTime(row.getCell(2).toString());
standData.setHour(row.getCell(3).toString());
standData.setMinute(row.getCell(4).toString());
standData.setSecond(row.getCell(5).toString());
standData.setLat(row.getCell(6).toString());
standData.setLon(row.getCell(7).toString());
standData.setStrength(row.getCell(8).toString());
standData.setGradient(row.getCell(9).toString());
standData.setError(row.getCell(10).toString());
standData.setMethod(row.getCell(11).toString());
standData.setProvince(row.getCell(12).toString());
standData.setCountry(row.getCell(13).toString());
standData.setCity(row.getCell(14).toString());
ruleList.add(standData);
}
}
//System.out.println(ruleList.size());
//开始筛选出三公里
ArrayList<standData> distanceList = new ArrayList<standData>();
for (int i = 0; i < ruleList.size(); i++) {
String lat = ruleList.get(i).getLat();
String lon = ruleList.get(i).getLon();
double dis = getDistance.getDis(parseDouble(lat), parseDouble(lon), latitude, longitude);
if (dis <= distance) {
//System.out.println(dis);
distanceList.add(ruleList.get(i));
}
}
return distanceList;
} catch (Exception e) {
}
return null;
}
}
通过实例化该工具类,我们可以获得一个站点周围2km/3km范围内的所有闪击点的各种要素的list集合。
2.3需求工具类编写createData
上面我们得到了所有符合条件的点。但是这还是不行的。我们需要对这些数据进行相应的统计分析。得到正闪,负闪的最大强度,平均强度,年份对应的闪击数,月份对应的闪击数,每小时对应的闪击数。以及每个闪击强度范围对应的闪击数。具体如下表。
2011-2020闪击次数 | 正闪次数 | 平均正闪强度 | 最大正闪强度 | 负闪次数 | 平均负闪强度 | 最大负闪强度 |
---|---|---|---|---|---|---|
针对上面的表格,我们要创建一个light的bean类来对其进行描述。
public class light {
private int times;
//Positive number
private double positiveTimes;
//negative number
private double negativeTimes;
//averagePositive value
private double averagePositive;
//averageNegative value
private double averageNegative;
//最大正闪
private double maxPositive;
//最大负闪
private double maxNegative ;
//每年发生的闪击次数
private ArrayList<Integer> yearTimes;
private ArrayList<Integer> monthTimes;
private ArrayList<Integer> dayTimes;
private ArrayList<Integer> rangeTimes;
private ArrayList<map> yearTimesMap;
private ArrayList<map> monthTimesMap;
private ArrayList<map> dayTimesMap;
private ArrayList<map> rangeTimesMap;
private ArrayList<standData> standDataArrayList;
}
同样的,省略掉get与set方法。
有了light对象后,我们只需要通过各种计算,然后将各个参量set到里面,然后作为返回值返回出去。篇幅有限,这里只针对最“扯淡”的范围闪击数进行记录。
1.因为各个闪击强度有正负之分,但这里不考虑正负号,对所有的闪击进行求绝对值。
2.因为事先不知道每个闪击数值的大小,所以我们并不能事先声明好各个数值范围。
3.因为闪击范围一般是按照0-10,10-20,20-30这种形式写的。所以我们将(闪击强度/10)取整作为索引值,闪击强度作为对应的值。之后,每一个闪击强度都进行相应的操作。这样,每一个闪击点都会有一个索引值。然后统计每个索引值对应的个数。最后,根据索引值,可以将索引再转换位范围字符串。
如:
3.报告编写与excel生成(getReport)
在createData工具类中,我们返回了一个light对象。这个light对象包含了我们所需要的所有信息。所以我们在main函数中只需要将其实例化就可以了。
light light = createData.getExcel(file, rule1, latitude, longitude, distance);
在报告中,我们还会遇到下面这种情况,问你哪个时间段,哪一年的闪击次数最多。
这是我们就需要对实例化的结果进行排序。Java提供了一种比较快捷的比较接口Comparator。我们新建一个MyComparator类实现Comparator。
public class MyComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
map e1=(map)o1;
map e2=(map)o2;
return e1.getTimes()- e2.getTimes();
}
}
然后实例化该类
ArrayList<map> yearTimesMap = light.getYearTimesMap();
MyComparator myComparator = new MyComparator();
Collections.sort(yearTimesMap, myComparator);
这样我们的数组就会按照从大到小的顺序进行排列。很方便,哈哈哈哈。
最后,因为Java画图很不方便。所以我们将各种数据存入到一个excel里,开始使用python进行画图操作。
4.使用Python一键出图
有了数据,没有图怎么行?python提供了一个画图的包。功能和matlab很像,功能强大。
from pylab import mpl
import matplotlib.pyplot as plt
from matplotlib.pyplot import MultipleLocator
由于我们这次任务画的图主要是散点图,条形图和柱状图,不需要进行复杂的操作,所以比较简单。由于并不是很熟悉python的工程结构,所以代码写的不是很优雅。想到哪写到哪。比较垃圾。不过,又不是不能用,哈哈哈哈哈。
首先是散点图
#设置画幅大小
plt.figure(figsize=(10, 5))
plt.rcParams['savefig.dpi'] = 300 #图片像素
plt.rcParams['figure.dpi'] = 300
# 画散点图
plt.scatter(lon, lat,c='black',marker='.')
plt.scatter(lon1,lat1,c='red',marker='.')
plt.plot(linewidth = '0.5',color='#000000')
#设置横纵坐标的名称以及对应字体格式
font2 = {'weight':'normal','size': 20,}
plt.xlabel("经 度",font2)
plt.ylabel("纬 度",font2)
#设置坐标字体
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
//设置图片保存路径
plt.savefig(a+'.png')
plt.show()
条形图和折线图的绘制和这个类似。不赘述了。
5.操作示意图
5.1IDE内直接启动
这个是最方便的,也是最容易改需求的。我们直接在task中运行main方法。
他会自动弹出可视化窗口,让我们来选择要处理的文件。以及各种参数。
最后选择保存路径,就可以静待报告的自动生成了。
5.2桌面工具exe启动
我将jar包打包成了一个可执行文件。就叫做light.exe。没有给他设置icon。功能和操作步骤是上面的一致。
5.3网页端启动
我们还可以把jar包打包好,放到服务器上。然后给出相应的接口,我们再写好前端页面。这样就可以随时随地在任意有网络的地方写报告了。这也是后话,可以参考一下之前写的掩星数据可视化展示疾风亦有龟途-作品 (lolxiaoguo.cn)
6.结语
最后,看到的同志,可以点一点网站为数不多的广告,在寒冷的冬天,给小郭凑一杯奶茶钱。哈哈哈哈哈。
版权属于: 依依东望