java实现NC数据等值线等值面可视化

工作中的项目需要将NC科学数据进行等值线等值面处理,并展示给用户。网上找资料真费劲,基本没有直接可参考的,自己收集了些资料,参考着同事的思路实现效果了,跟大家分享下,也给自己做个记录。

刚开始写博客,写的不好的地方请大家见谅。

先上效果图。

由于数据边界比较敏感就给地图打马赛克了,截图也只有部分区域,不影响效果。

思路

1.首先java后端加载nc文件,读取nc变量的值,一般是多维数组。java读取NC文件用的是netcdf-4.3.22.jar。

2.通过第三方jar包对数组数据进行等值线处理,我用的wContour.jar,文末有下载链接。

3.后端生成的等值线数据返回到前端,通过leaflet展示。

java后端代码

主方法

	//生成等值线主方法
	public static Map<String, Object> isolineProcess() throws IOException{
		Map<String, Object> resMap = new HashMap<String, Object>();
		String filePath = "";//文件路径
		String element = "";//nc变量名
		int depthIndex = 1;//nc文件深度序列
		int timeIndex = 1;//nc文件时间序列
		//获取NC的数据
        Map map = getNcData(filePath,element,depthIndex,timeIndex);
        if(map == null || map.size()==0){
        	return resMap;
        }
        //等值线等值面间隔,间隔越小:过渡越均匀,性能消耗越大,响应越慢
        double[] dataInterval = new double[]{0,1,2,3,4, 5,6,7,8,9, 10,11,12,13,14,15, 
        		16,17,18,19, 20,21,22,23,24, 25,26,27,28,29, 30, 35};
        String strGeojson = nc2EquiSurface(map, dataInterval);
        resMap.put("geojson", strGeojson);
        resMap.put("dataArr", map.get("eleData"));
        return resMap;
	}

处理nc:

// 获取nc数据
	public static <T> Map getNcData(String ncpath,String element,int depthIndex,int timeIndex) throws IOException {
		String lonVarName = "lon";//经度变量
        String latVarName = "lat";//纬度变量
		//加载nc文件
		NetcdfFile ncfile =  NetcdfDataset.open(ncpath);
		//读取经纬度数据
		Variable varLon = ncfile.findVariable(lonVarName);
		Variable varLat = ncfile.findVariable(latVarName);
		Object lonObj = null;
		Object latObj = null;
		lonObj = (Object) varLon.read().copyToNDJavaArray();
		latObj = (Object) varLat.read().copyToNDJavaArray();
		Map map = new HashMap();
		map = readNCLonLat(map,lonObj,latObj);
		int latLength = (int) map.get("latLength");
		int lonLength = (int) map.get("lonLength");
	
		//读取变量数据
		Variable varPre = ncfile.findVariable(element);
		int[] org = { timeIndex, depthIndex, 0, 0 };// 每一维的起点,起点从0开始
		int[] sha = { 1, 1, latLength, lonLength };// 相应维读取的条数
		/*
		 * nc数据类型可能是short、folat、double,所以用Object接收,通过反射判断数据类型
		 */
		Object pre = null;
		try {
			pre = (Object) varPre.read(org,sha).copyToNDJavaArray();
		} catch (InvalidRangeException e) {
			e.printStackTrace();
		}
		String className = pre.getClass().getComponentType().getName();
		
		Double invalidValueDou = -9999.0;//无效值
		Double scaleFactorDou = 1.0;//比例因子数值
		/*
		 * 这里只判断了short数组,其他数据类型的判断类似.
		 * 我的数据是四维的:经纬度,时间,深度。
		 */
		if("[[[s".equalsIgnoreCase(className)){
			short[][][][] dataArr = (short[][][][])pre;
			//创建新的double数组保存数据
			double[][] dPre = new double[dataArr[0][0].length][dataArr[0][0][0].length];
            //循环将short数组转为double数组
            for (int i = 0, len = dataArr[0][0].length; i < len; i++) {
            	short[] _pre = dataArr[0][0][i];
                for (int j = 0, jlen = _pre.length; j < jlen; j++) {
                	Double value = Double.parseDouble(String.valueOf(_pre[j]));
                	if(invalidValueDou != null && Double.compare(value,invalidValueDou)==0){
                		dPre[i][j] = invalidValueDou;
                		continue;//无效值跳过
                	}
                	if(scaleFactorDou == null){//比例因子处理
                		dPre[i][j] = value;
                	}else{
                		dPre[i][j] = value * scaleFactorDou;
                	}
                    
                }
            }
            map.put("eleData", dPre);
		}
		return map;
	}
	
	//处理nc经纬度
	private static Map readNCLonLat(Map map, Object lonObj, Object latObj) {
		/**
		 * nc数据类型可能是short、folat、double,所以用Object接收,通过反射判断数据类型,
		 * 这里只判断了short数组,其他数据类型的判断类似
		 */
		String lonclassName = lonObj.getClass().getComponentType().getName();
		int lonLength = 1;
		int latLength = 1;
		if ("[s".equalsIgnoreCase(lonclassName)) {
			short[] lonArr = (short[]) lonObj;
			short[] latArr = (short[]) latObj;
			lonLength = lonArr.length;
			latLength = latArr.length;
			//创建double数组存放经纬度数据,方便后续计算
			double[] dLon = new double[lonArr.length], dLat = new double[latArr.length];
			for (int i = 0, len = lonArr.length; i < len; i++) {
				dLon[i] = Double.parseDouble(String.valueOf(lonArr[i]));
			}
			for (int i = 0, len = latArr.length; i < len; i++) {
				dLat[i] = Double.parseDouble(String.valueOf(latArr[i]));
			}
			map.put("lon", dLon);
			map.put("lat", dLat);
		}
		return map;
	}

 

生成等值线:

//nc数据生成等值线数据
    public static String nc2EquiSurface(Map ncData, double[] dataInterval) {
        String geojsonpogylon = "";
 
        List<PolyLine> cPolylineList = new ArrayList<PolyLine>();
        List<Polygon> cPolygonList = new ArrayList<Polygon>();
 
        double[][] _gridData = (double[][]) ncData.get("eleData");
        int[][] S1 = new int[_gridData.length][_gridData[0].length];
        double[] _X = (double[]) ncData.get("lon"), _Y = (double[]) ncData.get("lat");
        double _undefData = Double.parseDouble((String)ncData.get("invalid"));
        List<Border> _borders = Contour.tracingBorders(_gridData, _X, _Y,
                S1, _undefData);
        int nc = dataInterval.length;
        cPolylineList = Contour.tracingContourLines(_gridData, _X, _Y, nc,
                dataInterval, _undefData, _borders, S1);// 生成等值线
 
        cPolylineList = Contour.smoothLines(cPolylineList);// 平滑
        cPolygonList = Contour.tracingPolygons(_gridData, cPolylineList,
                _borders, dataInterval);
 
        geojsonpogylon = getPolygonGeoJson(cPolygonList);
 
        return geojsonpogylon;
    }
    
    //拼装geogson
    public static String getPolygonGeoJson(List<Polygon> cPolygonList) {
		String geo = null;
		String geometry = " { \"type\":\"Feature\",\"geometry\":";
		String properties = ",\"properties\":{ \"value\":";
		
		String head = "{\"type\": \"FeatureCollection\"," + "\"features\": [";
		String end = "  ] }";
		if (cPolygonList == null || cPolygonList.size() == 0) {
			return null;
		}
		try {
			for (Polygon pPolygon : cPolygonList) {
				List<Object> ptsTotal = new ArrayList<Object>();
					for (PointD ptd : pPolygon.OutLine.PointList) {
						List<Double> pt = new ArrayList<Double>();
						pt.add(doubleFormat(ptd.X));
	                    pt.add(doubleFormat(ptd.Y));
						ptsTotal.add(pt);
					}
				List<Object> list3D = new ArrayList<Object>();
				list3D.add(ptsTotal);
				JSONObject js = new JSONObject();
				js.put("type", "Polygon");
				js.put("coordinates", list3D);
				
				geo = geometry + js.toString() + properties  +pPolygon.LowValue + "} }" + "," + geo;
			}
			if (geo.contains(",")) {
				geo = geo.substring(0, geo.lastIndexOf(","));
			}
			
			geo = head + geo + end;
		} catch (Exception e) {
			e.printStackTrace();
			return geo;
		}
		return geo;
	}
    
    /**
   	 * double保留两位小数
   	 */
   	public static double doubleFormat(double d) {  
           BigDecimal bg = new BigDecimal(d);  
           double f1 = bg.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();  
           return f1;
       } 

后端代码基本就这些,最后返回的是geoJson格式的数据。后端处理过程还是比较慢的,我这个效果本地测试大约3秒,如果用MATLAB做应该会好些,但是不会MATLAB~~~

其实到这基本就可以了,前端拿着数据渲染就行,我是用leaflet实现的。

等之后有时间了在把前端js代码补充下。

另外还有一种方法是把nc数据直接返回到前端,js进行等值线处理,这种方法就是比较消耗浏览器性能。

 


2020-12-12更新,

netcdf-4.3.22.jar和wContour.jar上传了,需要的朋友点击下载

把前端代码补上: 前端通过ajax获取到数据后渲染

// 添加等值线,等值面
function loadLine(geojsonData) {
	
	var maxTemp = 35;
	var minTemp = 0;
	var rgbValue = "";
	$.getJSON('data/json/color.json',function(colorJson){
		let num = colorJson.colorList.length;
		arr = geojsonData.features;
		// 循环添加多边形
		for (var i = 0; i < arr.length; i++) {
			var dataone = arr[i].geometry.coordinates[0];
			for (var j = 0; j < dataone.length; j++) {
				var temp1 = dataone[j][0];
				var temp2 = dataone[j][1];
				dataone[j][0] = temp2;
				dataone[j][1] = temp1;
			}
			//var value = Math.round(arr[i].properties.value);
			var value = arr[i].properties.value;
			// console.log(value);
			//获取颜色rgb.颜色赋值
			var rgb = "";
			if(value >= 30){  //超过30°取最后一个
				rgb =  "rgb("+colorJson.colorList[212].color213+")";
			}else if(value <= 0){  //低于0°取第一个
				rgb =  "rgb("+colorJson.colorList[0].color1+")";
			}else{  //0°~30°
			let interval = 30/num; //最高30°,最低0°
			let index = parseInt(value/interval);
			let colorObj = colorJson.colorList[index-1];
			let name = "color"+index;
			rgb =  "rgb("+colorObj[name]+")";
			}
			polygon = L.polygon(dataone).addTo(polygon_group);
			polygon.setStyle({
				color : rgb,// 
				//color :"rgb(0,192,255)",
				weight : 0,
				fillOpacity : 1
			});
		}
		
	});
	//创建图层,设置层级
	map.createPane('ncline');
	map.getPane('ncline').style.zIndex = 800;
	L.geoJson(geojsonData, {
		 color: "black",
		 fill:false,
		 pane: 'ncline',
		 weight: 1,
		 lineCap:"5",
		 text:"25"
	}).addTo(polygon_group);
	polygon_group.addTo(map);
}

 

总结:上述代码等值线效果还行,不过最近用了geoserver,感觉展示效果也不错,大家也可以试试

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值