前言
本章的主要内容是投影的图形坐标系哦的转换
CRS Lab Application
首先还是导入所需的依赖
<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>
然后创建我们的CRSLab类
public class CRSLab {
private File sourceFile;
private SimpleFeatureSource featureSource;
private MapContent map;
public static void main(String[] args) throws Exception {
CRSLab lab = new CRSLab();
lab.displayShapefile();
}
这里在main中new了CRSLab类,并执行了其中的displayShapefile方法,从名字就可以看出是用来展示shapefile的一个方法,具体实现往下看
private void displayShapefile() throws Exception {
sourceFile = JFileDataStoreChooser.showOpenFile("shp", null);
if (sourceFile == null) {
return;
}
FileDataStore store = FileDataStoreFinder.getDataStore(sourceFile);
featureSource = store.getFeatureSource();
// Create a map context and add our shapefile to it
map = new MapContent();
Style style = SLD.createSimpleStyle(featureSource.getSchema());
Layer layer = new FeatureLayer(featureSource, style);
map.layers().add(layer);
// Create a JMapFrame with custom toolbar buttons
JMapFrame mapFrame = new JMapFrame(map);
mapFrame.enableToolBar(true);
mapFrame.enableStatusBar(true);
JToolBar toolbar = mapFrame.getToolBar();
toolbar.addSeparator();
toolbar.add(new JButton(new ValidateGeometryAction()));
toolbar.add(new JButton(new ExportShapefileAction()));
// Display the map frame. When it is closed the application will exit
mapFrame.setSize(800, 600);
mapFrame.setVisible(true);
}
在displayShapefile方法中,首先使用文件选择器JFileDataStoreChooser.showOpenFile("shp", null);
选择shapefile文件。然后创建了地图容器new MapContent();
和地图框架new JMapFrame(map);
来展示加载的shapefile文件。这一步没什么可说的,关键点在下面,我们给地图框架的ToolBar添加了两个按钮
JToolBar toolbar = mapFrame.getToolBar();
toolbar.addSeparator();
toolbar.add(new JButton(new ValidateGeometryAction()));
toolbar.add(new JButton(new ExportShapefileAction()));
并为两个按钮定义了两个类new ValidateGeometryAction()
和new ExportShapefileAction()
作为按钮点击后的执行的内容,本文的关键也在这两个类之间。
首先来看第一个
class ValidateGeometryAction extends SafeAction {
ValidateGeometryAction() {
super("Validate geometry");
putValue(Action.SHORT_DESCRIPTION, "Check each geometry");
}
public void action(ActionEvent e) throws Throwable {
int numInvalid = validateFeatureGeometry(null);
String msg;
if (numInvalid == 0) {
msg = "All feature geometries are valid";
} else {
msg = "Invalid geometries: " + numInvalid;
}
JOptionPane.showMessageDialog(
null, msg, "Geometry results", JOptionPane.INFORMATION_MESSAGE);
}
}
可以看到这个类继承了SafeAction的类
通过翻阅源码可知,SafeAction是AbstractAction的子类,属于其”安全版本“,它将记录所有的错误信息。而AbstractAction是JFC Action接口的实现,也就是执行的接口了(这里有点难以描述,感兴趣的可以去查阅源码,不感兴趣只需要知道我们实现继承SafeAction并重写其action方法就可以实现按钮的功能即可)
在action方法中调用了另一个方法validateFeatureGeometry(null)
该方法详情如下
private int validateFeatureGeometry(ProgressListener progress) throws Exception {
final SimpleFeatureCollection featureCollection = featureSource.getFeatures();
// Rather than use an iterator, create a FeatureVisitor to check each fature
class ValidationVisitor implements FeatureVisitor {
public int numInvalidGeometries = 0;
public void visit(Feature f) {
SimpleFeature feature = (SimpleFeature) f;
Geometry geom = (Geometry) feature.getDefaultGeometry();
if (geom != null && !geom.isValid()) {
numInvalidGeometries++;
System.out.println("Invalid Geoemtry: " + feature.getID());
}
}
}
ValidationVisitor visitor = new ValidationVisitor();
// Pass visitor and the progress bar to feature collection
featureCollection.accepts(visitor, progress);
return visitor.numInvalidGeometries;
}
其作用是验证shapefile中的要素图形是否合法,并且返回上面读取的要素中的合法的要素的数量。这个按钮的功能也就是验证导入的shapefile中的合法的要素的数量。
再看看第二个按钮的类
class ExportShapefileAction extends SafeAction {
ExportShapefileAction() {
super("Export...");
putValue(Action.SHORT_DESCRIPTION, "Export using current crs");
}
public void action(ActionEvent e) throws Throwable {
exportToShapefile();
}
}
该类调用了方法exportToShapefile()
private void exportToShapefile() throws Exception {
SimpleFeatureType schema = featureSource.getSchema();
JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
chooser.setDialogTitle("Save reprojected 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, "Cannot replace " + file);
return;
}
CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
boolean lenient = true; // allow for some error due to different datums
MathTransform transform = CRS.findMathTransform(dataCRS, worldCRS, lenient);
SimpleFeatureCollection featureCollection = featureSource.getFeatures();
Transaction transaction = new DefaultTransaction("Reproject");
try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
dataStore.getFeatureWriterAppend(createdName, transaction);
SimpleFeatureIterator iterator = featureCollection.features()) {
while (iterator.hasNext()) {
// copy the contents of each feature and transform the geometry
SimpleFeature feature = iterator.next();
SimpleFeature copy = writer.next();
copy.setAttributes(feature.getAttributes());
Geometry geometry = (Geometry) feature.getDefaultGeometry();
Geometry geometry2 = JTS.transform(geometry, transform);
copy.setDefaultGeometry(geometry2);
writer.write();
}
transaction.commit();
JOptionPane.showMessageDialog(null, "Export to shapefile complete");
} catch (Exception problem) {
problem.printStackTrace();
transaction.rollback();
JOptionPane.showMessageDialog(null, "Export to shapefile failed");
} finally {
transaction.close();
}
}
从名字可以看出是一个导出shapefile的类,还是逐行看一下功能
首先是选择文件保存的位置
SimpleFeatureType schema = featureSource.getSchema();
JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
chooser.setDialogTitle("Save reprojected 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, "Cannot replace " + file);
return;
}
然后进行坐标的转换
CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
boolean lenient = true; // allow for some error due to different datums
MathTransform transform = CRS.findMathTransform(dataCRS, worldCRS, lenient);
这里首先获取了源shapefile的坐标系,然后获取了地图容器的左边西,然后建立了两者之间转换的数学方法transform
下面为导出的shapefile文件创建要素类型
DataStoreFactorySpi factory = new ShapefileDataStoreFactory();
Map<String, Serializable> create = new HashMap<>();
create.put("url", file.toURI().toURL());
create.put("create spatial index", Boolean.TRUE);
DataStore dataStore = factory.createNewDataStore(create);
SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(schema, worldCRS);
dataStore.createSchema(featureType);
// Get the name of the new Shapefile, which will be used to open the FeatureWriter
String createdName = dataStore.getTypeNames()[0];
最后创建一个事务,将源shapefile的所有要素转换坐标系后添加到新的文件中
SimpleFeatureCollection featureCollection = featureSource.getFeatures();
Transaction transaction = new DefaultTransaction("Reproject");
try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
dataStore.getFeatureWriterAppend(createdName, transaction);
SimpleFeatureIterator iterator = featureCollection.features()) {
while (iterator.hasNext()) {
// copy the contents of each feature and transform the geometry
SimpleFeature feature = iterator.next();
SimpleFeature copy = writer.next();
copy.setAttributes(feature.getAttributes());
Geometry geometry = (Geometry) feature.getDefaultGeometry();
Geometry geometry2 = JTS.transform(geometry, transform);
copy.setDefaultGeometry(geometry2);
writer.write();
}
transaction.commit();
JOptionPane.showMessageDialog(null, "Export to shapefile complete");
} catch (Exception problem) {
problem.printStackTrace();
transaction.rollback();
JOptionPane.showMessageDialog(null, "Export to shapefile failed");
} finally {
transaction.close();
}
可以看到这里使用了JTS的transform方法并传入了源geometry和转换的transform来进行坐标系的转换。
最后运行main方法即可。
拓展
Geometry
Geometry其实就是GIS的形状,是要素的几何外形。
下面有几个典型的Geometry和其创建方式
Point
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
WKTReader reader = new WKTReader(geometryFactory);
Point point = (Point) reader.read("POINT (1 1)");
Line
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
WKTReader reader = new WKTReader(geometryFactory);
LineString line = (LineString) reader.read("LINESTRING(0 2, 2 0, 8 6)");