导出ShapeFile工具类
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.*;
/**
* @author LiuXing
* @date 2022-01-10
*/
public class ExportShapeFileUtil {
static String BLANK = " ";
/**
* 根据ListMap导出完整shape
*
* @param response response
* @param list listMap
* @return boolean
*/
public static boolean exportCompleteShape(HttpServletResponse response, List<Map<String, Object>> list) {
boolean flag = false;
FileOutputStream fileOutputStream = null;
String id = String.valueOf(System.currentTimeMillis());
String tempPath = System.getProperty("catalina.home") + "/temp/" + id + "/";
//临时文件
String shpFilePath = tempPath + id + ".shp";
//压缩包,把xls文件压缩到zip中,在浏览器下载
String zipPath = System.getProperty("catalina.home") + "/temp/" + id + ".zip";
File dir = new File(tempPath);
//每目录就新建
if (!dir.exists()) {
boolean mkdirs = dir.mkdirs();
}
//把生成的文件压缩为.zip的
File zipFile = new File(zipPath);
File shpFile = new File(shpFilePath);
try {
//处理点线面
Map<String, Object> map = list.get(0);
String geomText = map.get("geomText").toString();
String featureTypeStr = SubStringUtils.subStr(geomText, 0, StrUtil.indexOf(geomText, '('));
if (StrUtil.containsAnyIgnoreCase(featureTypeStr, BLANK)) {
featureTypeStr = SubStringUtils.subStr(featureTypeStr, 0, StrUtil.indexOf(featureTypeStr, ' '));
}
if (StrUtil.equalsIgnoreCase(featureTypeStr, GeomFormerType.POINT.value)) {
featureTypeStr = GeomTitleTypeEnum.POINT.value;
} else if (StrUtil.equalsIgnoreCase(featureTypeStr, GeomFormerType.LINESTRING.value)) {
featureTypeStr = GeomTitleTypeEnum.LINESTRING.value;
} else if (StrUtil.equalsIgnoreCase(featureTypeStr, GeomFormerType.POLYGON.value)) {
featureTypeStr = GeomTitleTypeEnum.POLYGON.value;
} else if (StrUtil.equalsIgnoreCase(featureTypeStr, GeomFormerType.MULTILINESTRING.value)) {
featureTypeStr = GeomTitleTypeEnum.MULTILINESTRING.value;
} else if (StrUtil.equalsIgnoreCase(featureTypeStr, GeomFormerType.MULTIPOLYGON.value)) {
featureTypeStr = GeomTitleTypeEnum.MULTIPOLYGON.value;
}
//构建typeSpec
StringBuilder typeSpecStringBuilder = new StringBuilder();
typeSpecStringBuilder.append("the_geom:" + featureTypeStr + ":4326,");
assert CollUtil.isNotEmpty(list);
Map<String, Object> stringObjectMap = list.get(0);
for (String field : stringObjectMap.keySet()) {
if (!StrUtil.equals("geomText", field) && !StrUtil.equals("gid", field) && !StrUtil.equals("geom", field)) {
typeSpecStringBuilder.append(field).append(",");
}
}
String typeSpec = typeSpecStringBuilder.substring(0, typeSpecStringBuilder.length() - 1);
final SimpleFeatureType featureType = DataUtilities.createType("Location", typeSpec);
//处理shape数据
List<SimpleFeature> features = new ArrayList<>();
ListFeatureCollection collection = new ListFeatureCollection(featureType, features);
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
for (Map<String, Object> row : list) {
String wkt = row.get("geomText").toString();
//WKT转Geometry
WKTReader wktReader = new WKTReader();
Geometry geometry = null;
try {
geometry = wktReader.read(wkt);
featureBuilder.add(geometry);
} catch (Exception e) {
e.printStackTrace();
}
for (String colum : row.keySet()) {
if (!StrUtil.equals("geomText", colum) && !StrUtil.equals("gid", colum) && !StrUtil.equals("geom", colum)) {
featureBuilder.add(row.get(colum));
}
}
SimpleFeature feature = featureBuilder.buildFeature(null);
collection.add(feature);
}
ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("url", shpFile.toURI().toURL());
params.put("create spatial index", Boolean.TRUE);
ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
//处理中文乱码
newDataStore.setCharset(Charset.forName("GBK"));
newDataStore.createSchema(featureType);
newDataStore.forceSchemaCRS(DefaultGeographicCRS.WGS84);
Transaction transaction = new DefaultTransaction("create");
String typeName = newDataStore.getTypeNames()[0];
SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
if (featureSource instanceof SimpleFeatureStore) {
SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
featureStore.setTransaction(transaction);
try {
featureStore.addFeatures(collection);
transaction.commit();
} catch (Exception problem) {
problem.printStackTrace();
transaction.rollback();
} finally {
transaction.close();
}
}
FileOutputStream stream = new FileOutputStream(zipFile);
ZipUtils.toZip(tempPath, stream, true);
// 读到流中
//文件的存放路径
InputStream inStream = new FileInputStream(zipPath);
// 设置输出的格式
response.reset();
response.setContentType("application/bin");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFile.getName(), "UTF-8"));
ServletOutputStream outputStream = response.getOutputStream();
// 循环取出流中的数据
byte[] b = new byte[1024];
int len;
while ((len = inStream.read(b)) > 0) {
outputStream.write(b, 0, len);
}
inStream.close();
outputStream.flush();
outputStream.close();
flag = true;
} catch (Exception ignored) {
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (Exception ignored) {
}
}
File fileDir = new File(tempPath);
if (fileDir.exists() && fileDir.isDirectory()) {
File[] files = fileDir.listFiles();
assert files != null;
for (File f : files) {
if (f.exists()) {
boolean delete = f.delete();
}
}
boolean delete = fileDir.delete();
}
if (zipFile.exists()) {
boolean delete = zipFile.delete();
}
}
return flag;
}
}
其他辅助工具类
3.1 压缩包工具类
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 压缩文件Utils
*
* @date 2022-01-10
*/
public class ZipUtils {
private static final int BUFFER_SIZE = 2 * 1024;
/**
* 压缩成ZIP 方法1
*
* @param srcDir 压缩文件夹路径
* @param out 压缩文件输出流
* @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
* <p>
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(String srcDir, OutputStream out, boolean keepDirStructure) throws RuntimeException {
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
File sourceFile = new File(srcDir);
compress(sourceFile, zos, sourceFile.getName(), keepDirStructure);
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtils", e);
} finally {
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 递归压缩方法
*
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
* <p>
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
*/
private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean keepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
if (sourceFile.isFile()) {
// 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
zos.putNextEntry(new ZipEntry(name));
// copy文件到zip输出流中
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
// Complete the entry
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
// 需要保留原来的文件结构时,需要对空文件夹进行处理
if (keepDirStructure) {
// 空文件夹的处理
zos.putNextEntry(new ZipEntry(name + "/"));
// 没有文件,不需要文件的copy
zos.closeEntry();
}
} else {
for (File file : listFiles) {
// 判断是否需要保留原来的文件结构
if (keepDirStructure) {
// 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
// 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
compress(file, zos, name + "/" + file.getName(), keepDirStructure);
} else {
compress(file, zos, file.getName(), keepDirStructure);
}
}
}
}
}
}
3.2 方便处理点线面的枚举
/**
* Shape文件将要使用的类型
*
* @author LiuXing
* @date 2022-01-11
*/
public enum GeomTitleTypeEnum {
/**
* 点
*/
POINT("Point"),
/**
* 线
*/
LINESTRING("LineString"),
/**
* 面
*/
POLYGON("Polygon"),
/**
* 复杂线
*/
MULTILINESTRING("MultiLineString"),
/**
* 复杂面
*/
MULTIPOLYGON("MultiPolygon");
public String value;
GeomTitleTypeEnum(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}
/**
* 原始类型
* @author LiuXing
* @date 2022-01-11
*/
public enum GeomFormerType {
/**
* 点
*/
POINT("POINT"),
/**
* 线
*/
LINESTRING("LINESTRING"),
/**
* 面
*/
POLYGON("POLYGON"),
/**
* 复杂线
*/
MULTILINESTRING("MULTILINESTRING"),
/**
* 复杂面
*/
MULTIPOLYGON("MULTIPOLYGON");
public String value;
GeomFormerType(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}
3.3字符串切割工具
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author LiuXing
* @date 2021-05-10
*/
public class SubStringUtils {
private static final Logger logger = LoggerFactory.getLogger(SubStringUtils.class);
/**
* @param strings 字符串数组
* @param splitStr 连接数组的字符串
* @Title: join
* @Description: 用指定字符串数组相连接,并返回
* @return: String
*/
public static String join(String[] strings, String splitStr) {
if (strings != null) {
if (strings.length == 1) {
return strings[0];
}
StringBuffer sb = new StringBuffer();
for (String str : strings) {
sb.append(str).append(splitStr);
}
if (sb.length() > 0) {
sb.delete(sb.length() - splitStr.length(), sb.length());
}
return sb.toString();
}
return null;
}
/**
* @param str 字符串
* @param end 结束位置
* @Title: subStrStart
* @Description: 从头开始截取
* @return: String
*/
public static String subStrStart(String str, int end) {
return subStr(str, 0, end);
}
/**
* @param str 字符串
* @param start 开始位置
* @Title: subStrEnd
* @Description: 从尾开始截取
* @return: String
*/
public static String subStrEnd(String str, int start) {
return subStr(str, str.length() - start, str.length());
}
/**
* @param str 待截取的字符串
* @param length 长度 ,>=0时,从头开始向后截取length长度的字符串;<0时,从尾开始向前截取length长度的字符串
* @return
* @throws RuntimeException
* @Title: subStr
* @Description: 截取字符串 (支持正向、反向截取)
* @return: String
*/
public static String subStr(String str, int length) throws RuntimeException {
if (str == null) {
logger.error("字符串为null");
throw new NullPointerException("字符串为null");
}
int len = str.length();
if (len < Math.abs(length)) {
String msg = "最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(length));
logger.error(msg);
throw new StringIndexOutOfBoundsException(msg);
}
if (length >= 0) {
return subStr(str, 0, length);
} else {
return subStr(str, len - Math.abs(length), len);
}
}
/**
* 截取字符串 (支持正向、反向选择)
*
* @param str 待截取的字符串
* @param start 起始索引 ,>=0时,从start开始截取;<0时,从length-|start|开始截取
* @param end 结束索引 ,>=0时,从end结束截取;<0时,从length-|end|结束截取
* @return 返回截取的字符串
* @throws RuntimeException
*/
public static String subStr(String str, int start, int end) throws RuntimeException {
if (str == null) {
logger.error("字符串为空");
throw new NullPointerException("");
}
int len = str.length();
//记录起始索引
int s = 0;
//记录结尾索引
int e = 0;
if (len < Math.abs(start)) {
String msg = "最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(start));
logger.error(msg);
throw new StringIndexOutOfBoundsException(msg);
} else if (start < 0) {
s = len - Math.abs(start);
} else if (start < 0) {
s = 0;
} else {//>=0
s = start;
}
if (len < Math.abs(end)) {
String msg = "最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(end));
logger.error(msg);
throw new StringIndexOutOfBoundsException(msg);
} else if (end < 0) {
e = len - Math.abs(end);
} else if (end == 0) {
e = len;
} else {//>=0
e = end;
}
if (e < s) {
logger.error("截至索引小于起始索引:" + (e - s));
throw new StringIndexOutOfBoundsException("截至索引小于起始索引:" + (e - s));
}
return str.substring(s, e);
}
public static void main(String[] args) {
String str = "12345abcde";
System.out.println("--------------------------------");
System.out.println("正向截取长度为4,结果:\n" + SubStringUtils.subStr(str, 4));
System.out.println("反向截取长度为4,结果:\n" + SubStringUtils.subStr(str, -4));
System.out.println("--------------------------------");
System.out.println("正向截取到第4个字符的位置,结果:\n" + SubStringUtils.subStrStart(str, 4));
System.out.println("反向截取到第4个字符的位置,结果:\n" + SubStringUtils.subStrEnd(str, 4));
System.out.println("--------------------------------");
System.out.println("从第2个截取到第9个,结果:\n" + SubStringUtils.subStr(str, 1, 9));
System.out.println("从第2个截取到倒数第1个,结果:\n" + SubStringUtils.subStr(str, 1, -1));
System.out.println("从倒数第4个开始截取,结果:\n" + SubStringUtils.subStr(str, -4, 0));
System.out.println("从倒数第4个开始截取,结果:\n" + SubStringUtils.subStr(str, -4, 10));
}
}