需求
由于shp数据存储到postgresql数据库中,前端调用数据库实现数据的渲染,最近有一个新的需求,前端圈选数据,实现数据的下载,数据可以是shp、dxf、excel格式,这里主要记录在后端通过调用gdal来实现这个需求
具体实现
实现数据查询
前端传递一个polygon,需要在后端计算这个polygon裁剪的数据,这样才能得出圈选的数据,polygon格式如下:
json
"geometry":"POLYGON ((110.97538610392178 21.560137351924578,110.97979054002349 21.560079864736938,110.97929410551264 21.55479668272995,110.97338335499501 21.554995383717067,110.97538610392178 21.560137351924578))"
主要查询:
java
List<Map<String, Object>> resultList = analysisMapper.queryContains(layer, wktPolygon);
sql语句
java
<select id="queryContains" resultType="java.util.Map">
SELECT ST_AsGeoJSON(geom) as geometry,*
FROM
"${tableName}"
WHERE st_contains(st_setsrid('${polygon}'::geometry,4326),st_setsrid(geom,4326))
or ST_Intersects(st_setsrid('${polygon}'::geometry,4326), st_setsrid(geom,4326))
order by gid
</select>
生成shp数据
生成数据需要使用GDAL库,在调用前,需要先注册一下:
java
ogr.RegisterAll();
主要的思路如下:
- 由于从数据库中查询的结果是一个表,要生成shp,需要先创建一个shp,在定义属性名及类型,然后写入普通属性,写入空间属性
- 创建shp
java
gdal.SetConfigOption("SHAPE_ENCODING", "");
// 假设所有要素都有相同的几何类型,仅查看第一个要素
String geomTypeStr = (String) resultList.get(0).get("the_geom");
String upperWkt = geomTypeStr.toUpperCase();
int geomType = ogr.wkbUnknown; // 默认值
if (upperWkt.startsWith("POINT")) {
geomType = ogr.wkbPoint;
} else if (upperWkt.startsWith("LINESTRING")) {
geomType = ogr.wkbLineString;
} else if (upperWkt.startsWith("POLYGON")) {
geomType = ogr.wkbPolygon;
} else if (upperWkt.startsWith("MULTIPOINT")) {
geomType = ogr.wkbPoint;
} else if (upperWkt.startsWith("MULTILINESTRING")) {
geomType = ogr.wkbLineString;
} else if (upperWkt.startsWith("MULTIPOLYGON")) {
geomType = ogr.wkbPolygon;
} else if (upperWkt.startsWith("GEOMETRYCOLLECTION")) {
geomType = ogr.wkbGeometryCollection;
}
Driver shpDriver = ogr.GetDriverByName("ESRI Shapefile");
DataSource shpDS = shpDriver.CreateDataSource(filePath);
Layer glayer = shpDS.CreateLayer(layer, null, geomType);
- 添加字段
java
// 添加字段,假设第一个元素包含所有字段
Set<String> keys = resultList.get(0).keySet();
for (String key : keys) {
if (!"geom".equals(key)) { // 排除几何字段
Object value = resultList.get(0).get(key);
if (value instanceof Integer) {
glayer.CreateField(new FieldDefn(key, 0));
} else if (value instanceof Double) {
glayer.CreateField(new FieldDefn(key, 2));
} else if (value instanceof String) {
glayer.CreateField(new FieldDefn(key, 4));
}
}
}
- 天才数据
java
// 填充数据
for (Map<String, Object> featureData : resultList) {
Feature feature = new Feature(glayer.GetLayerDefn());
for(Map.Entry<String,Object>entry:featureData.entrySet()){
String key = entry.getKey();
Object value = entry.getValue();
if("the_geom".equals(key)){
int[] pnSRID = new int[1];
try{
String geoJsonString = (String)value;
org.gdal.ogr.Geometry geom = org.gdal.ogr.Geometry.CreateFromWkt(geoJsonString);
feature.SetGeometry(geom);
geom.delete();
}
catch (Exception e){
e.printStackTrace();
}
}else if(!"geom".equals(key)) {
// 设置属性数据
int fieldIndex = feature.GetFieldIndex(key);
if (value instanceof Integer) {
feature.SetField(fieldIndex, (Integer) value);
} else if (value instanceof Double) {
feature.SetField(fieldIndex, (Double) value);
} else if (value instanceof String) {
feature.SetField(fieldIndex, (String) value);
}
}
}
glayer.CreateFeature(feature);
feature.delete();
}
shpDS.delete();
压缩数据
由于被圈选的图层不止一个,因此多个数据传输比较麻烦,最好将生成的数据打包成压缩包,最终传输给前端
java
public void zipShapefile(String shapefilePath ,String zipFilePath){
byte[] buffer = new byte[1024];
try{
FileOutputStream fos = new FileOutputStream(zipFilePath);
ZipOutputStream zos = new ZipOutputStream(fos);
File dir = new File(shapefilePath);
File[] files = dir.listFiles();
for(File file:files){
FileInputStream fis = new FileInputStream(file);
zos.putNextEntry(new ZipEntry(file.getName()));
int length;
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
fis.close();
}
zos.close();
}catch(IOException ioe){
ioe.printStackTrace();
}
}
生成dxf
由于shp数据在生成的时候存放在本地的文件夹内,而gdal从shp生成dxf比较简单,因此这里生成dxf会直接先生成shp文件,再从shp文件转成dxf,最后,再将数据打包
java
public void ShapefileToDXF(String shpFilePath){
File folder = new File(shpFilePath);
if(folder.isDirectory()){
File[] files = folder.listFiles();
if(files !=null){
for(File file:files){
if(file.getName().endsWith(".shp")){
// 调用转换函数
convertShpToDxf(file.getAbsolutePath(), file.getAbsolutePath().replace(".shp", ".dxf"));
}
}
}
deleteFilesInFolder(folder,"shp");
}
}
public static void convertShpToDxf(String shpPath, String dxfPath) {
// 获取Shapefile驱动
Driver shpDriver = ogr.GetDriverByName("ESRI Shapefile");
if (shpDriver == null) {
System.err.println("Shapefile driver not available.");
return;
}
// 打开Shapefile数据源
DataSource shpDataSource = shpDriver.Open(shpPath, 0); // 0 means read-only
if (shpDataSource == null) {
System.err.println("Failed to open shapefile.");
return;
}
if (shpDataSource.GetLayerCount() == 0) {
System.err.println("No layers found in shapefile.");
shpDataSource.delete();
return;
}
// 获取DXF驱动
Driver dxfDriver = ogr.GetDriverByName("DXF");
if (dxfDriver == null) {
System.err.println("DXF driver not available.");
return;
}
// 创建DXF文件
DataSource dxfDataSource = dxfDriver.CreateDataSource(dxfPath, null);
if (dxfDataSource == null) {
System.err.println("Failed to create DXF file.");
return;
}
// 从Shapefile复制图层到DXF
Layer shpLayer = shpDataSource.GetLayerByIndex(0);
if (shpLayer == null) {
System.err.println("Failed to get layer from shapefile.");
return;
}
// 创建DXF图层,只包含几何数据
Layer dxfLayer = dxfDataSource.CreateLayer("dxf_layer", shpLayer.GetSpatialRef(), shpLayer.GetGeomType());
if (dxfLayer == null) {
System.err.println("Failed to create DXF layer.");
System.exit(1);
}
// 复制几何对象到新图层
Feature shpFeature;
while ((shpFeature = shpLayer.GetNextFeature()) != null) {
// 创建一个新特征,可能需要根据DXF的要求调整几何类型或属性
Feature newFeature = new Feature(dxfLayer.GetLayerDefn());
newFeature.SetGeometry(shpFeature.GetGeometryRef());
// 可能需要调整属性设置代码
if (dxfLayer.CreateFeature(newFeature) != 0) {
System.err.println("Failed to add feature to DXF.");
}
newFeature.delete(); // 清理新创建的特征对象
}
// // 复制图层到DXF文件中
// Layer newLayer = dxfDataSource.CopyLayer(shpLayer, "new_layer_name", null);
// if (newLayer == null) {
// System.err.println("Failed to copy layer to DXF.");
// }
// 清理资源
shpDataSource.delete(); // 关闭Shapefile
dxfDataSource.delete(); // 关闭DXF文件
}
删除冗余文件
由于每次请求都会在本地生成文件,因此会造成数据的冗余,因此,再每开启下一次请求时,需要找到存储的文件夹,进行清空,使之一直保持只保存一次请求的文件;再者,再生成dxf时,会先生成shp,相当于shp是中间文件,再传输给前端时会对dxf进行压缩,此时中间文件就可能被压缩进去,因此此时也需要把shp文件删除
java
public static void deleteFilesInFolder(final File folder,String delType) {
if (!folder.exists()) {
return;
}
File[] files = folder.listFiles();
if (files != null) { // 为空的文件夹路径可能导致 null 返回
for (File f : files) {
if(delType.equals("all")){
if (f.isFile()) {
f.delete(); // 删除每个文件
}
}else if(delType.equals("shp")){
if(f.getName().endsWith(".dbf")||f.getName().endsWith(".shp")||f.getName().endsWith(".shx")){
if (f.isFile()) {
f.delete(); // 删除每个文件
}
}
}
}
}
}
生成excel
这个步骤比较简单,代码如下:
java
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1");
Row headerRow = sheet.createRow(0);
//创建表头单元格,并设置单元格的值
Set<String> keys = resultList.get(0).keySet();
Map<String,Integer> excelKey = new HashMap<>();
int colIndex = 0;
for (String key : keys) {
Cell headerCell1 = headerRow.createCell(colIndex);
excelKey.put(key,colIndex);
headerCell1.setCellValue(key);
colIndex++;
}
//填充数据
int rowIndex = 1;
for (Map<String, Object> featureData : resultList){
Row row = sheet.createRow(rowIndex++);
for(Map.Entry<String,Object>entry:featureData.entrySet()){
String key = entry.getKey();
Object value = entry.getValue();
colIndex = excelKey.get(key);
Cell cell = row.createCell(colIndex);
cell.setCellValue(value.toString());
}
}
//保存excel文件
try(FileOutputStream outputStream = new FileOutputStream(filePath+"\\"+layer+".xls")){
workbook.write(outputStream);
}