当前位置:   article > 正文

解决geotools处理矢量数据格式转换NPE异常_geotools jdk17

geotools jdk17
1.背景简介

项目中需要实现矢量数据的shp文件转成geojson的需求,计划通过geotools工具来实现。
geotools:GeoTools是一个开源的Java库,用于处理地理空间数据和执行空间分析。它提供了丰富的GIS(地理信息系统)功能和工具,可以处理包括矢量数据、栅格数据、影像数据等不同类型的地理数据,支持空间对象操作、地图投影和坐标转换、空间查询和空间分析等能力。

2.引入依赖
<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-shapefile</artifactId>
    <version>29.0</version>
</dependency>
<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojson</artifactId>
    <version>29.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
3.代码示例

下面这段代码实现将shp文件转化成geojson格式的文件:

@SneakyThrows
@Test
public void shpToGeo() {

    // shp文件
    File file = new File("D:\\test\\shp\\city.shp");
    Map<String, Object> map = new HashMap<>();
    map.put("url", URLs.fileToUrl(file));
    DataStore dataStore = DataStoreFinder.getDataStore(map);

    String typeName = dataStore.getTypeNames()[0];
    SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
    // geojson文件
    File geojsonFile = new File("D:\\test\\shp\\city.geojson");

    SimpleFeatureCollection featureCollection = featureSource.getFeatures();
    FileOutputStream geoJsonOutputStream = new FileOutputStream(geojsonFile);
    // 写入数据
    new FeatureJSON().writeFeatureCollection(featureCollection, geoJsonOutputStream);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
4.报错详情

运行上面的代码进行格式转换,可能会报NPE异常:
image.png

5.原因分析

根据异常信息,直接定位到源头(上图红框内),查看一下SystemUtils.isJavaVersionAtLeast的处理逻辑。
下面是SystemUtils.isJavaVersionAtLeast的处理逻辑与代码调用链路:

/**
* 判断java版本是否符合要求
*/
public static boolean isJavaVersionAtLeast(JavaVersion requiredVersion) {
    return JAVA_SPECIFICATION_VERSION_AS_ENUM.atLeast(requiredVersion);
}

public boolean atLeast(JavaVersion requiredVersion) {
    return this.value >= requiredVersion.value;
}

// java 指定版本
JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION);
public static final String J 	AVA_SPECIFICATION_VERSION = getSystemProperty("java.specification.version");
// 读取系统属性
private static String getSystemProperty(String property) {
    try {
        return System.getProperty(property);
    } catch (SecurityException var2) {
        System.err.println("Caught a SecurityException reading the system property '" + property + "'; the SystemUtils property value will default to null.");
        return null;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

首先是判断JAVA_SPECIFICATION_VERSION_AS_ENUM与requiredVersion的值,这里的JAVA_SPECIFICATION_VERSION_AS_ENUM指的是java的指定版本,requiredVersion指的是运行该段代码要求的Java版本。下面看一下这两个值分别来自哪里。
JAVA_SPECIFICATION_VERSION_AS_ENUM是JavaVersion(org.apache.commons.lang3这个包)的一个枚举值:

static JavaVersion get(String nom) {
    if ("0.9".equals(nom)) {
        return JAVA_0_9;
    } else if ("1.1".equals(nom)) {
        return JAVA_1_1;
    } else if ("1.2".equals(nom)) {
        return JAVA_1_2;
    } else if ("1.3".equals(nom)) {
        return JAVA_1_3;
    } else if ("1.4".equals(nom)) {
        return JAVA_1_4;
    } else if ("1.5".equals(nom)) {
        return JAVA_1_5;
    } else if ("1.6".equals(nom)) {
        return JAVA_1_6;
    } else if ("1.7".equals(nom)) {
        return JAVA_1_7;
    } else if ("1.8".equals(nom)) {
        return JAVA_1_8;
    } else if ("9".equals(nom)) {
        return JAVA_9;
    } else if ("10".equals(nom)) {
        return JAVA_10;
    } else if (nom == null) {
        return null;
    } else {
        float v = toFloatVersion(nom);
        if ((double)v - 1.0 < 1.0) {
            int firstComma = Math.max(nom.indexOf(46), nom.indexOf(44));
            int end = Math.max(nom.length(), nom.indexOf(44, firstComma));
            if (Float.parseFloat(nom.substring(firstComma + 1, end)) > 0.9F) {
                return JAVA_RECENT;
            }
        }

        return null;
    }
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

从上面的代码可以看出对Java版本的判断只到Java10,Java10以后的结果直接返回null(NPE就来自这里)了,猜测原因应该是工程中引入的lang3依赖的版本比较老(3.7),该版本发布时,Java10以后的jdk版本尚未发布。

下面看一下requiredVersion的值,根据报错信息org.geotools.util.NIOUtilities.clean这个方法调用了 org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast这个方法触发了NPE,下面看一下clean()这个方法的实现逻辑:

public static boolean clean(ByteBuffer buffer) {
    if (buffer != null && buffer.isDirect()) {
        PrivilegedAction<Boolean> action = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9) ? () -> {
            return (new CleanupAfterJdk8(buffer)).clean();
        } : () -> {
            return (new CleanupPriorJdk9(buffer)).clean();
        };
        return (Boolean)AccessController.doPrivileged(action);
    } else {
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

clean()这个方法中调用了SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9),也就是说传递给SystemUtils.isJavaVersionAtLeast(JavaVersion requiredVersion)这个方法的requiredVersion参数的值是9。

根据上面的分析,可以在下面这个地方打个断点验证一下:
image.png
根据上面的断点信息可以看出requiredVersion9,JAVA_SPECIFICATION_VERSION_AS_ENUMnull。

再在下面这个地方打个断点看一下:
image.png
可以看出从系统属性中读取出来的JAVA_SPECIFICATION_VERSION==11,也就是工程指定的jdk版本,然后从下面这段代码读取JAVA_SPECIFICATION_VERSION_AS_ENUM枚举值的时候,返回了null:

static JavaVersion get(String nom) {
    if ("0.9".equals(nom)) {
        return JAVA_0_9;
    } else if ("1.1".equals(nom)) {
        return JAVA_1_1;
    } else if ("1.2".equals(nom)) {
        return JAVA_1_2;
    } else if ("1.3".equals(nom)) {
        return JAVA_1_3;
    } else if ("1.4".equals(nom)) {
        return JAVA_1_4;
    } else if ("1.5".equals(nom)) {
        return JAVA_1_5;
    } else if ("1.6".equals(nom)) {
        return JAVA_1_6;
    } else if ("1.7".equals(nom)) {
        return JAVA_1_7;
    } else if ("1.8".equals(nom)) {
        return JAVA_1_8;
    } else if ("9".equals(nom)) {
        return JAVA_9;
    } else if ("10".equals(nom)) {
        return JAVA_10;
    } else if (nom == null) {
        return null;
    } else {
        float v = toFloatVersion(nom);
        if ((double)v - 1.0 < 1.0) {
            int firstComma = Math.max(nom.indexOf(46), nom.indexOf(44));
            int end = Math.max(nom.length(), nom.indexOf(44, firstComma));
            if (Float.parseFloat(nom.substring(firstComma + 1, end)) > 0.9F) {
                return JAVA_RECENT;
            }
        }

        return null;
    }
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
6.解决方式

上面根据代码调用链已经分析出了导致NPE的原因,原因在于org.apache.commons.lang3.JavaVersion的get()方法返回了null,上面也猜测了原因,应该是工程中引入的lang3依赖的版本比较老(3.7版本),该版本发布时,Java10以后的jdk版本尚未发布。那么沿着这个思路,升级commons-lang3的版本,将commons-lang3升级至3.12.0

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

升级版本后,查看org.apache.commons.lang3.JavaVersion的get()方法:

static JavaVersion get(String versionStr) {
    if (versionStr == null) {
        return null;
    } else {
        switch (versionStr) {
            case "0.9":
                return JAVA_0_9;
            case "1.1":
                return JAVA_1_1;
            case "1.2":
                return JAVA_1_2;
            case "1.3":
                return JAVA_1_3;
            case "1.4":
                return JAVA_1_4;
            case "1.5":
                return JAVA_1_5;
            case "1.6":
                return JAVA_1_6;
            case "1.7":
                return JAVA_1_7;
            case "1.8":
                return JAVA_1_8;
            case "9":
                return JAVA_9;
            case "10":
                return JAVA_10;
             case "11":
                 return JAVA_11;
             case "12":
                 return JAVA_12;
             case "13":
                 return JAVA_13;
             case "14":
                 return JAVA_14;
             case "15":
                 return JAVA_15;
             case "16":
                 return JAVA_16;
             case "17":
                 return JAVA_17;
             default:
                 float v = toFloatVersion(versionStr);
                 if ((double)v - 1.0 < 1.0) {
                     int firstComma = Math.max(versionStr.indexOf(46), versionStr.indexOf(44));
                     int end = Math.max(versionStr.length(), versionStr.indexOf(44, firstComma));
                     if (Float.parseFloat(versionStr.substring(firstComma + 1, end)) > 0.9F) {
                         return JAVA_RECENT;
                     }
                 } else if (v > 10.0F) {
                     return JAVA_RECENT;
                 }

                 return null;
         }
     }
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

从上面代码可以看出升级版本后的代码能够正常判断jdk17及以前的java版本。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/476147
推荐阅读
  

闽ICP备14008679号