前言
最近整理项目代码,偶然间看到了以前写的空间查询代码,顿时回想起来那些试图解决空间与条件复合查询分页问题的夜晚。于是想借着这个机会记录一下到底应该如何进行空间信息的管理呢。
常见方式梳理
基于Arcgis Restful API的方式
技术框架
后台向通过arcgis发布的arcgis服务发送restful请求,通过调整请求参数来获取查询结果,arcgis再去数据库检索数据,并把结果返回请求方,请求方按照规范解析json数据,再组装其他属性。
其中:
- arcgis负责发布图层服务,访问postgrelsql查询数据
- postgresl单独保存数据,并单独再管理业务数据,两者通过gid关联
- 后台服务,请求arcgis并将空间数据和关系数据组装
示例代码
public String queryByWhere(String tableUrl, String queryWhere, List<Point> pointList, boolean returnIdsOnly){
if(queryWhere == null || queryWhere.isEmpty()) {
queryWhere = new String("1=1");
}
Map<String, String> params = new HashMap<>();
JSONArray geoJsonArray = new JSONArray();
JSONArray ringsJsonArray = new JSONArray();
JSONObject geoJson = new JSONObject();
//geometry
if(pointList != null && pointList.size() > 4) {
for(Point p : pointList){
geoJsonArray.add(JSONArray.fromObject(p.toString()));
}
ringsJsonArray.add(geoJsonArray);
geoJson.accumulate(RINGS, ringsJsonArray);
params.put("geometry", geoJson.toString());
params.put("geometryType", "esriGeometryPolygon");
}
params.put("where", queryWhere);
//其他参数
if(returnIdsOnly) {
params.put("returnIdsOnly", "true");
}
params.put("outfields", "*");
params.put("f", "json");
String jsonResult = GisRestService.post(tableUrl, params);
return jsonResult;
}
public String queryByObjectIds(String tableUrl, int[] objectIdsArray){
if(objectIdsArray == null || objectIdsArray.length == 0) {
return null;
}
Map<String, String> params = new HashMap<>();
StringBuilder objectIdsStrBuf = new StringBuilder();
for(int i=0; i<objectIdsArray.length; i++) {
if(i != 0) {
objectIdsStrBuf.append(",");
}
objectIdsStrBuf.append(objectIdsArray[i]);
}
String objectIdsStr = objectIdsStrBuf.toString();
params.put("objectIds", objectIdsStr);
params.put("outfields", "*");
params.put("f", "json");
return GisRestService.post(tableUrl, params);
}
优点
比较直观,没有开发经验的话很容易就走上这条路,毕竟如果你手里只有锤子那么看什么都是钉子,当时我们什么也不会只能照葫芦画瓢。
缺点
但对于复杂场景处理较为困难,包括但不限于下面两个问题
- 大量数据的分页问题,由于业务数据与空间数据分开管理,仅通过gid关联,空间表中没有大部分关系字段,业务数据无法通过空间检索。所以只能先通过空间检索获取gids,再通过in和条件查询的方式实现复合查询,但分页有大问题,而且数据量打了in的效率很低。。。
- 空间库和关系库的同步问题,由于关系库中存放的是业务数据,增删改都是家常便饭,同步修改当然是好的,但异常情况会使空间和关系库不统一,需要单独开发同步模块保证两者的数据一致。
适合场景
这种解决方案有点像靠着金山要饭吃,postgrelsql是以全能而见长的,自身就有较强的空间信息处理能力,所以恐怕只适合没有后端情况。
基于mybatis+postgis+postgresql的方式
技术框架
- mybatis,负责数据库访问,xml中直接编写sql可以直接调用postgis的各种辅助方法
- postgis,负责复杂空间信息的处理和辅助
- postgresql,负责空间信息保存,这次业务数据和空间数据统一为一张表,对于空间数据会有空间字段
示例
Mapper
@Mapper
@Component
public interface MapEelementMapper {
/**
* 条件查询
* @param mapElement 查询条件
* @return 符合条件的数据
*/
List<MapElement> findByCondition(MapElement mapElement);
/**
* @param geometry 对变形地理信息
* @param type 元素大类
* @param subType 元素细分类
* @return 给定多边形区域中满足条件的元素的集合
*/
List<MapElement> findMapElementByPolygon(@Param("geometry") String geometry,
@Param("type") String type,
@Param("subType") String subType);
}
xml
<select id="findMapElementByPolygon" resultMap="MapElementMap">
SELECT id, name,
ST_AsGeoJson(element_location) as element_location
from map_elements
where ST_Contains( ST_MakePolygon(ST_GeomFromText('LINESTRING' || #{geometry}, 4326)) , element_location) = 't'
</select>
<select id="findByCondition" resultMap="MapElementMap">
SELECT id,
name,
ST_AsGeoJson(element_location) as element_location
FROM map_elements where deleted = false
<if test="name!=null">
and name like concat('%', #{name}, '%')
</if>
<if test="spatialPointsStr!=null">
and ST_Contains( ST_MakePolygon(ST_GeomFromText('LINESTRING' || #{spatialPointsStr}, 4326)) ,
element_location) = 't'
</if>
ORDER BY update_time DESC
</select>
优点
比较灵活,直接操作数据库,效率也相较于之前的方案要高得多,借助postgis可以实现许多以前想不到的功能,比如缓冲区分析,路径规划等。
技术栈也比较通用。
缺点
暂时没有想到、
基于geotools的方式
较为复杂,需要一定的开发经验,但更高的可控程度和更强的能力。这个后续再跟大家介绍。
其他方式
redis,mongodb,es都提供了一定的空间查询和处理的能力,有兴趣的同学可以去尝试一下。
总结
强烈推荐mybatis+postgrelsql+postgis的解决方案,有必要的再配合geoserver完全可以实现图层的发布显示。