1 引 言

展示雨量数据的分布情况,等值面图是非常合适的方法。以锦州市某日的降雨量为例,雨量站点大概有100个,分布于整个锦州市。首先尝试的方法是利用kriging.js生成等值面,但是由于锦州市边界区域太大,浏览器客户端生成压力非常大,速度也很慢。所以尝试着在服务端生成,然后客户端调用显示。

2 等值面生成的两种方式

2 .1 服务端生成

通用的方式是基于Java + Geotools + wContour在服务端处理数据,适用业务如下:

  • 大范围高密度的空间数据插值
  • 服务器端可以设置定时任务,流水线处理原始数据,生成目标数据(图片或者GeoJSON数据)

2 . 2 客户端生成

通用的方式是前端插值处理数据,生成网格数据,再进行等值线或等值面的生成,适用业务如下:

  • 数据更新频率高,实时性强
  • 按需服务,减轻服务器压力

可以使用如下方式:

  • 使用turf.js内置函数通过离散点插值、等值面绘制、裁剪,生成矢量数据然后绘制数据
  • 使用kriging.js内置的函数,离散点生成网格数据然后网格渲染
  • 结合turf.js和kriging.js两者的优点,提升交互性能、插值效果和显示效果

3 基于Java + Geotools + wContour在服务端处理数据

生成结果图1(边界不裁剪):

生成结果图2(边界裁剪):

完整代码:

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>

参考文献:

[1] 等值线图以及色斑图实现方式综述(https://www.jianshu.com/p/04362ff87641)

[2] geotools中等值面的生成与OL3中的展示_LZUGIS-CSDN博客_geoserver 等值线

[3] java实现反距离权重插值算法生成geojson矢量数据_兴诚的博客-CSDN博客_geotools

[4] 2021-10-28 实现等值面的一些问题 - 简书

1引 言展示雨量数据的分布情况,等值面图是非常合适的方法。以锦州市某日的降雨量为例,雨量站点大概有100个,分布于整个锦州市。首先尝试的方法是利用kriging.js生成等值面,但是由于锦州市边界区域太大,浏览器客户端生成压力非常大,速度也很慢。所以尝试着在服务端生成,然后客户端调用显示。2等值面生成的两种方式2.1 服务端生成通用的方式是基于Java + Geotools + wContour在服务端处理数据,适用业务如下:大范围高密度的空间数据插值 服务器端可以设置定时任务...
//nc数据生成等值线数据 public static List<Polygon> nc2EquiSurface(Map ncData, double[] dataInterval) throws IOException { // String geojsonpogylon = ""; List<PolyLine> cPolylineList = new ArrayList<PolyLine>(); List<.
package cn.cepec.talroad.air.common; import cn.cepec.talroad.air.common.geoutil.FeaureUtil; import cn.cepec.talroad.air.common.geoutil.GeoJSONUtil; import com.vividsolutions.jts.geom.Geometry; import net.sf.json.JSONObject; import org.geotools.
海洋数值预报里面,数据格式一般都为nc文件格式,存储的为科学数据,这些数据都是网格化的数据。由于有些网格点是陆地,如果用图片方式去展示流场的化,合理的方式是将这些陆地剔除。由于一些客户端软件可以做到,这里不考虑。这里提供两个软件可供参考。 一个是jzy3d , 里面使用ContourPictureGenerator可生成想要的等值面,返回的为BufferedImage,当然流场方向还需自
### 回答1: Java 是一种通用的编程语言,它可以应用于多个领域,包括 GIS 领域。在 GIS 领域中,Java 可以通过 wcontourgeotools 库来实现等值面分析、裁切、出图、入库、抽稀等操作。 wcontour 库是一个基于 Java 语言的等值线和等值面库,它可以用于生成等值线和等值面,以及计算等值线的交点和等值面的交线等操作。它的主要功能是将离散的地形或水文数据转换为连续的等值线或等值面,为地理信息系统和其他应用程序提供地形分析和展示的基础。 Geotools 库是一个开源的 Java 库,它提供了各种 GIS 功能,包括地图绘制、矢量数据处理、栅格数据处理、空间分析和空间数据库等。它可以用于读取和写入各种 GIS 格式文件、进行空间分析、进行空间数据库操作等,是广泛应用于 GIS 领域的重要库之一。 通过 wcontourgeotools 库,可以实现等值面分析、裁切、出图、入库、抽稀等操作。例如,可以使用 wcontour 库生成等值面,然后使用 geotools 库将等值面输出为图像或其他格式文件,也可以使用 geotools 库将等值面写入空间数据库中进行存储和管理。另外,还可以使用 wcontour 库对等值面进行抽稀操作,以减少数据量和提高效率。 总之,Java 通过 wcontourgeotools 库提供了丰富的 GIS 功能,可以实现广泛的地理信息处理任务。 ### 回答2: Java是一种通用编程语言,广泛应用于地理信息系统的各个方面,包括等值面分析、裁切、出图、入库和抽稀等。其中,wcontourgeotoolsJava中常用的工具库,用于地理信息处理。 等值面分析是地理信息处理中的一种重要技术,可用于分析地形等复杂地理场景。wcontour是一个流行的Java程序包,用于进行等值线和等值面计算。它可以处理多种数据格式,包括地图投影和网格数据,以及支持多种等值线计算算法。除此之外,wcontour还可以针对等值线进行插值,使得等值面计算更加精确。 GeotoolsJava中一个开放源代码的地理信息系统工具库,可以用于实现地图投影、几何对象操作、卫星图像处理等功能。它提供了许多地理信息操作的API,可以使用Java程序读取、处理和输出GIS数据。在等值面分析方面,geotools可以读取和处理矢量文件,包括SHP、KML和GML等格式,可以进行裁切、合并、转换、符号化等操作。此外,geotools还提供了生成等值面图形的一些基本类库和API,可以用于生成等值面的图形文件。 入库是地理信息系统中的一种存储和管理方式,可以保存不同格式和规模的地图数据。Java中常用的入库方式包括PostGIS和Oracle Spatial等。这些入库方式可以将地理信息数据存储在关系数据库中,可以通过SQL语句进行查询和管理。此外,Java中还可以使用自定义的地理信息存储方案,如Hadoop和NoSQL数据库等。 抽稀是一种地理信息处理中的数据压缩技术,可以在不影响地图质量的情况下将数据压缩成较小的文件。Java中常用的抽稀库包括Douglas-Peucker算法和Ramer-Douglas-Peucker算法等。这些算法可以计算出一条近似于原始地图数据的折线,从而减少数据量,提高地图渲染和绘制速度。 总之,Java通过wcontourgeotools等工具库,可以实现等值面分析、裁切、出图、入库和抽稀等地理信息处理和管理功能。这些功能可用于开发各种地理信息应用程序,包括地图制作、地形分析、城市规划等。