当前任务是在SpringBoot服务中.,将kml、geojson、包含shp文件的文件夹的zip文件解析为geojson字符串
kml文件和geojson文件
其中zip文件结构如图
点开同名文件夹后有如下矢量文件
之前尝试过在window上配置gdal,但是由于需要在linux上运行,配置过程中涉及到的dll文件不跨平台,于是重新尝试使用geotool工具来解析文件
com.amazonaws
aws-java-sdk
1.5.5
org.geotools
gt-geojson
24.2
org.geotools
gt-shapefile
22.3
org.geotools
gt-main
24.2
org.geotools.xsd
gt-xsd-kml
24.2
package com.tx.agriculture.util;
public class StringUtil {
//获取路径最后的文件(夹)
public static String getFileNameFromPath(String path){
int length = path.split("\\\\").length;
String s = path.split("\\\\")[length - 1];
return s;
}
public static String getTypeFromFileName(String fileName){
int i = fileName.lastIndexOf(".");
String substring = fileName.substring(i);
return substring;
}
public static void main(String[] args) {
String typeFromFileName = StringUtil.getTypeFromFileName("asdasklda.zip");
System.out.println(typeFromFileName);
String fileNameFromPath = StringUtil.getFileNameFromPath("a\\b\\aweq\\qweq\\test\\area.mkl");
System.out.println(fileNameFromPath);
}
}
涉及到的实体类
package com.tx.common.core.exception;
/**
* 错误代码枚举
*
* @author c20220052
* @date 2022/12/29
*/
public enum ErrorCodeEnum{
SUCCESS(200,"操作成功"),
PARAM_ERROR(201,"参数异常"),
PARAM_NULL(203,"参数为空"),
PARAM_FORMAT_ERROR(204,"参数格式不正确"),
PARAM_VALUE_ERROR(205,"参数值不正确"),
DUPLICATE_PRIMARY_KEY(206,"唯一键冲突"),
SYSTEM_ERROR(207,"系统异常"),
UNKNOWN_ERROR(208,"未知异常"),
AUTH_ERROR(209,"认证失败"),
NO_PERMISSION_ERROR(210,"没有权限"),
INVALID_REQUEST_ERROR(211,"无效请求"),
SERVER_ERROR(212,"第三方服务异常")
;
private int code;
private String message;
ErrorCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
package com.tx.common.core.vo;
import com.tx.common.core.exception.ErrorCodeEnum;
import java.io.Serializable;
/**
* 返回结果
*
* @author jhj
* @date 2022/12/29
*/
public class Result implements Serializable {
private Integer code;
private String msg;
private Object data;
public static Result success(){
Result result = new Result();
result.code = ErrorCodeEnum.SUCCESS.getCode();
return result;
}
public static Result success(Object data){
Result result = new Result();
result.code = ErrorCodeEnum.SUCCESS.getCode();
result.data = data;
return result;
}
public static Result failure(Integer code, String message){
Result result = new Result();
result.code = code;
result.msg = message;
return result;
}
public static Result failure(ErrorCodeEnum errorCodeEnum){
Result result = new Result();
result.code = errorCodeEnum.getCode();
result.msg = errorCodeEnum.getMessage();
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
package com.tx.agriculture.domain.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 文件 用于接收pojo文件参数
*
* @author jhj
* @date 2022/12/22
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MyFile {
private String name;
private String absulotePath;
}
import com.amazonaws.util.json.JSONArray;
import com.amazonaws.util.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.kml.KMLConfiguration;
import org.geotools.xsd.PullParser;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import java.util.Enumeration;
import java.util.zip.ZipFile;
@Slf4j
public class FileParse {
//这里添入文章提到的函数
//判断完整性
//zip文件解压
//geojson文件解析
//kml文件解析
//shp文件解析
}
在这里先不忙写代码,先整理一下大概思路。
根据需求我们需要解压、分析方法
由于涉及一些文件操作,跨操作系统会涉及到路径问题,这里我采用的是将路径写在yml配置文件中(也可以运行时获取绝对路径),总之要保证路径是正确的
此外压缩文件中的shp需要其他辅助文件(prj)才能正常读取,因此需要进行文件完整性的判定。
然后就是解析,根据目前三种文件,设计三个方法来解析,另外增加文件解压、文件删除的方法函数
base-upload-path: C:/www/changzhou/static/res
/**
* 是完整
*
* @param zipInputStream zip编码输入流
* @return {@link Map}<{@link String},{@link Object}>
* @throws IOException ioexception
*/
public static Map isComplete(ZipInputStream zipInputStream) throws IOException {
Map map = new HashMap<>();
ZipInputStream zin =zipInputStream;
//wheather there are shp/prj files
Boolean flag1= false;
Boolean flag2= false;
//遍历整个文件结构
ZipEntry ze;
String dirName="";
while((ze = zin.getNextEntry()) != null){
if(dirName.equals("")){
dirName=ze.getName();
}
if(ze.toString().endsWith("shp")){
flag1=true;
// log.info("shape file!");
}
else if(ze.toString().endsWith("prj")) {
flag2=true;
// log.info("prj file!");
}
}
zin.closeEntry();
map.put("dirName",dirName.split("/")[0]);
//如果必要文件存在缺失,返回false
if(!(flag1&&flag2)){
map.put("complete",false);
return map;
// throw new IOException();
}
map.put("complete",true);
return map;
}
/**
* 解压zip文件
*
* @param srcPath zip文件路径
* @param desPath 目标路径
*/
public static void decompression(String srcPath,String desPath){
//20MB
int BUFFER = 1024*1024*20;
String fileName = "";
try {
//BOS BIS ZE ZF
BufferedOutputStream dest = null;
BufferedInputStream is = null;
ZipEntry entry;
ZipFile zipfile = new ZipFile(srcPath);
//遍历
Enumeration dir = zipfile.entries();
while (dir.hasMoreElements()){
entry = (ZipEntry) dir.nextElement();
//
if( entry.isDirectory()){
fileName = entry.getName();
fileName = fileName.substring(0, fileName.length() - 1);
File fileObject = new File(desPath +"\\"+ fileName);
//创建文件夹
if(!fileObject.exists()){
fileObject.mkdir();
}
}
}
Enumeration e = zipfile.entries();
//解压
while (e.hasMoreElements()) {
entry = (ZipEntry) e.nextElement();
if( entry.isDirectory()){
continue;
}else{
is = new BufferedInputStream(zipfile.getInputStream(entry));
int count;
byte[] dataByte = new byte[BUFFER];
FileOutputStream fos = new FileOutputStream(desPath+entry.getName());
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = is.read(dataByte, 0, BUFFER)) != -1) {
dest.write(dataByte, 0, count);
}
dest.flush();
dest.close();
is.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* geojson文件 解析为 geojson
*
* @param path 路径
* @throws IOException ioexception
*/
public static String resolutionGeojson(String path) throws IOException {
File file = new File(path);
FileReader fileReader = new FileReader(file);
Reader reader = new InputStreamReader(new FileInputStream(file), "Utf-8");
int ch = 0;
StringBuffer sb = new StringBuffer();
while ((ch = reader.read()) != -1) {
sb.append((char) ch);
}
fileReader.close();
reader.close();
String jsonStr = sb.toString();
// System.out.printf(jsonStr);
// System.out.println(JSON.parseObject(jsonStr));
return jsonStr;
}
/**
* kml转换为geojson
*
* @param input 输入
* @return {@link StringWriter}
* @throws IOException ioexception
*/
public static String kml2Geojson(InputStream input) throws IOException {
try {
PullParser parser = new PullParser(new KMLConfiguration(), input, SimpleFeature.class);
FeatureJSON fjson = new FeatureJSON();
StringWriter sw = new StringWriter();
SimpleFeature simpleFeature = (SimpleFeature) parser.parse();
sw.append("{ \"type\": \"FeatureCollection\", \"features\": [");
while (simpleFeature != null) {
SimpleFeatureType featureType = simpleFeature.getFeatureType();
fjson.setFeatureType(featureType);
fjson.writeFeature(simpleFeature, sw);
simpleFeature = (SimpleFeature) parser.parse();
if (simpleFeature != null) {
sw.append(",");
}
}
sw.append("]}");
sw.close();
return sw.toString();
} catch (Exception e) {
log.error("KML 文件解析报错:{}", e.getMessage());
return null;
} finally {
Objects.requireNonNull(input,"KML 文件输入流为空").close();
}
}
/**
* shp转换为Geojson
* @param shpPath
* @return
*/
public static Map shape2Geojson(String shpPath){
Map map = new HashMap();
FeatureJSON fjson = new FeatureJSON();
String dir = shpPath.split("\\\\")[shpPath.split("\\\\").length-1];
shpPath = shpPath+"\\"+dir+".shp";
try{
StringBuffer sb = new StringBuffer();
//json
sb.append("{\"type\": \"FeatureCollection\",\"features\": ");
File file = new File(shpPath);
ShapefileDataStore shpDataStore = new ShapefileDataStore(file.toURL());
//设置编码
Charset charset = Charset.forName("GBK");
shpDataStore.setCharset(charset);
//
String typeName = shpDataStore.getTypeNames()[0];
SimpleFeatureSource featureSource = shpDataStore.getFeatureSource (typeName);
//
SimpleFeatureCollection result = featureSource.getFeatures();
SimpleFeatureIterator itertor = result.features();
//使用array存储json JSONObject
JSONArray array = new JSONArray();
while (itertor.hasNext())
{
SimpleFeature feature = itertor.next();
StringWriter writer = new StringWriter();
fjson.writeFeature(feature, writer);
com.amazonaws.util.json.JSONObject json = new JSONObject(writer.toString());
array.put(json);
}
itertor.close();
//写入StringBuffer
sb.append(array.toString());
sb.append("}");
// log.info(String.valueOf(sb));
//写入文件
// cm.append2File(jsonPath, sb.toString());
map.put("status", "success");
map.put("message", sb.toString());
}
catch(Exception e){
map.put("status", "failure");
map.put("message", e.getMessage());
e.printStackTrace();
}
return map;
}
/**
* 删除dir !!谨慎操作!!
*/
public static void flushDir(String desPath) {
Path path = Paths.get(desPath);
try {
Files.walkFileTree(path, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
Files.delete(file);
// log.info("删除文件: {}", file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir,
IOException exc) throws IOException {
Files.delete(dir);
log.info("文件夹被删除: {}", dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deleteFile(String path){
File file = new File(path);
file.delete();
}
至此完成了工具类的编写
package com.tx.agriculture.controller;
import cn.hutool.core.io.file.FileNameUtil;
import com.alibaba.fastjson.JSONObject;
import com.tx.agriculture.domain.entity.MyFile;
import com.tx.agriculture.service.UploadService;
import com.tx.common.core.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/upload")
public class UploadController {
@Value("${base-upload-path}")
private String baseUploadPath;
@PostMapping("/file")
public Result file(MultipartFile file) throws IOException {
Path basePath = Paths.get(baseUploadPath);
String filename = file.getOriginalFilename();
String prefix = FileNameUtil.getPrefix(filename);
String suffix = FileNameUtil.getSuffix(filename);
int i = 1;
StringBuilder sb = new StringBuilder();
Path path = basePath.resolve(filename);
while (Files.exists(path)){
filename = sb.append(prefix).append(i++).append(".").append(suffix).toString();
path = basePath.resolve(filename);
sb.setLength(0);
}
file.transferTo(path);
return Result.success(path.toUri().toString().substring(basePath.getParent().toUri().toString().length()-1));
}
@Autowired
UploadService uploadService;
/**
* 上传文件
*
* @param file 文件
* @return {@link Result}
* @throws IOException ioexception
*/
@PostMapping("/parse")
public Result parse(MultipartFile file) throws IOException {
Path basePath = Paths.get(baseUploadPath);
String filename = file.getOriginalFilename();
String prefix = FileNameUtil.getPrefix(filename);
String suffix = FileNameUtil.getSuffix(filename);
JSONObject jsonObject = new JSONObject();
StringBuilder sb = new StringBuilder();
Path path = basePath.resolve(filename);
//多文件上传
int i = 1;
while (Files.exists(path)){
filename = sb.append(prefix).append(i++).append(".").append(suffix).toString();
path = basePath.resolve(filename);
sb.setLength(0);
}
//存储到本地
file.transferTo(path);
//对文件进行解析
try{
jsonObject = uploadService.fileUpload(file, path);
}catch (Exception e){
//文件参数异常
return Result.failure(201,e.getMessage());
}
return Result.success(jsonObject);
}
/**
* 删除文件(夹)
*
* @param myFile 包含文件名
* @return {@link Result}
*/
@PostMapping("/delete")
public Result delete(@RequestBody MyFile myFile){
String name =myFile.getName();
String path =baseUploadPath+"\\"+name;
try{
uploadService.fileDelete(path);
} catch(Exception e){
return Result.failure(205,"当前文件不存在:"+name);
}
return Result.success("当前文件成功删除:"+name);
}
}
package com.tx.agriculture.service;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
public interface UploadService {
public JSONObject fileUpload(MultipartFile multipartFile, Path path) throws Exception;
public void fileDelete(String name) throws Exception;
}
package com.tx.agriculture.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.tx.agriculture.service.UploadService;
import com.tx.agriculture.util.FileParse;
import com.tx.agriculture.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.zip.ZipInputStream;
import java.nio.file.Path;
@Slf4j
@Service
public class UploadServiceImpl implements UploadService {
/**
* 文件上传
*
* @param multipartFile 多媒体文件
* @param path 目录+文件 路径
*/
@Override
public JSONObject fileUpload(MultipartFile multipartFile, Path path) throws Exception{
//最终存入数据库的字符串 geojson
String geojson="";
String name = multipartFile.getName();
//分割路径
String sep = File.separator;
String filePath=path.toString();
//需要返回当前文件名
int length = filePath.split("\\\\").length;
String actualFileName=filePath.split("\\\\")[length-1];
//multipartFile 先转为 inputStream
InputStream inputStream=multipartFile.getInputStream();
log.info("正在上传文件:"+name);
//判断 文件是否是压缩包
//文件类型
String type =filePath.substring(filePath.length()-3);
if(type.equals("zip")){
//判断文件完整性:inputStream 转为 ZipInputStream
Charset gbk = Charset.forName("gbk");
ZipInputStream zin = new ZipInputStream(inputStream,gbk);
Boolean complete = null;
Map resultMap=FileParse.isComplete(zin);
complete = (Boolean) resultMap.get("complete");
String dirName= (String) resultMap.get("dirName");
if(complete){
log.info("正在解压文件");
//解压文件
String srcPath=path.toString();
int len = srcPath.split("\\\\").length;
String zipName=srcPath.split("\\\\")[len-1];
String desPath= srcPath.substring(0,srcPath.length()-zipName.length());
FileParse.decompression(srcPath,desPath);
//解析文件为geojson
try{
//参数是zip文件 解压后的 文件路径
String dirPath =filePath.substring(0,filePath.length()-zipName.length()) + dirName;
geojson = (String) FileParse.shape2Geojson(dirPath).get("message");
}catch (Exception e){
e.printStackTrace();
}
}else {
log.info("necessary files don't exist :必要文件存在缺失...");
throw new IllegalArgumentException();
}
}
else if(type.equals("kml")){
geojson = FileParse.kml2Geojson(inputStream);
}else if (type.equals("son")){
//geojson文件
try{
geojson = FileParse.resolutionGeojson(path.toString());
}catch (Exception e){
e.printStackTrace();
log.info("geojson files are not formatted 文件格式错误:geojson...");
}
}else {
log.info("files are not supported 暂时不支持当前文件类型上传...");
throw new IllegalArgumentException();
}
log.info(geojson);
JSONObject jsonObject = (JSONObject) JSONObject.parse(geojson);
return jsonObject;
}
//删除文件
@Override
public void fileDelete(String path) throws Exception {
String fileName = StringUtil.getFileNameFromPath(path);
//删除服务器临时文件
File file = new File(path);
//文件不存在
if(!file.exists()){
throw new Exception();
}
if(file.isFile()){
FileParse.deleteFile(path);
}
else{
FileParse.flushDir(path);
}
log.info("正在删除文件:"+ fileName+"...");
}
}
注意,其中的文件路径字符串截取方式可能有些过于简单粗暴,不一定适合所有场景。有待改良
启动springboot服务后,我们进行一个简单的测试
选择上传文件
kml文件返回结果
geojson文件返回结果
这样就完成了基本的解析功能