JAVA实现shp文件导入坐标转换并导出
通过网上查资料,自己写的一个springBoot项目。实现简单的导入、解析、转换、导出。
所需maven
其中一些下载不到的jar包
链接:https://pan.baidu.com/s/1NwZuRe6_hB2UYjJ_Un-Afg
提取码:zj85
–来自百度网盘超级会员V2的分享
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 解析shp文件-->
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.7</version>
</dependency>
<dependency>
<groupId>org.geolatte</groupId>
<artifactId>geolatte-geom</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.geolatte</groupId>
<artifactId>geolatte-geojson</artifactId>
<version>1.6.0</version>
</dependency>
<!-- shp解析-->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-shapefile-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.ejml</groupId>
<artifactId>ejml-ddense</artifactId>
<version>0.39</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/ejml-ddense-0.32.jar</systemPath>
</dependency>
<dependency>
<groupId>org.ejml</groupId>
<artifactId>ejml-core</artifactId>
<version>0.39</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/ejml-core-0.39.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-opengis</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-opengis-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-data</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-data-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-api</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-api-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-main-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-metadata</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-metadata-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-referencing-19.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<version>19.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gt-geojson-19.2.jar</systemPath>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.json.simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/json-simple-1.1.jar</systemPath>
</dependency>
<dependency>
<groupId>javax.measure</groupId>
<artifactId>jsr-275-1.0-beta</artifactId>
<version>2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/jsr-275-1.0-beta-2.jar</systemPath>
</dependency>
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId>
<version>1.13</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/jts-1.13.jar</systemPath>
</dependency>
</dependencies>
导入接口
导入.shp文件喝.shx文件
@PostMapping("coordinate")
public Response<CoordinateVO> coordinateTransformation(MultipartFile[] shpFiles){
try {
File p = new File("shape/"+shpFiles[0].getOriginalFilename());
FileUtils.copyInputStreamToFile(shpFiles[0].getInputStream(), p);
File x = new File("shape/"+shpFiles[1].getOriginalFilename());
FileUtils.copyInputStreamToFile(shpFiles[1].getInputStream(), x);
Map<String, Object> map = coordinateService.coordinateTransformation(p);
// 操作完上传的文件 需要删除在根目录下生成的文件
if (p.exists()){
p.delete();
}
if (x.exists()){
x.delete();
}
return Response.success("解析成功!",map);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
上传的文件存到指定路径,临时文件使用完后面会删除。
解析逻辑代码
首先解析.shp文件和.shx文件,代码如下。
/**
* 解析shp文件
* @param shpFile
* @return
*/
public Map<String,Object> getFileData(File shpFile){
Map<String,Object> map = new HashMap();
JSONArray jsonArray = new JSONArray();
List<String> attributeID = new LinkedList();
try {
FileDataStore store = FileDataStoreFinder.getDataStore(shpFile);
// GeoTools读取ShapeFile文件的默认编码为ISO-8859-1。而我们中文操作系统下ShapeFile
// 文件的默认编码一般为utf-8
((ShapefileDataStore) store).setCharset(Charset.forName("utf-8"));
SimpleFeatureSource featureSource = store.getFeatureSource();
// 提取出属性的 ID 值
List<AttributeDescriptor> attrList=
featureSource.getSchema().getAttributeDescriptors();
for(AttributeDescriptor attr : attrList){
String ID = attr.getName().getLocalPart();
attributeID.add(ID);
}
SimpleFeatureCollection featureCollection = featureSource.getFeatures();
SimpleFeatureIterator featureIterator = featureCollection.features();
while (featureIterator.hasNext()) {
SimpleFeature feature = featureIterator.next();
JSONObject jsonObject = new JSONObject();
List list = feature.getAttributes();
for(int k = 0; k < list.size(); k++ ){
if (list.get(k)!=null){
jsonObject.put(attributeID.get(k), list.get(k).toString());
}else{
jsonObject.put(attributeID.get(k), null);
}
}
jsonArray.add(jsonObject);
jsonObject = null;
}
featureIterator.close();
store.dispose();
} catch (IOException e) {
e.printStackTrace();
return null;
}
map.put("data", jsonArray);
map.put("shpName", attributeID);
shpFile.exists();
return map;
}
获取map中数据
获取数据放入List中:
public List<ShpDTO> getShpDTO(Map<String, Object> fileData){
Object data = fileData.get("data");
String dataStr = data.toString();
cn.hutool.json.JSONArray dataArray = JSONUtil.parseArray(dataStr);
List<ShpDTO> list = new ArrayList<>();
for (Object da : dataArray) {
ShpDTO shpDTO = new ShpDTO();
cn.hutool.json.JSONObject daObject = JSONUtil.parseObj(da);
if (!ObjectUtil.isNull(daObject.get("name"))){
String name = JSONUtil.toJsonStr(daObject.get("name"));
shpDTO.setName(name);
}
if (!ObjectUtil.isNull(daObject.get("type"))){
String type = JSONUtil.toJsonStr(daObject.get("type"));
shpDTO.setType(type);
}
if (!ObjectUtil.isNull(daObject.get("the_geom"))){
String geom = JSONUtil.toJsonStr(daObject.get("the_geom"));
WKTReader reader = new WKTReader();
try {
Geometry geometry = reader.read(geom);
shpDTO.setGeom(geometry);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
list.add(shpDTO);
}
return list;
}
坐标转换
坐标转换工具类
import com.vividsolutions.jts.geom.*;
interface IConvertePoint {
public double getX();
public double getY();
public void setX(double x);
public void setY(double y);
public IConvertePoint Copy();
}
class ConverteCoordinate extends Coordinate implements IConvertePoint {
public ConverteCoordinate(Coordinate coordinate){
super(coordinate.x,coordinate.y,coordinate.z);
}
@Override
public double getX() {
return this.x;
}
@Override
public double getY() {
return this.y;
}
@Override
public void setX(double x) {
this.x = x;
}
@Override
public void setY(double y) {
this.y = y;
}
@Override
public IConvertePoint Copy() {
return new ConverteCoordinate(this);
}
}
public class CoordinateConverte {
//偏移算纬度差
private static double transformLon(double lon, double lat) {
double ret = 300.0 + lon + 2.0 * lat + 0.1 * lon * lon + 0.1 * lon * lat + 0.1 * Math.sqrt(Math.abs(lon));
ret += (20.0 * Math.sin(6.0 * lon * Math.PI) + 20.0 * Math.sin(2.0 * lon * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lon * Math.PI) + 40.0 * Math.sin(lon / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lon / 12.0 * Math.PI) + 300.0 * Math.sin(lon * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
//偏移算经度差
private static double transformLat(double lon, double lat) {
double ret = -100.0 + 2.0 * lon + 3.0 * lat + 0.2 * lat * lat + 0.1 * lon * lat + 0.2 * Math.sqrt(Math.abs(lon));
ret += (20.0 * Math.sin(6.0 * lon * Math.PI) + 20.0 * Math.sin(2.0 * lon * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
public static IConvertePoint Converte84202(IConvertePoint input) {
double e = 0.00669342162296594323; //扁率
double a = 6378245.0; //地球半径
double x = input.getX() - 105.0;
double y = input.getY() - 35.0;
double dLat = transformLat(x, y);
double dLon = transformLon(x, y);
double radLat = input.getY() / 180.0 * Math.PI;
double magic = Math.sin(radLat);
magic = 1 - e * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) /
((a * (1 - e)) / (magic * sqrtMagic) * Math.PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
IConvertePoint pt02 = input.Copy();
pt02.setX(input.getX() + dLon + 0.0000034);
pt02.setY(input.getY() + dLat + 0.0000021);
return pt02;
}
public static IConvertePoint Converte02284(IConvertePoint input) {
IConvertePoint pt84 = input.Copy();
double dis = 32400;
IConvertePoint ptl84 = input.Copy();
//按照84202方案转02为04
IConvertePoint pt04 = Converte84202(input);
//依据04与84相对02对称计算84,该84记为l84
ptl84.setX(input.getX() * 2 - pt04.getX());
ptl84.setY(input.getY() * 2 - pt04.getY());
for (int i = 0; i < 50; i++) {
//在l84基础上偏移02,记为l02
IConvertePoint ptl02 = Converte84202(ptl84);
double dx = ptl02.getX() - input.getX();
double dy = ptl02.getY() - input.getY();
double d = dx * dx * 10000 + dy * dy * 10000;
if (d <= dis) {
//记录最接近的l84点
pt84 = ptl84.Copy();
dis = d;
if (Math.abs(dx) < 0.000000025 && Math.abs(dy) < 0.000000025) {
//若l02和输入的02差异较小,则认为l84为真实84,返回
return ptl84;
}
}
//若若l02和输入的02差异较大,则调整l84继续尝试
ptl84.setX(ptl84.getX() - dx / 2);
ptl84.setY(ptl84.getY() - dy / 2);
}
//多次移动仍未达到较小, 取最接近l84点
return pt84;
}
public static Coordinate Converte84202(Coordinate input) {
IConvertePoint point = new ConverteCoordinate(input);
return (Coordinate) Converte84202(point);
}
public static Coordinate Converte02284(Coordinate input) {
IConvertePoint point = new ConverteCoordinate(input);
return (Coordinate) Converte02284(point);
}
public static Point Converte84202(Point input) {
return input.getFactory().createPoint(Converte84202(input.getCoordinate()));
}
public static Point Converte02284(Point input) {
return input.getFactory().createPoint(Converte02284(input.getCoordinate()));
}
public static LineString Converte84202(LineString input) {
Coordinate[] coordinates = input.getCoordinates();
for (int i = 0; i < coordinates.length; i++) {
coordinates[i] = Converte84202(coordinates[i]);
}
return input.getFactory().createLineString(coordinates);
}
public static LineString Converte02284(LineString input) {
Coordinate[] coordinates = input.getCoordinates();
for (int i = 0; i < coordinates.length; i++) {
coordinates[i] = Converte02284(coordinates[i]);
}
return input.getFactory().createLineString(coordinates);
}
public static Polygon Converte84202(Polygon input) {
Coordinate[] coordinates = input.getCoordinates();
for (int i = 0; i < coordinates.length; i++) {
coordinates[i] = Converte84202(coordinates[i]);
}
return input.getFactory().createPolygon(coordinates);
}
public static Polygon Converte02284(Polygon input) {
Coordinate[] coordinates = input.getCoordinates();
for (int i = 0; i < coordinates.length; i++) {
coordinates[i] = Converte02284(coordinates[i]);
}
return input.getFactory().createPolygon(coordinates);
}
public static MultiPolygon Converte84202(MultiPolygon input) {
int n = input.getNumGeometries();
Polygon polygons[] = new Polygon[n];
for (int i = 0; i < n; i++) {
polygons[i] = Converte84202((Polygon) input.getGeometryN(i));
}
return input.getFactory().createMultiPolygon(polygons);
}
public static MultiPolygon Converte02284(MultiPolygon input) {
int n = input.getNumGeometries();
Polygon polygons[] = new Polygon[n];
for (int i = 0; i < n; i++) {
polygons[i] = Converte02284((Polygon) input.getGeometryN(i));
}
return input.getFactory().createMultiPolygon(polygons);
}
public static Geometry Converte84202(Geometry input) {
if (input instanceof Point){
return Converte84202((Point) input);
}
if (input instanceof LineString){
return Converte84202((LineString) input);
}
if (input instanceof Polygon){
return Converte84202((Polygon) input);
}
if (input instanceof MultiPolygon){
return Converte84202((MultiPolygon) input);
}
return null;
}
public static Geometry Converte02284(Geometry input) {
if (input instanceof Point){
return Converte02284((Point) input);
}
if (input instanceof LineString){
return Converte02284((LineString) input);
}
if (input instanceof Polygon){
return Converte02284((Polygon) input);
}
if (input instanceof MultiPolygon){
return Converte02284((MultiPolygon) input);
}
return null;
}
}
生成shape相关文件
/**
* 生成shape文件
*
* @param shpPath 生成shape文件路径(包含文件名称)
* @param encode 编码
* @param geoType 图幅类型,Point和Rolygon
* @param geoms 图幅集合
*/
public static void write2Shape(String shpPath, String encode, String geoType, List<Geometry> geoms) {
try {
//创建shape文件对象
File file = new File(shpPath);
Map<String, Serializable> params = new HashMap<>();
params.put(ShapefileDataStoreFactory.URLP.key, file.toURI().toURL());
ShapefileDataStore ds = (ShapefileDataStore) new ShapefileDataStoreFactory().createNewDataStore(params);
//定义图形信息和属性信息
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setCRS(DefaultGeographicCRS.WGS84);
tb.setName("shapefile");
if ("Polygon".equals(geoType)) {
tb.add("the_geom", Polygon.class);
} else if ("MultiPolygon".equals(geoType)) {
tb.add("the_geom", MultiPolygon.class);
} else if ("Point".equals(geoType)) {
tb.add("the_geom", Point.class);
} else if ("MultiPoint".equals(geoType)) {
tb.add("the_geom", MultiPoint.class);
} else if ("LineString".equals(geoType)) {
tb.add("the_geom", LineString.class);
} else if ("MultiLineString".equals(geoType)) {
tb.add("the_geom", MultiLineString.class);
} else {
throw new Exception("Geometry中没有该类型:" + geoType);
}
ds.createSchema(tb.buildFeatureType());
//设置编码
Charset charset = Charset.forName(encode);
ds.setCharset(charset);
//设置Writer
FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriter(ds.getTypeNames()[0], Transaction.AUTO_COMMIT);
for (Geometry geom : geoms) {
//String type = geom.getGeometryType();
//写下一条
SimpleFeature feature = writer.next();
feature.setAttribute("the_geom", geom);
}
writer.write();
writer.close();
ds.dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
文件进行压缩
/**
* 压缩shape文件
*
* @param shpPath shape文件路径(包含shape文件名称)
*/
public static void zipShapeFile(String shpPath) {
try {
File shpFile = new File(shpPath);
String shpRoot = shpFile.getParentFile().getPath();
String shpName = shpFile.getName().substring(0, shpFile.getName().lastIndexOf("."));
String zipPath = shpRoot + File.separator + shpName + ".zip";
File zipFile = new File(zipPath);
InputStream input = null;
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
// zip的名称为
zipOut.setComment(shpName);
String[] shpFiles = new String[]{
shpRoot + File.separator + shpName + ".dbf",
shpRoot + File.separator + shpName + ".prj",
shpRoot + File.separator + shpName + ".shp",
shpRoot + File.separator + shpName + ".shx",
shpRoot + File.separator + shpName + ".fix"
};
for (int i = 0; i < shpFiles.length; i++) {
File file = new File(shpFiles[i]);
input = new FileInputStream(file);
zipOut.putNextEntry(new ZipEntry(file.getName()));
int temp = 0;
while ((temp = input.read()) != -1) {
zipOut.write(temp);
}
input.close();
}
zipOut.close();
} catch (Exception e) {
e.printStackTrace();
}
}
删除临时文件
/**
* 删除临时文件
* @param path
*/
public void deleteFile(String path){
String dbf = path.replace(".shp", ".dbf");
String fix = path.replace(".shp", ".fix");
String prj = path.replace(".shp", ".prj");
String shx = path.replace(".shp", ".shx");
File dbfFile = new File(dbf);
File fixFile = new File(fix);
File prjFile = new File(prj);
File shxFile = new File(shx);
File shpFile = new File(path);
if (dbfFile.exists()){
dbfFile.delete();
}
if (fixFile.exists()){
fixFile.delete();
}
if (prjFile.exists()){
prjFile.delete();
}
if (shxFile.exists()){
shxFile.delete();
}
if (shpFile.exists()){
shpFile.delete();
}
}
下载生成的压缩包
接口:
@GetMapping("downLoad")
public Response downLoad(HttpServletResponse response, DownLoadParam param)
逻辑代码:
下载完删除压缩包
/**
* 文件下载
* @param response
* @param param
* @return
*/
@Override
public void downLoad(HttpServletResponse response, DownLoadParam param) {
if (StrUtil.isEmpty(param.getUrl()) || StrUtil.isEmpty(param.getPathName())){
return;
}
try {
File zipFile = new File(param.getUrl());
InputStream fis = new BufferedInputStream(new FileInputStream(zipFile));
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
response.reset();
response.setContentType("application/octet-stream;charset=UTF-8");
String fileName = new String(param.getPathName().getBytes("gb2312"), "iso8859-1");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
OutputStream ouputStream = response.getOutputStream();
ouputStream.write(buffer);
ouputStream.flush();
ouputStream.close();
if (zipFile.exists()){
zipFile.delete();
}
return;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
前端相关代码
简单的form表单导入,a标签下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XMLHttpRequest上传文件</title>
<style>
#home{
text-align:center;
}
#download{
display: none;
}
#a{
display: none;
}
#btn_add_file{
margin-right: 630px;
}
</style>
<script src="./js/jquery-3.6.1.js"></script>
<script>
var url = '';
var pathName = '';
var http = window.location.protocol;
var host = window.location.host;
var local = http+'//'+host+'/';
function commit(){
console.log('123');
console.log(local);
const files1 = document.getElementById('files1').files;
const files2 = document.getElementById('files2').files;
var formData = new FormData();
formData.append("shpFiles",files1[0]);
formData.append("shpFiles",files2[0]);
$.ajax({
type: 'POST',
url:'http://localhost:19110/shpParse/coordinate',
processData: false, //是否把上传的数据 处理为对象 默认为true
contentType: false, //避免让jquery 设置请求头有可能会破坏分隔符。 而使服务器不能正常解析文件
data:formData,
success: function (data) {
url = data.data.url;
pathName = data.data.pathName;
alert(data.msg);
document.getElementById("download").style.display="inline";//显示
}
})
}
function down(){
var path = local+"shpParse/downLoad?"+"url=shape/"+pathName+"&"+"pathName="+pathName;
console.log(path);
document.getElementById("a").style.display="inline";
$("#a").attr("href",path);
}
</script>
</head>
<body>
<div id="home">
<form id="fileForm">
<span>选择.shp文件 </span><input type="file" name="files" multiple="multiple" id="files1" data-buttonText="选择.shp文件">
<span>选择.shx文件 </span><input type="file" name="files" multiple="multiple" id="files2" data-buttonText="选择.shx文件">
</form>
<br>
<br>
<br>
<input type="button" name="button" id="btn_add_file" value="上传文件" onclick="commit()">
<div id="download">
<div>下载文件</div>
<button id="but" onclick="down()">获取下载链接 </button>
<a id="a" href="">点击下载</a>
</div>
<br>
<div>
<p>使用说明:</p>
<p>上传文件.shp和.shx文件必须同时上传。</p>
<p>解析完成可以获取下载链接。</p>
<p>点击获取到的下载链接即可下载.zip文件。</p>
</div>
</div>
</body>
</html>
大佬发现哪里不合理希望指出来,感谢了。小弟先给拜一个!!!