GIS 坐标转换:Shp 数据重投影

关注我,带你一起学GIS ^

前言

在GIS开发中,经常需要进行数据的转换处理,特别是Shapefile数据的投影转换更是重中之重,如何高效、准确的将源数据坐标系转换到目标坐标系是我们需要研究解决的问题。

本篇教程在之前文章的基础上讲解如何将使用GeoTools工具实现Shapefile数据重投影。

开发环境

本文使用如下开发环境,以供参考。

时间:2025年

GeoTools:34-SNAPSHOT

IDE:IDEA2025.1.2

JDK:17

1. 安装依赖

pom.xml文件中添加gt-shapefilegt-swing两个依赖。其中gt-shapefile用于shp数据的转换处理,gt-swing属于图形用户界面工具库,用于地理空间数据的可视化、交互操作和地图应用的快速开发。

<dependencies>
    <dependency>
        <groupId>org.geotools</groupId>
        <artifactId>gt-shapefile</artifactId>
        <version>${geotools.version}</version>
    </dependency>
    <dependency>
        <groupId>org.geotools</groupId>
        <artifactId>gt-swing</artifactId>
        <version>${geotools.version}</version>
    </dependency>
    <dependency>
        <groupId>org.geotools</groupId>
        <artifactId>gt-epsg-hsql</artifactId>
        <version>${geotools.version}</version>
    </dependency>
</dependencies>
<repositories>
  <repository>
    <id>osgeo</id>
    <name>OSGeo Release Repository</name>
    <url>https://repo.osgeo.org/repository/release/</url>
    <snapshots><enabled>false</enabled></snapshots>
    <releases><enabled>true</enabled></releases>
  </repository>
  <repository>
    <id>osgeo-snapshot</id>
    <name>OSGeo Snapshot Repository</name>
    <url>https://repo.osgeo.org/repository/snapshot/</url>
    <snapshots><enabled>true</enabled></snapshots>
    <releases><enabled>false</enabled></releases>
  </repository>
</repositories>

2. 创建工具类

在开发工具中创建ShpReprojection类,用于实现数据的重投影并导出。

main方法中调用显示地图方法。

public class ValidateGeometry {

    // 定义Shp源数据
    private File sourceFile;
    // 定义要素源
    private SimpleFeatureSource featureSource;
    // 定义地图组件
    private MapContent map;

    public static void main(String[] args) throws Exception {
        ShpReprojection shpReprojection = new ShpReprojection();
        // 打开地图显示Shp文件
        shpReprojection.displayShapefile();
    }

定义displayShapefile显示方法,用于将Shapefile数据添加到地图窗口。通过在JMapFrame的工具栏上添加一个按钮来导出Shapefile文件。

// 显示Shp数据
private void displayShapefile() throws Exception{
    sourceFile = JFileDataStoreChooser.showOpenFile("shp",null);
    if(sourceFile==null){
        System.out.println("Shp 文件未知错误,请重新选择!");
        return;
    }
    // 数据仓库
    FileDataStore dataStore = FileDataStoreFinder.getDataStore(sourceFile);

    // 获取要素数据源
    featureSource = dataStore.getFeatureSource();

    // 创建地图
    map = new MapContent();
    Style style = SLD.createSimpleStyle(featureSource.getSchema());
    Layer layer = new FeatureLayer(featureSource,style);

    // 添加图层
    map.layers().add(layer);

    // 创建工具条
    JMapFrame  mapFrame = new JMapFrame(map);
    mapFrame.enableToolBar(true);
    mapFrame.enableStatusBar(true);

    JToolBar toolBar = mapFrame.getToolBar();
    toolBar.addSeparator();
    toolBar.add(new JButton(new ExportShapefileAction()));

    // 设置地图窗口大小
    mapFrame.setSize(800,600);
    mapFrame.setVisible(true);
}

在上面的代码中使用JMapFrame创建了一个工具条,使用JButton创建了一个按钮,并将其添加到工具栏中。点击该按钮,实现Shapefile数据的导出操作。

3. 导出 Shapefile 文件

通过定义一个内部嵌套类ExportShapefileAction来实现Shapefile文件的导出工作。

// 定义内部嵌套类
class ExportShapefileAction extends SafeAction {
    ExportShapefileAction() {
        super("ExportShapefileAction");
        putValue(Action.SHORT_DESCRIPTION, "Export using current crs");
    }

    public void action(ActionEvent e) throws Throwable{
        exportToShapefile();
    }
}

定义上文中的私有方法exportToShapefile,在该方法中实现数据导出。

首先使用JFileDataStoreChooser创建保存Shp文件选择框,然后获取当前选择文件。例子中是通过写死坐标系进行转换,需要在导出Shp时选择对应的投影坐标系导出。

// 导出重投影数据到Shapefile
private void exportToShapefile() throws Exception{
    // 选择Shp文件
    JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
    chooser.setDialogTitle("保存重投影 Shapefile 文件");
    chooser.setSaveFile(sourceFile);

    int returnVal = chooser.showSaveDialog(null);
    if(returnVal != JFileDataStoreChooser.APPROVE_OPTION){
        return;
    }
    File file = chooser.getSelectedFile();
    if(file.equals(sourceFile)){
        JOptionPane.showMessageDialog(null,"Can't replace " + file);
        return;
    }

    // 定义坐标系
    CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");
    CoordinateReferenceSystem webMercator = CRS.decode("EPSG:3857");

    boolean lenient = true;
    MathTransform transform = CRS.findMathTransform(wgs84, mapCrs, lenient);

    // 获取源数据结构
    SimpleFeatureType schema = featureSource.getSchema();
    SimpleFeatureCollection featureCollection = featureSource.getFeatures();

    // 创建 Shapefile 工厂
    ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
    Map<String, Serializable> create = new HashMap<>();
    create.put("url",file.toURI().toURL());
    create.put("create spatial index",Boolean.TRUE);
    create.put("charset","GBK"); // 设置中文字符集,以防乱码
    ShapefileDataStore dataStore = (ShapefileDataStore) factory.createNewDataStore(create);

    SimpleFeatureType type = SimpleFeatureTypeBuilder.retype(schema,webMercator);
    dataStore.createSchema(type);
    dataStore.forceSchemaCRS(CRS.decode("EPSG:3857"));  // 明确CRS

    // 获取数据名称
    String createdName = dataStore.getTypeNames()[0];

    // 创建重投影提交事务
    Transaction transaction = new DefaultTransaction("Reproject");
    try(FeatureWriter<SimpleFeatureType,SimpleFeature> writer = dataStore.getFeatureWriter(createdName,transaction)){

        SimpleFeatureIterator iterator =  featureCollection.features();
        while (iterator.hasNext()) {
            SimpleFeature feature = iterator.next();
            SimpleFeature newFeature = writer.next();
            // 写入目标属性
            newFeature.setAttributes(feature.getAttributes());
            // 写入重投影几何属性
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            Geometry newGeometry = JTS.transform(geometry, transform);

            newFeature.setDefaultGeometry(newGeometry);

            writer.write();
        }
        transaction.commit();
        JOptionPane.showMessageDialog(null,"Export shapefile success ");
    }catch(Exception e){
        e.printStackTrace();
        transaction.rollback();
        JOptionPane.showMessageDialog(null,"Export shapefile failed ");
    }finally{
        transaction.close();
    }
}

在复制源数据属性时,需要先调用iterator.next()之后才能调用writer.next(),然后就可以复制属性对象和几何对象数据。

SimpleFeature feature = iterator.next();
SimpleFeature newFeature = writer.next();
// 写入目标属性
newFeature.setAttributes(feature.getAttributes());
// 写入重投影几何属性
Geometry geometry = (Geometry) feature.getDefaultGeometry();
Geometry newGeometry = JTS.transform(geometry, transform);

newFeature.setDefaultGeometry(newGeometry);

writer.write();

注:使用此方式重投影Shapefile数据时,.prj文件已经生成,而且坐标也显示正确,但是在GIS软件ArcMap中打开时会提示缺少坐标参考信息。

属性表中确实是未能正确识别:

效果图片


OpenLayers示例数据下载,请在公众号后台回复:ol数据

全国信息化工程师-GIS 应用水平考试资料,请在公众号后台回复:GIS考试

GIS之路公众号已经接入了智能助手,欢迎大家前来提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏+关注 

本号不定时更新有关 GIS开发  相关内容,欢迎关注 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部