读取shape文件的zip包并提取出数据以及计算数据

需求

给一个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));
    }

结果

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值