[GIS small case] Isosurface map based on Java + Geotools + wContour

1 Introduction  

The isosurface map is a very suitable method to show the distribution of rainfall data. Taking the rainfall of a certain day in Jinzhou City as an example, there are about 100 rainfall stations distributed throughout Jinzhou City. The first method I tried was to use kriging.js to generate the isosurface, but because the border area of ​​Jinzhou City is too large, the pressure on the browser client to generate is very high, and the speed is also very slow. So try to generate it on the server side, and then call the display on the client side.

2. Two ways of isosurface generation  

2.1 Server generation

The common way is to process data on the server side based on Java + Geotools + wContour, and the applicable businesses are as follows:

  • Large-scale and high-density spatial data interpolation
  • The server can set up timing tasks, pipeline processing of raw data, and generate target data (picture or GeoJSON data)

2.2 Client generation _ 

The common method is to interpolate data at the front end, generate grid data, and then generate isolines or isosurfaces. The applicable services are as follows:

  • High data update frequency and strong real-time performance
  • On-demand service, reducing server pressure

You can use the following methods:

  • Use turf.js built-in functions to generate vector data through discrete point interpolation, isosurface drawing, and clipping, and then draw the data
  • Using kriging.js built-in functions, discrete points generate grid data and then grid rendering
  • Combine the advantages of both turf.js and kriging.js to improve interaction performance, interpolation effect and display effect

3 Process data on the server side based on Java + Geotools + wContour  

Generate the result Figure 1 (the border is not clipped):

Generate the result Figure 2 (boundary clipping):

 Full code:

package com.example.demo.meteoInfoTest;

import cn.hutool.json.JSONArray;
import com.tt.CommonMethod;
import com.tt.GeoJSONUtil;
import org.geotools.data.DataUtilities;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.json.simple.JSONObject;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;

import org.opengis.feature.simple.SimpleFeatureType;
import wcontour.Contour;
import wcontour.Interpolate;
import wcontour.global.Border;
import wcontour.global.PointD;
import wcontour.global.PolyLine;
import wcontour.global.Polygon;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.*;

public class EquiSurface {
    /**
     * 生成等值面
     *
     * @param trainData    训练数据
     * @param dataInterval 数据间隔
     * @param size         大小,宽,高
     * @param boundryFile  四至
     * @param isclip       是否裁剪
     * @return
     */
    public String calEquiSurface(double[][] trainData,
                                 double[] dataInterval,
                                 int[] size,
                                 String boundryFile,
                                 boolean isclip) {
        String geojsonpogylon = "";
        try {
            double _undefData = -9999.0;
            SimpleFeatureCollection polygonCollection = null;
            List<PolyLine> cPolylineList = new ArrayList<PolyLine>();
            List<Polygon> cPolygonList = new ArrayList<Polygon>();

            int width = size[0],
                    height = size[1];
            double[] _X = new double[width];
            double[] _Y = new double[height];

            File file = new File(boundryFile);
            ShapefileDataStore shpDataStore = null;

            shpDataStore = new ShapefileDataStore(file.toURL());
            //设置编码
            Charset charset = Charset.forName("GBK");
            shpDataStore.setCharset(charset);
            String typeName = shpDataStore.getTypeNames()[0];
            SimpleFeatureSource featureSource = null;
            featureSource = shpDataStore.getFeatureSource(typeName);
            SimpleFeatureCollection fc = featureSource.getFeatures();

            double minX = fc.getBounds().getMinX();
            double minY = fc.getBounds().getMinY();
            double maxX = fc.getBounds().getMaxX();
            double maxY = fc.getBounds().getMaxY();

            Interpolate.createGridXY_Num(minX, minY, maxX, maxY, _X, _Y);

            double[][] _gridData = new double[width][height];

            int nc = dataInterval.length;

            _gridData = Interpolate.interpolation_IDW_Neighbor(trainData,
                    _X, _Y, 12, _undefData);// IDW插值
            int[][] S1 = new int[_gridData.length][_gridData[0].length];
            List<Border> _borders = Contour.tracingBorders(_gridData, _X, _Y,
                    S1, _undefData);

            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);

            if (isclip) {
                polygonCollection = GeoJSONUtil.readGeoJsonByString(geojsonpogylon);
                SimpleFeatureCollection sm = clipPolygonFeatureCollection(fc, polygonCollection);
                FeatureCollection featureCollection = sm;
                FeatureJSON featureJSON = new FeatureJSON();
                StringWriter writer = new StringWriter();
                featureJSON.writeFeatureCollection(featureCollection, writer);
                geojsonpogylon = writer.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return geojsonpogylon;
    }

    /**
     * 裁剪图形
     * @param fc
     * @return
     */
    private SimpleFeatureCollection clipPolygonFeatureCollection(FeatureCollection fc,
                                                                 SimpleFeatureCollection gs) throws SchemaException, IOException {
        SimpleFeatureCollection simpleFeatureCollection = null;
        SimpleFeatureType TYPE = DataUtilities.createType("polygons",
                "the_geom:MultiPolygon,lvalue:double,hvalue:double");
        List<SimpleFeature> list = new ArrayList<>();
        SimpleFeatureIterator contourFeatureIterator = gs.features();
        FeatureIterator dataFeatureIterator = fc.features();
        while (dataFeatureIterator.hasNext()){
            SimpleFeature dataFeature = (SimpleFeature) dataFeatureIterator.next();
            Geometry dataGeometry = (Geometry) dataFeature.getDefaultGeometry();
            contourFeatureIterator = gs.features();
            while (contourFeatureIterator.hasNext()){
                SimpleFeature contourFeature = contourFeatureIterator.next();
                Geometry contourGeometry = (Geometry) contourFeature.getDefaultGeometry();
                Double lv = (Double) contourFeature.getProperty("lvalue").getValue();
                Double hv = (Double) contourFeature.getProperty("hvalue").getValue();
                if (dataGeometry.getGeometryType() == "MultiPolygon"){
                    for (int i = 0; i <dataGeometry.getNumGeometries(); i++){
                        Geometry geom = dataGeometry.getGeometryN(i);
                        if (geom.intersects(contourGeometry)){
                            Geometry geo = geom.intersection(contourGeometry);
                            SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
                            featureBuilder.add(geo);
                            featureBuilder.add(lv);
                            featureBuilder.add(hv);
                            SimpleFeature feature = featureBuilder.buildFeature(null);
                            list.add(feature);

                        }
                    }

                } else {
                    if (dataGeometry.intersects(contourGeometry)){
                        Geometry geo = dataGeometry.intersection(contourGeometry);
                        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
                        featureBuilder.add(geo);
                        featureBuilder.add(lv);
                        featureBuilder.add(hv);
                        SimpleFeature feature = featureBuilder.buildFeature(null);
                        list.add(feature);

                    }

                }

            }
        }

        contourFeatureIterator.close();
        dataFeatureIterator.close();
        simpleFeatureCollection = new ListFeatureCollection(TYPE, list);

        return simpleFeatureCollection;

    }

    private String getPolygonGeoJson(List<Polygon> cPolygonList) {
        String geo = null;
        String geometry = " { \"type\":\"Feature\",\"geometry\":";
        String properties = ",\"properties\":{ \"hvalue\":";

        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>();
                List<Object> pts = new ArrayList<Object>();

                PolyLine pline = pPolygon.OutLine;

                for (PointD ptD : pline.PointList) {
                    List<Double> pt = new ArrayList<Double>();
                    pt.add(ptD.X);
                    pt.add(ptD.Y);
                    pts.add(pt);
                }

                ptsTotal.add(pts);

                if (pPolygon.HasHoles()) {
                    for (PolyLine cptLine : pPolygon.HoleLines) {
                        List<Object> cpts = new ArrayList<Object>();
                        for (PointD ccptD : cptLine.PointList) {
                            List<Double> pt = new ArrayList<Double>();
                            pt.add(ccptD.X);
                            pt.add(ccptD.Y);
                            cpts.add(pt);
                        }
                        if (cpts.size() > 0) {
                            ptsTotal.add(cpts);
                        }
                    }
                }

                JSONObject js = new JSONObject();
                js.put("type", "Polygon");
                js.put("coordinates", ptsTotal);
                double hv = pPolygon.HighValue;
                double lv = pPolygon.LowValue;

                if (hv == lv) {
                    if (pPolygon.IsClockWise) {
                        if (!pPolygon.IsHighCenter) {
                            hv = hv - 0.1;
                            lv = lv - 0.1;
                        }

                    } else {
                        if (!pPolygon.IsHighCenter) {
                            hv = hv - 0.1;
                            lv = lv - 0.1;
                        }
                    }
                } else {
                    if (!pPolygon.IsClockWise) {
                        lv = lv + 0.1;
                    } else {
                        if (pPolygon.IsHighCenter) {
                            hv = hv - 0.1;
                        }
                    }

                }

                geo = geometry + js.toString() + properties + hv
                        + ", \"lvalue\":" + lv + "} }" + "," + geo;

            }
            if (geo.contains(",")) {
                geo = geo.substring(0, geo.lastIndexOf(","));
            }

            geo = head + geo + end;
        } catch (Exception e) {
            e.printStackTrace();
            return geo;
        }
        return geo;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        EquiSurface equiSurface = new EquiSurface();
        CommonMethod cm = new CommonMethod();

        // 雨量站点经纬度及雨量值
        JSONArray jsonArray = GeoJSONUtil.yuliang();
        double[][] trainData = new double[jsonArray.size()][3];
        for (int i = 0; i < jsonArray.size(); i++){
            trainData[i][0] = jsonArray.getJSONObject(i).getDouble("LGTD");
            trainData[i][1] = jsonArray.getJSONObject(i).getDouble("LTTD");
            trainData[i][2] = jsonArray.getJSONObject(i).getDouble("DYP");
        }

        double[] dataInterval = new double[]{0.0001, 2, 4, 6, 9, 10, 20, 30};

        String boundryFile = "F:\\我的文档\\等值面图研究\\jz-data\\jz-shape\\锦州_R.shp";

        int[] size = new int[]{100, 100};

        boolean isclip = false;

        try {
            String strJson = equiSurface.calEquiSurface(trainData, dataInterval, size, boundryFile, isclip);
            String strFile = "C:\\Users\\admin\\Desktop\\临时\\jinzhou.json";
            cm.append2File(strFile, strJson);
            System.out.println(strFile + "差值成功, 共耗时" + (System.currentTimeMillis() - start) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
    public static SimpleFeatureCollection readGeoJsonByString(String geojsonpogylon) throws IOException {
        FeatureJSON fjson = new FeatureJSON();
        SimpleFeatureCollection featureCollection = (SimpleFeatureCollection) fjson.readFeatureCollection(geojsonpogylon);
        return featureCollection;
    }

    public void append2File(String strFile, String strJson){
        File f=new File(strFile);//新建一个文件对象,如果不存在则创建一个该文件
        FileWriter fw;
        try {
            fw = new FileWriter(f);
            fw.write(strJson);//将字符串写入到指定的路径下的文件中
            fw.close();
        } catch (IOException e) { e.printStackTrace(); }
    }
<script type="text/javascript">
    var bounds = [120.711715945734340, 40.783993710141660,
        122.595342945920280, 42.126609285989790];
    var projection = new ol.proj.Projection({
        code: 'EPSG:4326',
        units: 'degrees'
    });
    $.get("../data/jinzhou/jinzhou-cj2.json",null,function(result){
        var features = (new ol.format.GeoJSON()).readFeatures(result);
        var vectorSource = new ol.source.Vector();
        vectorSource.addFeatures(features);
        var vector = new ol.layer.Vector({
            source: vectorSource,
            style:function(feature, resolution) {
                var lvalue = feature.get("lvalue"), color = "0,0,0,0";
                if(lvalue<4){
                    color = "166,242,143,255";
                }
                else if(lvalue>=4&&lvalue<6){
                    color = "87,238,40,255";
                }
                else if(lvalue>=6&&lvalue<10){
                    color = "22,210,22,255";
                }
                else if(lvalue>=10&&lvalue<20){
                    color = "97,184,255,255";
                }
                else{
                    color = "28,125,204,255";
                }
                return new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        color: '#ffffff',
//						        lineDash: [10],
                        width: 1
                    }),
                    fill: new ol.style.Fill({
                        color: "rgba("+color+")",
                        opacity:0.6
                    })
                });
            }
        });
        var map = new ol.Map({
            controls: ol.control.defaults({
                attribution: false
            }),
            target: 'map',
            layers: [vector],
            view: new ol.View({
                projection: projection
            })
        });
        map.getView().fit(bounds, map.getSize());
    });
</script>

references:

[1] Summary of implementation methods of contour map and color spot map (https://www.jianshu.com/p/04362ff87641)

[2]  The generation of isosurface in geotools and the display in OL3_LZUGIS-CSDN Blog_geoserver Contour

[3]  Java realizes the inverse distance weight interpolation algorithm to generate geojson vector data_Xingcheng's blog-CSDN blog_geotools

[4]  2021-10-28 Some Problems in Realizing Isosurfaces - Brief Book

Guess you like

Origin blog.csdn.net/u013517229/article/details/122925421