需求
给一个shp的zip包,计算里面的所有shp的最大bbox
分析
首先需要解压,然后是解析里面的shp数据,进行计算。
解压采用java的util包即可,分析shp数据则使用GIS领域大名鼎鼎的geotools。由于geotools给的API是基于文件的,所以无奈,只能是解压出来数据并生成到磁盘,然后再交给geotools来进行计算。
实现
解压工具
ZipUtil
读取zip包并解压到磁盘上形成文件。
注意:
1. 其中 getZipSize 函数计算size时会出现对明明有数据的zip包却返回-1的问题,替换使用 getZipSize_new 函数。
2. readZipFile函数在读取数据的时候,由于是使用的getZipSize的计算方法,因此有判断size的逻辑部分,做不同的读取操作。本文为了能够深刻牢记这一坑,所以不会做优化处理 :)。读者可以对此函数自行修改,使用getZipSize_new的计算来完善代码。
package com.jfqqq.zip;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
* 1. ze.getSize();有时候无用! 会返回-1!这时候数据可能还是存在的!比如world_merc.zip就是实例!
* 至于为什么,以后再研究!
* 所以也需要强制读取!
*
* 2. 目前测试通过,已经支持这样的目录:
* shapes.zip:解压出来:
* |shapes //目录0
* | shape.cpg //文件
* | shape.dbf //文件
* | shape.prj //文件
* | shape.sbn //文件
* | shape.sbx //文件
* | shape.shp //文件
* | shape.shx //文件
* | shp1 //目录1
* | shape - 副本.cpg //文件
* | shape - 副本.dbf //文件
* | shape - 副本.prj //文件
* | shape - 副本.sbn //文件
* | shape - 副本.sbx //文件
* | shape - 副本.shp //文件
* | shape - 副本.shx //文件
* | shp2 //目录2
* | shape_2.cpg //文件
* | shape_2.dbf //文件
* | shape_2.prj //文件
* | shape_2.sbn //文件
* | shape_2.sbx //文件
* | shape_2.shp //文件
* | shape_2.shx //文件
*
*
*/
public class ZipUtil {
private static Logger logger = LoggerFactory.getLogger(ZipUtil.class);
/**
* 读取zip文件内的文件,返回文件名称列表
*
* @param path
* @return
*/
public static List<String> readZipFileName(String path) {
List<String> list = new ArrayList<>();
try {
ZipFile zipFile = new ZipFile(path);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
list.add(entries.nextElement().getName());
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 获取文件数量
*
* @param zipFilePath
* @return
*/
public static int getZipFileCount(String zipFilePath) {
ZipFile zf = null;
int count = 0;
try {
zf = new ZipFile(zipFilePath);
count = zf.size(); //返回zip文件中的条目数
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
zf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return count;
}
/**
* 读取zip文件内的文件,返回文件内容列表
*
* @param path
* @return Map 返回的数据是一个map,key为文件相对路径,value为文件的byte数组数据,由于考虑到数据的容量上限,采用list中包含多个数组来承载
*
*/
public static Map<String, List<byte[]>> readZipFile(String path) {
Map<String, List<byte[]>> map = new HashMap<>();
ZipInputStream zin = null;
InputStream in = null;
try {
ZipFile zipFile = new ZipFile(path);
in = new BufferedInputStream(new FileInputStream(path));
zin = new ZipInputStream(in);
ZipEntry ze;
while ((ze = zin.getNextEntry()) != null) {
List<byte[]> dataBus = null;
if (ze.isDirectory()) {
} else {
long sizeLong = ze.getSize();
if (sizeLong > 0) {
// List<byte[]> tempDataBus = new ArrayList<>();
int arrSize = 1;
BigDecimal sizeLongNum = BigDecimal.valueOf(sizeLong);
BigDecimal intMaxNum = BigDecimal.valueOf(Integer.MAX_VALUE);
int compareValue = sizeLongNum.compareTo(intMaxNum);
if (compareValue == 1) {//sizeLong > Integer.MAX_VALUE
BigDecimal divide = sizeLongNum.divide(intMaxNum, RoundingMode.UP);
arrSize = divide.intValue();
// dataBus = new ArrayList<>(arrSize);
// BigDecimal count = BigDecimal.ZERO;
// for (int i = 0; i < arrSize - 1; i++) {
// byte[] temp = new byte[Integer.MAX_VALUE];
// tempDataBus.add(temp);
// count.add(intMaxNum);
// }
// 最后一个不用max_int,为了节省空间,所以计算出差值来创建
// byte[] temp = new byte[sizeLongNum.subtract(count).intValue()];
// tempDataBus.add(temp);
// } else {
// tempDataBus = new ArrayList<>(1);
// byte[] temp = new byte[sizeLongNum.intValue()];
// tempDataBus.add(temp);
}
BufferedInputStream br = new BufferedInputStream(zipFile.getInputStream(ze));
int readDataSize = 0;
int flag = 0;
byte[] dataPerson = null;//tempDataBus.get(flag);
if (compareValue == 1) {
dataPerson = new byte[intMaxNum.intValue()];
} else {
dataPerson = new byte[sizeLongNum.intValue()];
}
dataBus = new ArrayList<>(arrSize);
BigDecimal thisCount = BigDecimal.ZERO;
while ((readDataSize = br.read(dataPerson)) != -1) {
thisCount.add(BigDecimal.valueOf(readDataSize));
flag++;
dataBus.add(dataPerson);
int value = _getSize(sizeLongNum, thisCount, intMaxNum).intValue();
dataPerson = new byte[value];
}
logger.info("预期需要{}个数组,实际上用了{}个数组", arrSize, flag);
br.close();
} else {//longSize不大于0,也要强行读取,因为在实际测试world_merc.zip时,就出现了size<0,但是明明有数据的情况!
dataBus = new ArrayList<>();
byte[] dataPerson = new byte[1024];
dataBus.add(dataPerson);
BufferedInputStream br = new BufferedInputStream(zipFile.getInputStream(ze));
int readDataSize = 0;
int arrNum = 1;
while ((readDataSize = br.read(dataPerson)) != -1) {
arrNum += 1;
dataPerson = new byte[1024];
dataBus.add(dataPerson);
}
logger.info("用了{}个数组", arrNum);
br.close();
}
}
map.put(ze.getName(), dataBus);
}
zin.closeEntry();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
zin.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//此处返回无用,懒得修改了
return map;
}
/**
* 与生成到磁盘接口保持一致的入参,用于表明是同意逻辑开始删除所有生成的文件以及文件夹
*
* @param zpiFilePath
* @param stringListMap
*/
public static void delete(String zpiFilePath, Map<String, List<byte[]>> stringListMap) {
int lastFileSeparatorIndex = zpiFilePath.lastIndexOf(File.separator);
String zipParentPath = zpiFilePath.substring(0, lastFileSeparatorIndex);
//key为文件在zip中的相对全位置,value存储实际物理全位置
for (Map.Entry<String, List<byte[]>> entry : stringListMap.entrySet()) {
String key = entry.getKey();
String filePath = zipParentPath + File.separator + key;
File file = new File(filePath);
file.delete();
}
//最后再删除父目录
String parentPath = zpiFilePath.replace(".zip", "");
File file = new File(parentPath);
file.delete();
}
/**
* 将readZipFile解析出来的数据写到磁盘上
*
* @param zpiFilePath
* @param stringListMap
*/
public static Map<String, String> toDisk(String zpiFilePath, Map<String, List<byte[]>> stringListMap) {
int lastFileSeparatorIndex = zpiFilePath.lastIndexOf(File.separator);
String zipParentPath = zpiFilePath.substring(0, lastFileSeparatorIndex);
//key为文件在zip中的相对全位置,value存储实际物理全位置
Map<String, String> fileNameMap = new HashMap<>(stringListMap.size());
for (Map.Entry<String, List<byte[]>> entry : stringListMap.entrySet()) {
String key = entry.getKey();
String filePath = zipParentPath + File.separator + key;
fileNameMap.put(key, filePath);
// if ("I:\\tobedelete\\shapes/".equals(filePath)) {
// System.out.println("1111");
// }
File file = new File(filePath);
if (file.isDirectory()) {
if (!file.exists()) {
file.mkdirs();
}
continue;
}
if (!file.exists()) {
File parentFile = file.getParentFile();
parentFile.mkdirs();
}
try {
List<byte[]> value = entry.getValue();
if (value != null) {//'目录'的值是不对应文件的,所以目录的bytes会是null
FileOutputStream fileOutputStream = new FileOutputStream(file);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
for (byte[] bytes : value) {
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
}
bufferedOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return fileNameMap;
}
/**
* 内部函数,服务于readZipFile函数
*
* @param sizeLongNum
* @param thisCount
* @param intMaxNum
* @return
*/
private static BigDecimal _getSize(BigDecimal sizeLongNum, BigDecimal thisCount, BigDecimal intMaxNum) {
BigDecimal subtract = sizeLongNum.subtract(thisCount);
BigDecimal sizeNum = null;
if (subtract.compareTo(intMaxNum) == 1) {
sizeNum = intMaxNum;
} else {
sizeNum = subtract;
}
return sizeNum;
}
/**
* 获取zip文件大小
* 有时候会获取到的大小为-1,但是明明有数据
*
* @param path
* @return
* @throws IOException
*/
public static long getZipSize(String path) throws IOException {
long allSize = 0l;
ZipFile zipFile = new ZipFile(path);
ZipInputStream zin = null;
InputStream in = null;
in = new BufferedInputStream(new FileInputStream(path));
zin = new ZipInputStream(in);
ZipEntry ze;
while ((ze = zin.getNextEntry()) != null) {
long sizeLong = ze.getSize();
System.out.println(sizeLong);
allSize += sizeLong;
}
return allSize;
}
/**
* 获取zip文件大小 新接口
* 这个接口修复了getZipSize接口的问题
* @param path
* @return
* @throws IOException
*/
public static long getZipSize_new(String path) throws IOException {
long allSize = 0l;
ZipFile zipFile = new ZipFile(path);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry element = entries.nextElement();
if (element.isDirectory()) {
//
} else {
long size = element.getSize();
allSize += size;
System.out.println(size);
}
}
return allSize;
}
}
ShapeFileZipUtil
基于zipUtil,提供对shp数据的操作能力。调用该util时该util会调用zipUtil进行zip操作
package com.jfqqq.zip;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 目前测试通过,已经支持这样的目录:
* 同步于ZipUtil的第二点说明
*
*/
public class ShapeFileZipUtil {
/**
* 计算给定zip文件中的所有shp的最大envelope
*
* @param zpiFilePath zip文件绝对路径,包含文件名和后缀
* @return
* @throws Exception
*/
public static Envelope calculateMaxEnvelopeFromZip(String zpiFilePath) throws Exception {
Envelope envelope = new Envelope();
Map<String, List<Geometry>> geoMap = readShapeGeometries(zpiFilePath);
for (List<Geometry> value : geoMap.values()) {
for (Geometry geometry : value) {
Envelope envelopeInternal = geometry.getEnvelopeInternal();
envelope.expandToInclude(envelopeInternal);
}
}
return envelope;
}
/**
* 给定zipFile路径,然后解压出来并返回获取到的geometry
*
* @param zpiFilePath
* @return 一个map,key为相对文件名(在zip包中的相对位置全称),value为该文件中的所有geometry
* @throws Exception
*/
public static Map<String, List<Geometry>> readShapeGeometries(String zpiFilePath) throws Exception {
//读取zip文件,key为文件相对文件位置路径,value为数据字节流,考虑到数组容量,拆分为多个数组存储
Map<String, List<byte[]>> stringListMap = ZipUtil.readZipFile(zpiFilePath);
//将解析到的文件解压到磁盘上(geotools支持的是从文件中读取数据,所以只好解压到磁盘上再读取...)
Map<String, String> fileNameMap = ZipUtil.toDisk(zpiFilePath, stringListMap);
//转化为全路径list
List<String> filePaths = fileNameMap.entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toList());
//读取shape文件
Map<String, List<Geometry>> geoMap = readShapeFromFile(filePaths);
//需要删除掉解压出来的文件,别一直在那里搁着...
ZipUtil.delete(zpiFilePath, stringListMap);
return geoMap;
}
/**
* 删除文件
*
* @param filePaths
*/
private static void deleteFiles(List<String> filePaths) {
for (String filePath : filePaths) {
File file = new File(filePath);
file.delete();
}
}
/**
* 从shp文件中读取geometry数据
*
* @param filePaths shp文件在磁盘下的全路径
* @return
* @throws Exception
*/
public static Map<String, List<Geometry>> readShapeFromFile(List<String> filePaths) throws Exception {
Map<String, List<Geometry>> geoMap = new HashMap<>();
for (String filePath : filePaths) {
List<Geometry> geometries = new ArrayList<>();
if (!filePath.endsWith(".shp")) {
continue;
}
File file = new File(filePath);
Map<String, Object> map = new HashMap<>();
map.put("url", file.toURI().toURL());
DataStore dataStore = DataStoreFinder.getDataStore(map);
String typeName = dataStore.getTypeNames()[0];
FeatureSource<SimpleFeatureType, SimpleFeature> source =
dataStore.getFeatureSource(typeName);
Filter filter = Filter.INCLUDE; // ECQL.toFilter("BBOX(THE_GEOM, 10,20,30,40)")
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(filter);
try (FeatureIterator<SimpleFeature> features = collection.features()) {
while (features.hasNext()) {
SimpleFeature feature = features.next();
Object value = feature.getDefaultGeometryProperty().getValue();
if (value instanceof Geometry) {
Geometry geometry = (Geometry) value;
geometries.add(geometry);
// Envelope geomEnvelope = geometry.getEnvelopeInternal();
// envelope.expandToInclude(geomEnvelope);
}
}
}
geoMap.put(filePath, geometries);
}
return geoMap;
}
}
测试
@Test
public void testReadShapeZip() throws Exception {
System.out.println(ShapeFileZipUtil.calculateMaxEnvelopeFromZip(zpiFilePath));
}
结果