前要
最近在研究海量空间数据应用技术方案,技术栈依然依托于geoserver,在此基础衍生和扩展。前期写过一篇Geoserver+GeoMesa技术博客,感兴趣的可以关注查阅。考虑到es的查询性能优秀,特此研究geoserver与es结合结束路线。说到此,博主对国内的一些知识权和博客质量略感失望。知识产权意识非常低下,博主搜索geoserver+es方案,某度前六条搜索结果完全一致,但并不是同一作者,不清楚是作者不同的id号还是知识作品被别人剽窃(剽窃可能性非常大,博主之前深受其害,曾经投诉过),最重要的这六条搜索结果完全是照搬geoserver官网,甚至可以说单纯中英文翻译了一编就发博客,但是官网漏了一条非常重要的技术,没有这项技术,根本无法支撑空间数据geoserver与es结合应用,可见搜索结果作者完全没有进行验证,就开始误人子弟,更可气的是盲目剽窃和跟风的偷盗者(更加不可能自己验证),完全是对知识的亵渎和不尊重。博主在此写博客做一篇补充,希望能帮到更多的地理空间开发爱好者。
环境搭建
- jdk 1.8
-
nodejs(可选)
使用npm安装es-head依赖包与启动。
-
geoserver用与连接es,空间数据服务发布,空间数据展示。
-
geoserver es插件 官网插件缺失一geojson依赖包,如果不添加将无法使用wms地图服务,很多博文照搬官网不做验证,很让人诟病。
-
geojson依赖jar包
-
es安装步骤可自行查阅部署
-
kibana用于es可视化性能监控和管理,可提供空间数据导入展示功能
Kibana 空间数据上传展示效果 -
Logstash (可选)
可用于关系型数据库(Oracle、Postgresql、Mysql等)和文本文件与es数据的同步和导入功能。详情可参照官网介绍。
-
web端es可视化系统,可用于数据查询,类似于Kibana
es-head效果图
集成部署
- 将geoserver es插件包复制与geoserver/lib文件夹下,tomcat启动
geoserver es效果图 es参数配置可见官网说明:
https://docs.geoserver.org/latest/en/user/community/elasticsearch/index.html
-
es空间数据导入
es服务端开放两种空间数据类型geo-point(用于单点)和geo-shape(可用于复杂空间矢量要素),es支持geojson、geohash、wkt标准空间数据格式,很遗憾不支持wkb格式,因此关系型数据库数据同步要记得格式转换,目前只支持wgs84经纬度数据,数据导入记得坐标系转换。
我们可使用上述kibana工具导入geojson文件数据,展示图如下,默认点聚合算法效果:
效果图展示 es-head数据列表查看如下:
es-head数据查询展示
使用es java api数据同步
java程序同步更新es,局部代码如下:
/**
* Bean name default 函数名字
*
* @return
*/
@Bean(name = "transportClient")
public TransportClient transportClient() {
LOGGER.info("Elasticsearch初始化开始。。。。。");
TransportClient transportClient = null;
try {
// 配置信息
Settings esSetting = Settings.builder()
.put("cluster.name", clusterName) //集群名字
.put("client.transport.sniff", true)//增加嗅探机制,找到ES集群
.put("thread_pool.search.size", Integer.parseInt(poolSize))//增加线程池个数,暂时设为5
.build();
//配置信息Settings自定义
transportClient = new PreBuiltTransportClient(esSetting);
TransportAddress transportAddress = new TransportAddress(InetAddress.getByName(hostName), Integer.valueOf(port));
transportClient.addTransportAddresses(transportAddress);
} catch (Exception e) {
LOGGER.error("elasticsearch TransportClient create error!!", e);
}
return transportClient;
}
初始化es客户端
/**
* 创建索引
*
* @param index
* @return
*/
@Override
public boolean createIndex(String index) {
if (!isIndexExist(index)) {
LOGGER.info("Index is not exits!");
}
CreateIndexResponse indexresponse = client.admin().indices().prepareCreate(index).execute().actionGet();
LOGGER.info("执行建立成功?" + indexresponse.isAcknowledged());
return indexresponse.isAcknowledged();
}
/**
* 删除索引
*
* @param index
* @return
*/
@Override
public boolean deleteIndex(String index) {
if (!isIndexExist(index)) {
LOGGER.info("Index is not exits!");
}
AcknowledgedResponse dResponse = client.admin().indices().prepareDelete(index).execute().actionGet();
if (dResponse.isAcknowledged()) {
LOGGER.info("delete index " + index + " successfully!");
} else {
LOGGER.info("Fail to delete index " + index);
}
return dResponse.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param index
* @return
*/
@Override
public boolean isIndexExist(String index) {
IndicesExistsResponse inExistsResponse = client.admin().indices().exists(new IndicesExistsRequest(index)).actionGet();
if (inExistsResponse.isExists()) {
LOGGER.info("Index [" + index + "] is exist!");
} else {
LOGGER.info("Index [" + index + "] is not exist!");
}
return inExistsResponse.isExists();
}
/**
* @Description: 判断inde下指定type是否存在
*/
@Override
public boolean isTypeExist(String index, String type) {
return isIndexExist(index)
? client.admin().indices().prepareTypesExists(index).setTypes(type).execute().actionGet().isExists()
: false;
}
@Override
public boolean createMapping(String index, String type) {
// 创建index
Map<String, Object> settings = new HashMap<>();
settings.put("number_of_shards", 4); // 分片数量
settings.put("number_of_replicas", 0); // 复制数量, 导入时最好为0, 之后2-3即可
settings.put("refresh_interval", "10s");// 刷新时间
CreateIndexRequestBuilder prepareCreate = client.admin().indices().prepareCreate(index);
prepareCreate.setSettings(settings);
try {
// 创建mapping
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject(type)
.startObject("properties")
.startObject("osm_id").field("type", "text").endObject()
.startObject("code").field("type", "long").endObject()
.startObject("fclass").field("type", "text").endObject()
.startObject("name").field("type", "text").endObject()
.startObject("geom").field("type", "geo_point").endObject()
.endObject()
.endObject()
.endObject();
prepareCreate.addMapping(type, mapping);
CreateIndexResponse response = prepareCreate.execute().actionGet();
return response.isAcknowledged();
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
return false;
}
索引功能封装
@Override
public String addData(JSONObject jsonObject, String index, String type, String id) {
IndexResponse response;
try {
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.field("osm_id",jsonObject.getString("osmId"))
.field("code",jsonObject.getIntValue("code"))
.field("fclass",jsonObject.getString("fclass"))
.field("name",jsonObject.getString("name"))
.startObject("geom").field("lat", jsonObject.getDoubleValue("lat")).field("lon", jsonObject.getDoubleValue("lon")).endObject()
.endObject();
response = client.prepareIndex(index, type, id).setSource(xContentBuilder).get();
LOGGER.info("addData response status:{},id:{}", response.status().getStatus(), response.getId());
return response.getId();
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
return null;
}
@Override
public String addData(Map<String, ?> source, String index, String type, String id) {
IndexResponse response = client.prepareIndex(index, type, id).setSource(source).get();
LOGGER.info("addData response status:{},id:{}", response.status().getStatus(), response.getId());
return response.getId();
}
@Override
public String addData(JSONObject jsonObject, String index, String type) {
return addData(jsonObject, index, type, UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
}
/**
* 通过ID删除数据
*
* @param index 索引,类似数据库
* @param type 类型,类似表
* @param id 数据ID
*/
@Override
public String deleteDataById(String index, String type, String id) {
DeleteResponse response = client.prepareDelete(index, type, id).execute().actionGet();
LOGGER.info("deleteDataById response status:{},id:{}", response.status().getStatus(), response.getId());
return response.getId();
}
@Override
public String updateDataById(JSONObject jsonObject, String index, String type, String id) {
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index(index).type(type).id(id).doc(jsonObject);
ActionFuture<UpdateResponse> updateResponseActionFuture = client.update(updateRequest);
return updateResponseActionFuture.actionGet().getId();
}
@Override
public String updateDataById(Map<String, ?> source, String index, String type, String id) {
return null;
}
/**
* 通过ID获取数据
*
* @param index 索引,类似数据库
* @param type 类型,类似表
* @param id 数据ID
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @return
*/
@Override
public Map<String, Object> searchDataById(String index, String type, String id, String fields) {
GetRequestBuilder getRequestBuilder = client.prepareGet(index, type, id);
if (StringUtils.isNotEmpty(fields)) {
getRequestBuilder.setFetchSource(fields.split(","), null);
}
GetResponse getResponse = getRequestBuilder.execute().actionGet();
return getResponse.getSource();
}
es数据的增删改查操作
geoserver服务发布




后续
geoserver+es海量数据应用暂时讲解到这里,如果有感兴趣的博友可关注和评论博主,一起探讨。后续博主打算追踪验证geoserver+es数据查询展示效率,与geoserver+关系型数据库数据查询展示效率做综合对比。知识点稍显凌乱,如有错误,欢迎指正。谢谢大家地支持。
java程序代码地址(下载develop分支):
https://gitee.com/yangdengxian/geodatastore/tree/develop/
poi数据大家可以自行在osm网站下载