1.gdal下载

gdal下载官网地址: https://www.gisinternals.com/archive.php
下载后文件:

在这里插入图片描述

2.安装gdal:

安装gdal-3.9.0-1930-x64-core.msi,再安装gdal-3.9.0-1930-x64-filegdb.msi,一直下一步,filegdb用户数据导出
双击安装:t开头默认,c开头自定义
卸载:右键有卸载功能
安装后文件:

在这里插入图片描述
解压release-1930-x64-gdal-3-9-0-mapserver(地图服务器组件)-8-0-1.zip文件,然后把需要的文件都复制到解压的这个文件中:
解压后的文件:
在这里插入图片描述

把C:\Program Files\GDAL的文件gdalplugins文件夹和 FileGDBAPI.dll 都复制到 解压目录下的bin下
在这里插入图片描述

在这里插入图片描述
把解压目录下 gdal\java 下的 gdal.jar 和 gdalalljin.dll 复制到 bin 下
在这里插入图片描述
把解压目录下的 bin\gdal\apps下所有文件都复制到 bin下
在这里插入图片描述

3.环境变量配置:
GDAL_HOME : E:\02-SoftWare\use\gdal\gdal-3-9
GDAL_DRIVER_PATH : %GDAL_HOME%\bin\gdalplugins
GDAL_DATA: %GDAL_HOME%\bin\gdal-data
PROJ_LIB: E:\02-SoftWare\use\gdal\gdal-3-9\bin\proj9\share
path: %GDAL_HOME%\bin
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4.测试:
打开cmd, 进到解压目录下的 bin目录, 输入 ogrinfo --formats , 如果有 FileGDB 就说明 ok了 (这里我自己改了文件夹名字,原始的是 bin)
gdalinfo --version
在这里插入图片描述
简单介绍gdal:
gdb是arcgis重要的数据格式之一(还有shp文件,java会处理这两种数据格式),它把矢量数据按以下层级结构存为本地文件
在这里插入图片描述
gdal提供了一些函数允许我们读取gdb,我们可以通过ini调用gdal来再java中读取gdal

5.Java开发:
在springboot项目中添加gd## 标题al.jar包
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
相关的jar:

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.8</version>
    </dependency>


    <!--Geometry使用JTS包-->
    <dependency>
        <groupId>org.locationtech.jts</groupId>
        <artifactId>jts-core</artifactId>
        <version>1.18.1</version>
    </dependency>

    <dependency>
        <groupId>org.wololo</groupId>
        <artifactId>jts2geojson</artifactId>
        <version>0.17.0</version>
    </dependency>

java测试gdb代码:

    
    public static void main(String[] args) throws Exception {
        gdal.AllRegister();
        int count = ogr.GetDriverCount();
        for (int i = 0; i < count; i++) {
            String driverName = ogr.GetDriver(i).getName();
            System.out.print(driverName + "\t");
        }
        gdal.GDALDestroyDriverManager();
    }

java导入导出gdal,shp文件:

package com.kjh.gdal.utils;

import cn.hutool.core.io.FileUtil;
import com.kjh.gdal.pojo.poi.GdalExportField;
import com.kjh.gdal.pojo.poi.PoiGdalData;
import com.kjh.gdal.pojo.poi.PoiShpData;
import org.gdal.gdal.gdal;
import org.gdal.ogr.*;
import org.gdal.osr.SpatialReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.BiFunction;

/**
 * @program: jstool
 * @description: <p>
 * @author: KJH
 * @create: 2022-11-25 13:39
 */
public class GDALUtil {

    /**
     * GDAL 数据导入/导出工具类
     * 支持 FileGDB、OpenFileGDB、ESRI Shapefile 等 OGR 支持的格式
     */

    private static final Logger logger = LoggerFactory.getLogger(GDALUtil.class);

    // 驱动名称常量
    private static final String DRIVER_FILE_GDB = "FileGDB";
    private static final String DRIVER_OPEN_FILE_GDB = "OpenFileGDB";
    private static final String DRIVER_ESRI_SHAPEFILE = "ESRI Shapefile";

    // 注册标志
    private static volatile boolean registered = false;

    static {
        register();
    }

    private GDALUtil() {
        // 工具类不允许实例化
    }

    /**
     * 注册 GDAL 驱动并设置环境配置
     */
    public static synchronized void register() {
        if (registered) {
            return;
        }
        try {
            logger.info("GDAL 版本信息: {}", gdal.VersionInfo("--version"));
            gdal.AllRegister();
            gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
            gdal.SetConfigOption("SHAPE_ENCODING", "CP936");
            registered = true;
            logger.info("GDAL 注册成功");
        } catch (Exception e) {
            logger.error("GDAL 注册失败", e);
            throw new RuntimeException("GDAL 初始化失败", e);
        }
    }

    // ======================== 导入功能 ========================

    /**
     * 通用导入方法,自动检测数据源格式
     * 支持 GDB、Shapefile、GeoJSON 等所有 OGR 支持的格式
     *
     * @param filePath 文件路径(.gdb 文件夹、.shp 文件、.geojson 文件等)
     * @return 包含所有要素属性及几何(WKT 格式)的列表
     */
    public static List<Map<String, Object>> importFromFile(String filePath) {
        register();
        List<Map<String, Object>> result = new ArrayList<>();
        DataSource dataSource = null;
        try {
            // 使用 ogr.Open 自动检测格式
            dataSource = ogr.Open(filePath, 0);
            if (dataSource == null) {
                logger.warn("无法打开数据源: {}", filePath);
                return result;
            }

            int layerCount = dataSource.GetLayerCount();
            logger.info("数据源包含 {} 个图层", layerCount);

            for (int i = 0; i < layerCount; i++) {
                Layer layer = dataSource.GetLayer(i);
                if (layer == null) continue;

                String layerName = layer.GetName();
                logger.info("读取图层: {}", layerName);

                Feature feature;
                while ((feature = layer.GetNextFeature()) != null) {
                    try {
                        Map<String, Object> row = new LinkedHashMap<>();

                        // 获取几何
                        Geometry geometry = feature.GetGeometryRef();
                        if (geometry != null) {
                            String geoJson = geometry.ExportToJson();
                            String wkt = WktGeomjsonUtils.geojson2Wkt(geoJson);
                            row.put("POINTS", wkt);
                        }

                        // 获取所有属性字段
                        for (int j = 0; j < feature.GetFieldCount(); j++) {
                            String fieldName = feature.GetFieldDefnRef(j).GetName();
                            Object value = readFieldValue(feature, j);
                            row.put(fieldName, value);
                        }
                        result.add(row);
                    } finally {
                        feature.delete();
                    }
                }
            }

            logger.info("导入完成,共 {} 条记录", result.size());
        } catch (Exception e) {
            logger.error("导入数据失败", e);
            throw new RuntimeException("导入数据失败: " + e.getMessage(), e);
        } finally {
            if (dataSource != null) {
                dataSource.delete();
            }
        }
        return result;
    }

    /**
     * 从 GDB 文件导入数据(保留原方法,保持向后兼容)
     *
     * @param gdbPath GDB 文件夹或 .gdb 路径
     * @return 包含所有要素属性及几何(WKT 格式)的列表
     */
    public static List<Map<String, Object>> importFromGdb(String gdbPath) {
        // 直接调用通用方法
        return importFromFile(gdbPath);
    }

    /**
     * 从 Feature 读取指定索引字段的值,根据字段类型自动转换
     */
    private static Object readFieldValue(Feature feature, int index) {
        int type = feature.GetFieldType(index);
        return FieldTypeHandler.readValue(feature, index, type);
    }

    /**
     * 字段类型处理器枚举
     */
    private enum FieldTypeHandler {
        INTEGER(ogr.OFTInteger, (f, i) -> f.GetFieldAsInteger(i)),
        INTEGER_LIST(ogr.OFTIntegerList, (f, i) -> f.GetFieldAsIntegerList(i)),
        REAL(ogr.OFTReal, (f, i) -> f.GetFieldAsDouble(i)),
        REAL_LIST(ogr.OFTRealList, (f, i) -> f.GetFieldAsDoubleList(i)),
        STRING(ogr.OFTString, (f, i) -> f.GetFieldAsString(i)),
        STRING_LIST(ogr.OFTStringList, (f, i) -> f.GetFieldAsStringList(i)),
        BINARY(ogr.OFTBinary, (f, i) -> f.GetFieldAsBinary(i)),
        DATE(ogr.OFTDate, (f, i) -> {
            int[] year = new int[1], month = new int[1], day = new int[1];
            int[] hour = new int[1], minute = new int[1], tz = new int[1];
            float[] second = new float[1];
            f.GetFieldAsDateTime(i, year, month, day, hour, minute, second, tz);
            return java.sql.Date.valueOf(LocalDate.of(year[0], month[0], day[0]));
        }),
        TIME(ogr.OFTTime, (f, i) -> {
            int[] year = new int[1], month = new int[1], day = new int[1];
            int[] hour = new int[1], minute = new int[1], tz = new int[1];
            float[] second = new float[1];
            f.GetFieldAsDateTime(i, year, month, day, hour, minute, second, tz);
            int sec = (int) second[0];
            int nano = (int) ((second[0] - sec) * 1_000_000_000);
            return Time.valueOf(LocalTime.of(hour[0], minute[0], sec, nano));
        }),
        DATETIME(ogr.OFTDateTime, (f, i) -> {
            int[] year = new int[1], month = new int[1], day = new int[1];
            int[] hour = new int[1], minute = new int[1], tz = new int[1];
            float[] second = new float[1];
            f.GetFieldAsDateTime(i, year, month, day, hour, minute, second, tz);
            int sec = (int) second[0];
            int nano = (int) ((second[0] - sec) * 1_000_000_000);
            LocalDateTime ldt = LocalDateTime.of(
                    LocalDate.of(year[0], month[0], day[0]),
                    LocalTime.of(hour[0], minute[0], sec, nano)
            );
            return Timestamp.valueOf(ldt);
        }),
        INTEGER64(ogr.OFTInteger64, (f, i) -> f.GetFieldAsInteger64(i)),
        INTEGER64_LIST(ogr.OFTInteger64List, (f, i) -> f.GetFieldAsIntegerList(i));

        final int ogrType;
        final BiFunction<Feature, Integer, Object> reader;

        FieldTypeHandler(int ogrType, BiFunction<Feature, Integer, Object> reader) {
            this.ogrType = ogrType;
            this.reader = reader;
        }

        static Object readValue(Feature feature, int index, int type) {
            for (FieldTypeHandler handler : values()) {
                if (handler.ogrType == type) {
                    try {
                        return handler.reader.apply(feature, index);
                    } catch (Exception e) {
                        logger.warn("读取字段索引 {} 失败,类型码: {}", index, type, e);
                        return null;
                    }
                }
            }
            return feature.GetFieldAsString(index);
        }
    }

    // ======================== 导出功能 ========================

    /**
     * 导出 POI 数据到 GDB(要素类)
     */
    public static void exportToGdb(List<?> dataList, String savePath) {
        export(dataList, savePath, DRIVER_FILE_GDB, ogr.wkbPoint);
    }

    /**
     * 导出 POI 数据到 Shapefile
     */
    public static void exportToShapefile(List<?> dataList, String savePath) {
        logger.info("实际导出类型: {}", dataList.get(0).getClass().getName());
        export(dataList, savePath, DRIVER_ESRI_SHAPEFILE, ogr.wkbPoint);
    }

    /**
     * 通用导出方法
     */
    private static void export(List<?> dataList, String savePath, String driverName, int geometryType) {
        register();
        if (dataList == null || dataList.isEmpty()) {
            logger.warn("导出数据为空,跳过");
            return;
        }
        DataSource dataSource = null;
        try {
            Driver driver = ogr.GetDriverByName(driverName);
            if (driver == null) {
                throw new IllegalStateException("驱动 " + driverName + " 未找到");
            }
            dataSource = driver.CreateDataSource(savePath);
            if (dataSource == null) {
                throw new RuntimeException("创建 DataSource 失败: " + savePath);
            }
            SpatialReference sr = new SpatialReference();
            sr.ImportFromEPSG(4490);
            Layer layer = dataSource.CreateLayer("POI", sr, geometryType, null);
            if (layer == null) {
                throw new RuntimeException("创建图层失败");
            }

            Class<?> dataClass = dataList.get(0).getClass();
            List<Field> exportFields = getExportFields(dataClass);

            createFields(layer, exportFields);
            writeFeatures(layer, dataList, exportFields);

            dataSource.SyncToDisk();
            dataSource.FlushCache();
            logger.info("数据导出成功: {}", savePath);
        } catch (Exception e) {
            logger.error("导出到 {} 失败: {}", driverName, savePath, e);
            throw new RuntimeException("导出失败", e);
        } finally {
            if (dataSource != null) {
                dataSource.delete();
            }
        }
    }

    /**
     * 获取带有 @GdalExportField 注解的字段列表
     */
    private static List<Field> getExportFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(GdalExportField.class)) {
                field.setAccessible(true);
                fields.add(field);
            }
        }
        return fields;
    }

    /**
     * 在图层上创建字段
     */
    private static void createFields(Layer layer, List<Field> fields) {
        for (Field field : fields) {
            GdalExportField annotation = field.getAnnotation(GdalExportField.class);
            if (annotation.isGeom()) {
                continue;
            }
            String fieldName = annotation.name().isEmpty() ? field.getName() : annotation.name();
            int width = annotation.length();
            // 字符串字段最大宽度 254
            if (annotation.type() == ogr.OFTString) {
                if (width == 0 || width > 254) {
                    width = 254;
                }
            }
            FieldDefn fieldDefn = new FieldDefn();
            fieldDefn.SetName(fieldName);
            fieldDefn.SetType(annotation.type());
            fieldDefn.SetWidth(width);
            if (!annotation.alternativeName().isEmpty()) {
                fieldDefn.SetAlternativeName(annotation.alternativeName());
            }
            if (!annotation.nullAble()) {
                fieldDefn.SetNullable(0);
            }
            if (!annotation.defaultValue().isEmpty()) {
                fieldDefn.SetDefault(annotation.defaultValue());
            }
            fieldDefn.SetSubType(annotation.subType());
            layer.CreateField(fieldDefn);
        }
    }

    /**
     * 写入要素数据
     */
    private static void writeFeatures(Layer layer, List<?> dataList, List<Field> fields) throws IllegalAccessException {
        FeatureDefn featureDefn = layer.GetLayerDefn();
        for (Object obj : dataList) {
            Feature feature = new Feature(featureDefn);
            boolean hasGeom = false;
            boolean skipFeature = false;

            for (Field field : fields) {
                GdalExportField annotation = field.getAnnotation(GdalExportField.class);
                String fieldName = annotation.name().isEmpty() ? field.getName() : annotation.name();
                Object value = field.get(obj);
                if (value == null) continue;

                if (annotation.isGeom()) {
                    Geometry geometry = toGeometry(value);
                    if (geometry == null) {
                        logger.warn("要素几何无效,跳过: {}", obj);
                        skipFeature = true;
                        break;
                    }
                    feature.SetGeometry(geometry);
                    hasGeom = true;
                } else {
                    setFeatureField(feature, fieldName, annotation.type(), value);
                }
            }

            if (skipFeature || (!hasGeom && layer.GetGeomType() != ogr.wkbNone)) {
                feature.delete();
                continue;
            }

            try {
                layer.CreateFeature(feature);
            } catch (Exception e) {
                logger.error("创建要素失败,跳过该要素", e);
            } finally {
                feature.delete();
            }
        }
    }

    /**
     * 将对象转换为 GDAL Geometry
     */
    private static Geometry toGeometry(Object value) {
        if (value instanceof Geometry) {
            return (Geometry) value;
        }
        if (value instanceof String) {
            try {
                String wkt = ((String) value).trim();
                return Geometry.CreateFromWkt(wkt);
            } catch (Exception e) {
                logger.warn("WKT 转换失败: {}", value, e);
                return null;
            }
        }
        return null;
    }

    /**
     * 根据字段类型设置 Feature 字段值
     */
    private static void setFeatureField(Feature feature, String fieldName, int fieldType, Object value) {
        try {
            switch (fieldType) {
                case ogr.OFTInteger:
                    feature.SetField(fieldName, value.toString());
                    break;
                case ogr.OFTReal:
                    feature.SetField(fieldName, Double.valueOf(value.toString()));
                    break;
                case ogr.OFTString:
                    String strVal = value.toString();
                    if (strVal.length() > 254) {
                        strVal = strVal.substring(0, 254);
                        logger.warn("字段 {} 超长,已截断至254字符", fieldName);
                    }
                    feature.SetField(fieldName, strVal);
                    break;
                case ogr.OFTDate:
                case ogr.OFTDateTime:
                    setDateTimeField(feature, fieldName, value);
                    break;
                case ogr.OFTBinary:
                    feature.SetField(fieldName, value.toString());
                    break;
                default:
                    feature.SetField(fieldName, value.toString());
            }
        } catch (Exception e) {
            logger.warn("设置字段 {} 失败,值: {}", fieldName, value, e);
        }
    }

    /**
     * 设置日期时间字段
     */
    private static void setDateTimeField(Feature feature, String fieldName, Object value) {
        if (value instanceof Date) {
            Date date = (Date) value;
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            feature.SetField(fieldName,
                    cal.get(Calendar.YEAR),
                    cal.get(Calendar.MONTH) + 1,
                    cal.get(Calendar.DATE),
                    cal.get(Calendar.HOUR_OF_DAY),
                    cal.get(Calendar.MINUTE),
                    cal.get(Calendar.SECOND),
                    0);
        } else if (value instanceof LocalDateTime) {
            LocalDateTime ldt = (LocalDateTime) value;
            feature.SetField(fieldName,
                    ldt.getYear(),
                    ldt.getMonthValue(),
                    ldt.getDayOfMonth(),
                    ldt.getHour(),
                    ldt.getMinute(),
                    ldt.getSecond(),
                    0);
        } else if (value instanceof LocalDate) {
            LocalDate ld = (LocalDate) value;
            feature.SetField(fieldName,
                    ld.getYear(),
                    ld.getMonthValue(),
                    ld.getDayOfMonth(),
                    0, 0, 0, 0);
        } else {
            feature.SetField(fieldName, value.toString());
        }
    }

    // ======================== 辅助方法 ========================

    public static Geometry createGeometry(String wkt) {
        if (ObjectUtils.isEmpty(wkt)) {
            return null;
        }
        try {
            return Geometry.CreateFromWkt(wkt);
        } catch (Exception e) {
            logger.warn("创建几何失败: {}", wkt, e);
            return null;
        }
    }

    // ======================== 测试入口 ========================
    public static void main(String[] args) {
        // 开启调试模式
//        gdal.SetConfigOption("CPL_DEBUG", "ON");

        // 导出 POI 数据
        List<PoiGdalData> poiGdalList = buildPoiGdalDataList();
        List<PoiShpData> poiShpList = buildPoiShpDataList();

        String exportGDBPath = "D:\\website\\POI\\export_gdb";
        String exportShapefilePath = "D:\\website\\POI\\export_gdb\\POI_shp";
        FileUtil.del(exportGDBPath);
        FileUtil.del(exportShapefilePath);
        FileUtil.mkdir(exportGDBPath);
        FileUtil.mkdir(exportShapefilePath);
        exportGDBPath=exportGDBPath+"\\POITest.gdb";
        exportShapefilePath=exportShapefilePath+"\\POITest.shp";

        // 导出 Shapefile
        GDALUtil.exportToShapefile(poiShpList, exportShapefilePath);
        // 导出 GDB
        GDALUtil.exportToGdb(poiGdalList, exportGDBPath);

        // 导入 GDB
        List<Map<String, Object>> data = GDALUtil.importFromGdb(exportGDBPath);
        System.out.println("GDB 导入数据条数: " + data.size());

        // 导入 Shapefile(使用通用方法)
        List<Map<String, Object>> data2 = GDALUtil.importFromFile(exportShapefilePath);
        System.out.println("Shapefile 导入数据条数: " + data2.size());
    }


    private static List<PoiShpData> buildPoiShpDataList() {
        List<PoiShpData> list = new ArrayList<>();

        // 第一条数据
        PoiShpData poi1 = new PoiShpData();
        poi1.setPoiid("P001");
        poi1.setName("绍兴泉岗机械科技有限公司");
        poi1.setAddress("三界镇上官岭(104国道1572段)");
        poi1.setType("公司企业;公司;机械电子");
        poi1.setGeom("POINT(120.840486 29.743005)");  // WKT 格式,经度在前
        list.add(poi1);

        // 第二条数据
        PoiShpData poi2 = new PoiShpData();
        poi2.setPoiid("P002");
        poi2.setName("杭州西湖风景区");
        poi2.setAddress("杭州市西湖区龙井路1号");
        poi2.setType("风景区;旅游景点");
        poi2.setGeom("POINT(120.130778 30.258708)");
        list.add(poi2);

        // 可以继续添加更多数据...
        return list;
    }

    /**
     * 构建示例 POI 数据
     */
    private static List<PoiGdalData> buildPoiGdalDataList() {
        List<PoiGdalData> list = new ArrayList<>();

        // 第一条数据
        PoiGdalData poi1 = new PoiGdalData();
        poi1.setPoiid("P001");
        poi1.setName("绍兴泉岗机械科技有限公司");
        poi1.setAddress("三界镇上官岭(104国道1572段)");
        poi1.setType("公司企业;公司;机械电子");
        poi1.setGeom("POINT(120.840486 29.743005)");  // WKT 格式,经度在前
        list.add(poi1);

        // 第二条数据
        PoiGdalData poi2 = new PoiGdalData();
        poi2.setPoiid("P002");
        poi2.setName("杭州西湖风景区");
        poi2.setAddress("杭州市西湖区龙井路1号");
        poi2.setType("风景区;旅游景点");
        poi2.setGeom("POINT(120.130778 30.258708)");
        list.add(poi2);

        // 可以继续添加更多数据...
        return list;
    }
}

PoiGdalData(PoiShpData同字段):

@Data
@ExcelIgnoreUnannotated
public class PoiGdalData {

    @GdalExportField(length = 50,name = "ID"/*,nullAble = false*/)
    private String poiid;

    @GdalExportField(length = 100,name = "NAME",nullAble = false)
    private String name;

    @GdalExportField(length = 200,name = "ADDRESS")
    private String address;

    @GdalExportField(name = "TYPE")
    private String type;

    @GdalExportField(name = "geom",isGeom = true)
    private String geom;

    public PoiGdalData() {
    }

}

WktGeomjsonUtils:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.wololo.geojson.GeoJSON;
import org.wololo.jts2geojson.GeoJSONReader;
import org.wololo.jts2geojson.GeoJSONWriter;
public class WktGeomjsonUtils {
    /**
     * geojson 转 wkt
     *
     * @param geojson
     * @return
     * @throws Exception
     */
    public static String geojson2Wkt(String geojson) throws Exception {
        Geometry geometry = geojson2Geometry(geojson);
        return geometry2Wkt(geometry);
    }
 }

GdalExportField :

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GdalExportField {
    String name() default "";                //字段名,默认使用model字段名
    int type() default ogr.OFTString;  //字段类型
    int length() default 100;     //字段长度
    String alternativeName() default ""; //字段别名
    boolean nullAble() default true;           
    String defaultValue() default "";      
    int subType() default 0;            
    boolean isGeom() default false;     
}

以上基本就是java处理gdal和shp文件的大致流程,这里仅供参考学习,本人也参考了别人的文档,翻看了好多文档,感觉很麻烦,所以整理起来放在一起,经测试基本可以使用.

更多推荐