geotools请参考:geotools实现插入,更新,查询
包结构
采用技术:
dom4j,ThreadPoolTaskExecutor线程池,RestTemplate,guava retry,wfs协议,ogc协议,btoa加密,策略模式
通过geoserver来修改postgresql地理数据库,这样就不需要每次更新数据后,人为手动更新geoserver了
相关配置
相关maven
这里只贴了关键的maven
<repositories>
<repository>
<id>osgeo</id>
<name>osgeo Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>25.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.geotools/gt-shapefile -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>25.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.geotools.jdbc/gt-jdbc-postgis -->
<dependency>
<groupId>org.geotools.jdbc</groupId>
<artifactId>gt-jdbc-postgis</artifactId>
<version>25.2</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>25.2</version>
</dependency>
<dependency>
<groupId>it.geosolutions</groupId>
<artifactId>geoserver-manager</artifactId>
<version>1.7.0</version>
</dependency>
<!--重试机制-->
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
retry
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* @author qinlei
* @description todo
* @date 2021/10/26 9:55
*/
@Configuration
public class RetryConfig {
@Bean
public Retryer<Boolean> instance(){
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
//返回false也需要重试
.retryIfResult(Predicates.equalTo(false))
//抛出runtime异常,checked异常时都会重试,但是抛出error不会重试
.retryIfException()
//重试策略
.withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
//尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
return retryer;
}
}
restTemplate
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* 优雅的http请求方式RestTemplate
* @Return:
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//ms
factory.setConnectTimeout(15000);//ms
return factory;
}
}
ThreadPoolExecutor配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author qinlei
* @description 线程池配置
* @date 2021/4/14 11:35
*/
@Configuration
public class ThreadsPoolsConfig {
@Bean
public void initThreadPool(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(1);
//配置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(100);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-service-");
//线程空闲后的最大存活时间
executor.setKeepAliveSeconds(10);
executor.setAllowCoreThreadTimeOut(true);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
}
}
util
btoa加密
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 16:03
*/
public class BtoaEncode {
//btoa加密方法
public static String base64hash = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
str:要匹配的数据
reg:正则匹配的规则
*/
public static boolean isMatcher(String str,String reg){
//编译成一个正则表达式模式
Pattern pattern = Pattern.compile(reg);
//匹配模式
Matcher matcher = pattern.matcher(str);
if(matcher.matches()){
return true;
}
return false;
}
public static String botaEncodePassword(String pwd){
if(pwd==null||isMatcher(pwd,"([^\\u0000-\\u00ff])")){
throw new Error("INVALID_CHARACTER_ERR");
}
int i = 0,prev=0,ascii,mod=0;
StringBuilder result = new StringBuilder();
while (i<pwd.length()){
ascii = pwd.charAt(i);
mod = i%3;
switch (mod){
//第一个6位只需要让8位二进制右移两位
case 0:
result.append(base64hash.charAt(ascii>>2));
break;
//第二个6位=第一个8位的后两位+第二个八位的前四位
case 1:
result.append(base64hash.charAt((prev&3)<<4|(ascii>>4)));
break;
//第三个6位=第二个8位的后四位+第三个8位的前两位
//第四个6位 = 第三个8位的后6位
case 2:
result.append(base64hash.charAt((prev & 0x0f)<<2|(ascii>>6)));
result.append(base64hash.charAt(ascii&0x3f));
break;
}
prev = ascii;
i++;
}
//循环结束后看mod, 为0 证明需补3个6位,第一个为最后一个8位的最后两位后面补4个0。另外两个6位对应的是异常的“=”;
// mod为1,证明还需补两个6位,一个是最后一个8位的后4位补两个0,另一个对应异常的“=”
if(mod == 0){
result.append(base64hash.charAt((prev&3)<<4));
result.append("==");
}else if(mod == 1){
result.append(base64hash.charAt((prev&0x0f)<<2));
result.append("=");
}
return result.toString();
}
}
xml转换相关工具类
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.util.List;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 23:21
*/
public class XMLUtils {
/**
* 将xml转换为JSON对象
*
* @param xml
* xml字符串
* @return
* @throws Exception
*/
public static JSONObject xmltoJson(String xml) throws Exception {
JSONObject jsonObject = new JSONObject();
Document document = DocumentHelper.parseText(xml);
// 获取根节点元素对象
Element root = document.getRootElement();
iterateNodes(root, jsonObject);
return jsonObject;
}
/**
* 遍历元素
*
* @param node
* 元素
* @param json
* 将元素遍历完成之后放的JSON对象
*/
@SuppressWarnings("unchecked")
public static void iterateNodes(Element node, JSONObject json) {
// 获取当前元素的名称
String nodeName = node.getName();
// 判断已遍历的JSON中是否已经有了该元素的名称
if (json.containsKey(nodeName)) {
// 该元素在同级下有多个
Object Object = json.get(nodeName);
JSONArray array = null;
if (Object instanceof JSONArray) {
array = (JSONArray) Object;
} else {
array = new JSONArray();
array.add(Object);
}
// 获取该元素下所有子元素
List<Element> listElement = node.elements();
if (listElement.isEmpty()) {
// 该元素无子元素,获取元素的值
String nodeValue = node.getTextTrim();
array.add(nodeValue);
json.put(nodeName, array);
return;
}
// 有子元素
JSONObject newJson = new JSONObject();
// 遍历所有子元素
for (Element e : listElement) {
// 递归
iterateNodes(e, newJson);
}
array.add(newJson);
json.put(nodeName, array);
return;
}
// 该元素同级下第一次遍历
// 获取该元素下所有子元素
List<Element> listElement = node.elements();
if (listElement.isEmpty()) {
// 该元素无子元素,获取元素的值
String nodeValue = node.getTextTrim();
json.put(nodeName, nodeValue);
return;
}
// 有子节点,新建一个JSONObject来存储该节点下子节点的值
JSONObject object = new JSONObject();
// 遍历所有一级子节点
for (Element e : listElement) {
// 递归
iterateNodes(e, object);
}
json.put(nodeName, object);
return;
}
}
wfs-t 核心操作
WFSTXmlGenerate
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.jeecg.modules.wfs.exception.GeoXmlQlException;
import java.util.List;
import java.util.Map;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 16:44
*/
public abstract class WFSTXmlGenerate {
public static final String INSERT_METHOD = "Insert";
public static final String UPDATE_METHOD = "Update";
public static final String DELETE_METHOD = "Delete";
private Document document;
public Document getDocument() {
return document;
}
public WFSTXmlGenerate() {
document = DocumentHelper.createDocument();
Element wfs = document.addElement("wfs:Transaction");
wfs.addAttribute("version", "1.0.0");
wfs.addAttribute("service", "WFS");
wfs.addNamespace("ogc", "http://www.opengis.net/ogc");
wfs.addNamespace("wfs", "http://www.opengis.net/wfs");
wfs.addNamespace("gml", "http://www.opengis.net/gml");
wfs.addNamespace("xsi", "http://www.opengis.net/xsi");
wfs.addAttribute("xsi:schemaLocation", "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd");
}
/**
* 生成xml方法
*/
public abstract Document createXml(String businessId,String username, String typeName, String wfsPath, Map<String, Object> variables, Map<String, Object> params)throws GeoXmlQlException;
/**
* 创建k-v的节点
*
* @param property
* @param k
* @param v geom时,该v为jsonObject
* @param workspace 工作空间(geom需要 其余设置为空)
*/
void createKVNode(Element property, String k, Object v, String workspace) throws GeoXmlQlException {
if (StringUtils.isBlank(workspace)) {
Element key = property.addElement("wfs:Name");
key.setText(k);
Element value = property.addElement("wfs:Value");
if (v != null)
value.setText(v.toString());
return;
}
Element element = property.addElement(workspace + ":" + k);
if(v==null)
return;
JSONObject valueJson = JSON.parseObject(JSONObject.toJSONString(v));
String type = valueJson.getString("type");
if("general".equals(type) && v!=null){
element.setText(valueJson.getString("value"));
}else if("MultiLineString".equals(type)){
String coordinates = valueJson.getString("coordinates");
this.createMultiLineEle(element,type,coordinates);
}else if("Point".equals(type)){
String coordinates = valueJson.getString("coordinates");
this.createPointEle(element,type,coordinates);
}else{
}
}
private void createMultiLineEle(Element element,String type,String coordinates){
Element multiLineString = createGeomEle(element,type);
Element lineStringMember = multiLineString.addElement("gml:lineStringMember");
Element LineString = lineStringMember.addElement("gml:LineString");
createCoorEle(LineString,coordinates);
}
private void createPointEle(Element element,String type,String coordinates){
Element point = createGeomEle(element,type);
createCoorEle(point,coordinates);
}
private Element createGeomEle(Element element, String type){
Element point = element.addElement("gml:" + type);
point.addAttribute("srsName", "http://www.opengis.net/gml/srs/epsg.xml#4326");
return point;
}
private void createCoorEle(Element toPoint,String coordinates){
Element coor = toPoint.addElement("gml:coordinates");
coor.addAttribute("decimal", ".");
coor.addAttribute("cs", ",");
coor.addAttribute("ts", ";");
coor.setText(coordinates);
}
public void createUpdateNode(Element element, String typeName, Map<String,Object> variables, Map<String,Object> params) throws GeoXmlQlException {
Element updateNode = element.addElement("wfs:"+UPDATE_METHOD);
updateNode.addAttribute("typeName",typeName);
for (Map.Entry<String, Object> entry : variables.entrySet()) {
Element property = updateNode.addElement("wfs:Property");
this.createKVNode(property,entry.getKey(),entry.getValue(),null);
}
createFilterNode(updateNode,typeName.split(":")[0],params);
}
public void createInsertNode(String wfsPath,Element element, String typeName, Map<String,Object> variables) throws GeoXmlQlException {
String[] typeSplit = typeName.split(":");
String workspace = typeSplit[0];
element.addNamespace(workspace,"http://"+workspace);
element.addAttribute("xsi:schemaLocation",element.attributeValue("xsi:schemaLocation")+" http://"+workspace+" "
+ wfsPath+"/DescribeFeatureType?typename="+typeName);
Element insertNode = element.addElement("wfs:"+INSERT_METHOD);
Element property = insertNode.addElement(typeName);
for (Map.Entry<String, Object> entry : variables.entrySet()) {
this.createKVNode(property,entry.getKey(),entry.getValue(),workspace);
}
// createFilterNode(element,workspace,params);
}
public void createDeleteNode(Element element, String typeName, Map<String, Object> variables, Map<String, Object> params) throws GeoXmlQlException {
Element deleteNode = element.addElement("wfs:" + DELETE_METHOD);
deleteNode.addAttribute("typeName", typeName);
createFilterNode(deleteNode,typeName.split(":")[0], params);
}
/**
* 创建fid过滤器节点
* 为了安全性考虑 fids不允许非空 目前只提供id的过滤
*
* @param element
* @param params
*/
void createFilterNode(Element element,String workspace, Map<String, Object> params) throws GeoXmlQlException {
if (params == null || params.isEmpty()) {
return;
}
Element filter = element.addElement("ogc:Filter");
for (Map.Entry<String, Object> objectEntry : params.entrySet()) {
Element property = filter.addElement("ogc:PropertyIsEqualTo");
Element nameEle = property.addElement("ogc:PropertyName");
nameEle.setText(workspace+":"+objectEntry.getKey());
Element literalEle = property.addElement("ogc:Literal");
literalEle.setText(objectEntry.getValue() == null ? null : objectEntry.getValue().toString());
}
}
}
插入操作实现类
import org.dom4j.Document;
import java.util.Map;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 18:05
*/
public class InsertWfsXmlGenerate extends WFSTXmlGenerate{
public InsertWfsXmlGenerate() {
super();
}
@Override
public Document createXml(String businessId,String updateUser,String typeName,String wfsPath, Map<String, Object> variables, Map<String, Object> params) {
createInsertNode(wfsPath,this.getDocument().getRootElement(),typeName,variables);
return this.getDocument();
}
}
修改编辑操作实现类
import org.dom4j.Document;
import org.jeecg.modules.wfs.exception.GeoXmlQlException;
import java.util.Map;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 18:05
*/
public class UpdateWfsXmlGenerate extends WFSTXmlGenerate {
public UpdateWfsXmlGenerate() {
super();
}
@Override
public Document createXml(String businessId,String updateUser,String typeName,String wfsPath, Map<String, Object> variables, Map<String, Object> params) throws GeoXmlQlException{
this.createUpdateNode(this.getDocument().getRootElement(),typeName,variables,params);
return this.getDocument();
}
}
删除操作实现类
import org.dom4j.Document;
import java.util.Map;
/**
* @author qinlei
* @description todo
* @date 2021/10/21 18:05
*/
class DeleteWfsXmlGenerate extends WFSTXmlGenerate {
public DeleteWfsXmlGenerate() {
super();
}
@Override
public Document createXml(String businessId, String updateUser, String typeName, String wfsPath, Map<String, Object> variables, Map<String, Object> params) {
this.createDeleteNode(this.getDocument().getRootElement(), typeName, variables, params);
return this.getDocument();
}
}
插入加修改一个事务操作实现类
import org.dom4j.Document;
import org.jeecg.common.util.DateUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @author qinlei
* @description todo
* @date 2021/10/25 11:33
*/
public class InsertUpdateWfsXmlGenerate extends WFSTXmlGenerate{
public InsertUpdateWfsXmlGenerate() {
super();
}
@Override
public Document createXml(String businessId,String username,String typeName,String wfsPath, Map<String, Object> variables, Map<String, Object> params) {
Map<String, Object> vars = new HashMap<>();
vars.put("isvalid","0");
vars.put("uploader",username);
vars.put("uploadtime",DateUtils.getTimestamp());
createUpdateNode(this.getDocument().getRootElement(),typeName,vars,params);
createGeneralParams(variables,"isvalid","1");
createGeneralParams(variables,"surveyor",username);
createGeneralParams(variables,"surveydate", DateUtils.formatDate());
createGeneralParams(variables,"index_id",businessId);
createInsertNode(wfsPath,this.getDocument().getRootElement(),typeName,variables);
return this.getDocument();
}
private void createGeneralParams(Map<String, Object> variables,String key,Object param){
Map<String, Object> map = new HashMap<>();
map.put("type","general");
map.put("value",param);
variables.put(key,map);
}
}
异常类
/**
* @author qinlei
* @description geoserver专用异常类
* @date 2021/10/21 17:56
*/
public class GeoXmlQlException extends RuntimeException{
private static final long serialVersionUID = 1L;
public GeoXmlQlException(String message){
super(message);
}
public GeoXmlQlException(Throwable cause)
{
super(cause);
}
public GeoXmlQlException(String message, Throwable cause)
{
super(message,cause);
}
}
GEOTools封装工具
import com.alibaba.fastjson.JSONObject;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.geotools.data.*;
import org.geotools.data.postgis.PostgisNGDataStoreFactory;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.jeecg.common.util.RestUtil;
import org.jeecg.modules.wfs.exception.GeoXmlQlException;
import org.jeecg.modules.wfs.xmlGenerate.service.InsertUpdateWfsXmlGenerate;
import org.jeecg.modules.wfs.xmlGenerate.util.BtoaEncode;
import org.jeecg.modules.wfs.xmlGenerate.service.WFSTXmlGenerate;
import org.jeecg.modules.wfs.xmlGenerate.util.XMLUtils;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ExecutionException;
@Slf4j
@Service
public class GeoToolsManager {
public static final int CREATE = 0;
public static final int APPEND = 1;
public static final int OVERWRITE = 2;
private static CoordinateReferenceSystem crs4490;
@Autowired
private Retryer<Boolean> retryer;
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public GeoToolsManager() {
try {
crs4490 = CRS.decode("EPSG:4490");
} catch (FactoryException e) {
e.printStackTrace();
}
}
/**
* 根据数据库连接参数,获取数据源
*
* @return
* @throws IOException
*/
private DataStore getPostgisDataStore(String host, String port, String user, String password, String database, String schema) throws IOException {
Map<String, Object> params = new HashMap<>();
params.put("dbtype", "postgis");
params.put("host", host);
params.put("port", port);
params.put("schema", schema);
params.put("database", database);
params.put("user", user);
params.put("passwd", password);
params.put(PostgisNGDataStoreFactory.LOOSEBBOX.key, true);
/**
* 开启下面后测试数据(7000+要素)由原来的19秒加快到13秒
*/
params.put(PostgisNGDataStoreFactory.PREPARED_STATEMENTS.key, true);
System.out.println("连接数据库");
return DataStoreFinder.getDataStore(params);
}
public boolean shp2postgis(String shppath, String host, String port, String user, String password, String database, String schema, String tablename, String encode, int option) {
// 首先尝试连接数据库
DataStore pgDatastore = null;
try {
pgDatastore = getPostgisDataStore(host, port, user, password, database, schema);
} catch (IOException e) {
e.printStackTrace();
System.err.println("连接数据库失败");
return false;
}
// 尝试读取shp
ShapefileDataStore shpDataStore = null;
try {
shpDataStore = createShpDataStore(shppath, encode);
} catch (IOException e) {
e.printStackTrace();
System.err.println("读取shp失败");
return false;
} catch (FactoryException e) {
e.printStackTrace();
System.err.println("读取shp失败");
return false;
}
// 写入数据
return writePostGIS(pgDatastore, shpDataStore, tablename, option);
}
/**
* 根据shp文件创建数据源
*
* @param shppath
* @param encode
* @return
* @throws IOException
* @throws FactoryException
*/
private ShapefileDataStore createShpDataStore(String shppath, String encode) throws IOException, FactoryException {
ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(shppath).toURI().toURL());
// 根据用户指定的字符集编码设置解析编码
shapefileDataStore.setCharset(Charset.forName(encode));
// 如果没有指定坐标系,则强制使用4490
CoordinateReferenceSystem crs = shapefileDataStore.getSchema().getCoordinateReferenceSystem();
if (null == crs) {
crs = crs4490;
shapefileDataStore.forceSchemaCRS(crs);
}
// 根据数据源获取数据类型
return shapefileDataStore;
}
/**
* 根据数据源获取要素集合的迭代器
*
* @param dataStore
* @return
* @throws IOException
*/
private FeatureIterator getIterator(ShapefileDataStore dataStore) throws IOException {
// 获取shp数据源
FeatureSource featureSource = dataStore.getFeatureSource();
FeatureCollection featureCollection = featureSource.getFeatures();
return featureCollection.features();
}
/**
* 根据一个shp要素类型创建一个新的要素类型
*
* @param tablename
* @param shpfeaturetype
* @return
*/
private SimpleFeatureType createPostGISType(String tablename, SimpleFeatureType shpfeaturetype) {
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
// 这些需要设置下默认空间字段,否则不会识别
typeBuilder.setDefaultGeometry("geom");
// 这里设置一下名称,当shp文件名是大写时需要换成小写
typeBuilder.setName(tablename);
// 设置坐标系,单独设置没有起到作用
CoordinateReferenceSystem crs = shpfeaturetype.getCoordinateReferenceSystem();
typeBuilder.setCRS(crs);
// 新创建一个属性描述集合
List<AttributeDescriptor> attributeDescriptors = new ArrayList<>();
for (AttributeDescriptor attributeDescriptor : shpfeaturetype.getAttributeDescriptors()) {
//属性构造器
AttributeTypeBuilder build = new AttributeTypeBuilder();
build.init(attributeDescriptor.getType());
build.setNillable(true);
//获取字段名,改为小写
String name = StringUtils.isNotEmpty(attributeDescriptor.getLocalName()) ? attributeDescriptor.getLocalName().toLowerCase() : attributeDescriptor.getLocalName();
if (attributeDescriptor instanceof GeometryDescriptor) {
//修改空间字段名
name = "geom";
}
//设置字段名
build.setName(name);
//创建新的属性类
AttributeDescriptor descriptor = build.buildDescriptor(name, attributeDescriptor.getType());
attributeDescriptors.add(descriptor);
}
typeBuilder.addAll(attributeDescriptors);
return typeBuilder.buildFeatureType();
}
/**
* 将shp数据源写入postgis数据源
*
* @param pgDatastore
* @param shpDataStore
* @param tablename
* @param option
* @return
* @throws IOException
*/
private boolean writePostGIS(DataStore pgDatastore, ShapefileDataStore shpDataStore, String tablename, int option) {
SimpleFeatureType shpfeaturetype = null;
try {
shpfeaturetype = shpDataStore.getSchema();
} catch (IOException e) {
e.printStackTrace();
System.err.println("shp读取失败");
return false;
}
// 原shp数据源的迭代器
FeatureIterator iterator = null;
try {
iterator = getIterator(shpDataStore);
} catch (IOException e) {
e.printStackTrace();
System.err.println("shp读取失败");
return false;
}
// 新建一个postgis要素类型构造器
SimpleFeatureType postGISType = this.createPostGISType(tablename, shpfeaturetype);
// 新建一个事务
Transaction transaction = new DefaultTransaction();
// 新建一个要素写对象
FeatureWriter<SimpleFeatureType, SimpleFeature> featureWriter = null;
try {
// 表操作
switch (option) {
case CREATE:
pgDatastore.createSchema(postGISType);
System.out.println("postgis创建数据表成功");
break;
case APPEND:
break;
case OVERWRITE:
pgDatastore.removeSchema(tablename);
System.out.println("postgis删除数据表成功");
pgDatastore.createSchema(postGISType);
System.out.println("postgis创建数据表成功");
break;
default:
System.err.println("非法操作");
return false;
}
// 要素操作
featureWriter = pgDatastore.getFeatureWriterAppend(tablename, transaction);
System.out.println("开始写入");
Date startTime = new Date();
// 定义循环用到的变量
Feature feature = null;
SimpleFeature simpleFeature = null;
Collection<Property> properties = null;
Property property = null;
String pname = null;
Object pvalue = null;
while (iterator.hasNext()) {
feature = iterator.next();
simpleFeature = featureWriter.next();
properties = feature.getProperties();
Iterator<Property> propertyIterator = properties.iterator();
while (propertyIterator.hasNext()) {
property = propertyIterator.next();
pname = property.getName().toString().toLowerCase();
if ("the_geom".equals(pname)) {
pname = "geom";
}
// 如果值是空字符串则换成Null,表示无值
pvalue = property.getValue();
if (null == pvalue || (null != pvalue && StringUtils.isEmpty(property.getValue().toString()))) {
pvalue = null;
}
simpleFeature.setAttribute(pname, pvalue);
}
featureWriter.write();
}
// 最后提交事务,可以加快导入速度
transaction.commit();
Date endTime = new Date();
System.out.println("写入结束,共耗时:" + (endTime.getTime() - startTime.getTime()));
System.out.println("shp导入postgis成功");
return true;
} catch (IOException e) {
try {
transaction.rollback();
} catch (IOException ioException) {
ioException.printStackTrace();
}
return false;
} finally {
try {
transaction.close();
} catch (IOException e) {
e.printStackTrace();
}
if (null != iterator) {
iterator.close();
}
if (null != featureWriter) {
try {
featureWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
shpDataStore.dispose();
pgDatastore.dispose();
}
}
private HttpHeaders createAuthorHeader(String username, String password) {
HttpHeaders headers = RestUtil.getHeaderApplicationJson();
headers.add("Authorization", "Basic " + BtoaEncode.botaEncodePassword(username + ":" + password));
return headers;
}
@Value("${geoserver.wfs.error.path}")
private String path;
private String xmlString = null;
/**
*
* @param path 输出的地址
* @param content 写入的内容
*/
private void writeFile2Disk(String path,String content){
File file = new File(path);
if (!file.getParentFile().exists()) {
boolean result = file.getParentFile().mkdirs();
}
FileOutputStream fos= null;
OutputStreamWriter osw= null;
BufferedWriter bw= null;
try {
fos = new FileOutputStream(file);
osw = new OutputStreamWriter(fos, "UTF-8");
bw = new BufferedWriter(osw);
bw.write(content);
} catch (Exception e) {
e.printStackTrace();
log.error("=====> 写入error xml 失败",e);
} finally {
//注意关闭的先后顺序,先打开的后关闭,后打开的先关闭
try {
if(bw!=null){
bw.close();
}
if(osw!=null){
osw.close();
}
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
log.error("file流关闭失败,",e);
}
}
}
/**
*
* @param ip
* @param port
* @param username
* @param password
* @param typeName
* @param variables 要插入的参数
* @param params 要进行修改的数据唯一编号
* @return
* @throws Exception
*/
public void insertUpdateByGeoWfs(String businessId,String updateUser,String ip, String port, String username, String password, String typeName, JSONObject variables, Map<String, Object> params){
taskExecutor.execute(()->{
try {
retryer.call(()->{
try {
this.send2GeoWfs(businessId,updateUser,ip, port, username, password, typeName, variables, params, InsertUpdateWfsXmlGenerate.class);
//成功则释放xmlString
this.xmlString = null;
return true;
}catch (GeoXmlQlException e){
log.error("请求失败,{}",e);
return false;
}
});
} catch (ExecutionException e) {
e.printStackTrace();
this.writeFile2Disk(this.path,this.xmlString);
log.error("线程错误",e);
} catch (RetryException e) {
e.printStackTrace();
log.error("重试错误",e);
this.writeFile2Disk(this.path,this.xmlString);
}
});
}
private JSONObject send2GeoWfs(String businessId,String updateUser,String ip, String port, String username, String password, String typeName, JSONObject variables, Map<String, Object> params, Class<? extends WFSTXmlGenerate> c) throws Exception {
HttpHeaders headers = this.createAuthorHeader(username, password);
WFSTXmlGenerate generate = c.newInstance();
Document document = generate.createXml(businessId,updateUser,typeName, "http://" + ip + ":" + port + "/geoserver/wfs", variables, params);
log.info(document.asXML());
this.xmlString = document.asXML();
ResponseEntity<String> response = RestUtil.request("http://" + ip + ":" + port + "/geoserver/wfs", HttpMethod.POST, headers, null, document.asXML(), String.class);
if(response.getStatusCode().is4xxClientError()){
throw new GeoXmlQlException("host无法请求!");
}
if(response.getStatusCode().is5xxServerError()){
throw new GeoXmlQlException("系统错误!");
}
if(response.getStatusCode().is2xxSuccessful() && response.getBody().contains("SUCCESS")){
return XMLUtils.xmltoJson(response.getBody());
}
throw new GeoXmlQlException("请求错误!");
}
}
对外rest API 接口
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.utils.GeoToolsManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @author qinlei
* @description todo
* @date 2021/10/22 10:17
*/
@Slf4j
@Api(tags = "wfs-t操作geoserver")
@RestController
@RequestMapping("/wfs")
public class WFSController {
@Autowired
private GeoToolsManager geoToolsManager;
@PutMapping("/{typeName}/{user}/{businessId}")
public Result<?> updateGeoserverData(@PathVariable String typeName,
@PathVariable String user,
@PathVariable String businessId,
@RequestBody JSONObject jsonObject) {
try {
String ip = jsonObject.getString("ip");
String port = jsonObject.getString("port");
String username = jsonObject.getString("username");
String password = jsonObject.getString("password");
JSONObject variables = jsonObject.getJSONObject("variables");
HashMap<String, Object> params = jsonObject.getObject("params", HashMap.class);
geoToolsManager.insertUpdateByGeoWfs(businessId,user,ip,port,username,password,typeName,variables,params);
return Result.OK();
} catch (Exception e) {
log.error("重试失败", e);
return Result.error("重试失败");
}
}
}
接口使用方式
请求方式: put
请求路径url : http://localhost:10000/st-cloud/wfs/tscloud:mixed/qinlei/4563215482120214
请求body
{
"ip":"10.0.0.15",
"port":"9095",
"username":"admin",
"password":"bych1234",
"variables":{
"geom":{
"type":"Point",
"coordinates":"108.2326554521,34.1215421254"
},
"objectid":{
"type":"general",
"value":"11"
},
"hjd_bh":{
"type":"general",
"value":"test"
},
"hj_type":{
"type":"general",
"value":"gx"
},
"hj_reason":{
"type":"general",
"value":"test-reason"
}
},
"params":{
"hj_reason":"test-reason2",
"objectid":"11"
}
}
生成的xml
<?xml version="1.0" encoding="UTF-8"?>
<wfs:Transaction
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.opengis.net/xsi"
xmlns:scloud="http://scloud" version="1.0.0" service="WFS" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://scloud http://10.0.0.15:9095/geoserver/wfs/DescribeFeatureType?typename=scloud:mixed">
<wfs:Update typeName="scloud:mixed">
<wfs:Property>
<wfs:Name>uploader</wfs:Name>
<wfs:Value>qinlei</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>isvalid</wfs:Name>
<wfs:Value>0</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>uploadtime</wfs:Name>
<wfs:Value>2021-10-26 11:05:20.48</wfs:Value>
</wfs:Property>
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>scloud:hj_reason</ogc:PropertyName>
<ogc:Literal>test-reason2</ogc:Literal>
</ogc:PropertyIsEqualTo>
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>scloud:objectid</ogc:PropertyName>
<ogc:Literal>11</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
</wfs:Update>
<wfs:Insert>
<scloud:mixed>
<scloud:geom>
<gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:coordinates decimal="." cs="," ts=";">108.2326554521,34.1215421254</gml:coordinates>
</gml:Point>
</scloud:geom>
<scloud:objectid>11</scloud:objectid>
<scloud:hjd_bh>test</scloud:hjd_bh>
<scloud:hj_type>gx</scloud:hj_type>
<scloud:hj_reason>test-reason</scloud:hj_reason>
<scloud:isvalid>1</scloud:isvalid>
<scloud:surveyor>qinlei</scloud:surveyor>
<scloud:surveydate>2021-10-26</scloud:surveydate>
<scloud:index_id>4563215482120214</scloud:index_id>
</scloud:mixed>
</wfs:Insert>
</wfs:Transaction>
个人正在整理项目中用到的geotools,抽空本人会封装起来。 https://gitee.com/qlanto/geotools-kit