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

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

首先是判断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;
    }
}

从上面的代码可以看出对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;
    }
}

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

升级版本后,查看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;
         }
     }
}

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

猜你喜欢

转载自blog.csdn.net/Princeliu999/article/details/132780926