Web端Java中使用GeoTools导出Shape文件压缩包,包含.dbf; .fix; .prj; .shp; .shx
-
Sql 查询数据库中的需要导出的数据 geometry字段需要加上 ST_asText( )
例如:select g.name,ST_asText(g.geom) as "geomText" from tb_geometry g
List<Map<String, Object>> selectGeometryForShape();
-
导出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)); } }
-
相关pom
<properties> <geotools.version>26.1</geotools.version> </properties> <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-geojson</artifactId> <version>${geotools.version}</version> </dependency> <dependency> <groupId>org.geotools.jdbc</groupId> <artifactId>gt-jdbc-postgis</artifactId> <version>${geotools.version}</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-epsg-hsql</artifactId> <version>${geotools.version}</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-opengis</artifactId> <version>${geotools.version}</version> </dependency>