前言
在GIS开发中,属性查询是非常普遍的操作,这是每一个GISer都要掌握的必备技能。实现高效的数据查询功能可以提升用户体验,完成数据的快速可视化表达。
本篇教程在之前一系列文章的基础上讲解如何将使用GeoTools
工具结合OpenLayers
实现PostGIS
空间数据库数据的属性查询功能。
开发环境
本文使用如下开发环境,以供参考。
:::block-1
时间:2025年
GeoTools:v34-SNAPSHOT
IDE:IDEA2025.1.2
JDK:v17
OpenLayers:v9.2.4
Layui:v2.9.14
:::
1. 搭建SpringBoot后端服务
本文接着OpenLayers 从后端服务加载 GeoJSON 数据进行讲解,如果还没有读过,请从那里开始。
:::block-1
在开始本文之前,请确保你已经安装好了PostgreSQL
数据库,添加了PostGIS
插件,并且已经启用空间数据拓展。安装完成之后,你还需要将Shapefile
导入空间数据库。如果你还不了解如何导入空间数据,可参考之前的文章。
:::
1.1. 安装依赖
在pom.xml
文件中添加开发所需依赖,其中jdbc
和postgresql
依赖用于连接数据库。
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools.jdbc</groupId>
<artifactId>gt-jdbc-postgis</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- PostgreSQL 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
<snapshots><enabled>false</enabled></snapshots>
<releases><enabled>true</enabled></releases>
</repository>
<repository>
<id>osgeo-snapshot</id>
<name>OSGeo Snapshot Repository</name>
<url>https://repo.osgeo.org/repository/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>
1.2. 创建Countries
实体
如下图是我测试导入的世界国家行政区数据。
可选取部分字段创建业务对象。
package com.example.geotoolsboot.dao;
import lombok.Getter;
import lombok.Setter;
public class Countries {
@Setter
@Getter
public Integer gid; // 要素id
@Setter
@Getter
public String sovereignt; // 国家名称
@Setter
@Getter
public String sov_a3; // 国家名称缩写
@Setter
@Getter
public String admin; // 国家名称
@Setter
@Getter
public String adm0_a3; // 国家名称缩写
@Setter
@Getter
public String geom; // 几何字段名称
}
1.3. 创建数据库连接
在项目中创建数据库连接工具类PgUtils
,在Map
参数中填写数据库连接信息。
package com.example.geotoolsboot.utils;
import org.geotools.data.postgis.PostgisNGDataStoreFactory;
import java.util.HashMap;
import java.util.Map;
/**
* PostGIS 空间数据库工具类
*/
public class PgUtils {
public static Map<String, Object> connectPostGIS(){
// 连接PostGIS数据库
Map<String, Object> pgParams = new HashMap();
pgParams.put(PostgisNGDataStoreFactory.DBTYPE.key, "postgis");
pgParams.put(PostgisNGDataStoreFactory.HOST.key, "localhost");
pgParams.put(PostgisNGDataStoreFactory.PORT.key, "5432");
pgParams.put(PostgisNGDataStoreFactory.DATABASE.key, "geodata");
pgParams.put(PostgisNGDataStoreFactory.USER.key, "postgres");
pgParams.put(PostgisNGDataStoreFactory.PASSWD.key, "123456");
pgParams.put(PostgisNGDataStoreFactory.SCHEMA.key, "public"); // 明确指定schema
pgParams.put(PostgisNGDataStoreFactory.EXPOSE_PK.key, true); // 暴露主键
return pgParams;
}
}
1.4. 创建属性查询实现类
在项目中创建PgService
类用于实现空间数据的属性过滤操作。定义一个方法attributeFilter
,该方法接收一个字符串参数,也就是属性过滤条件,如"admin = 'China'"
,最后将查询结果总数和查询数据返回。
使用CQL.toFilter
方法进行属性过滤。
// 读取 PostGIS 空间数据库数据,并实现属性过滤
public Map<String,Object> attributeFilter(String filterParams) throws Exception{
// 存储返回对象
Map<String,Object> result = new HashMap<>();
// 国家数据列表
List<Countries> countries = new ArrayList<>();
// 创建数据库连接
Map<String, Object> pgParams = PgUtils.connectPostGIS();
DataStore dataStore = DataStoreFinder.getDataStore(pgParams);
// 数据库表名
String typeName = "countries";
SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
// 创建数据过滤器
Filter filter = CQL.toFilter(filterParams);
// Filter filter = CQL.toFilter("admin = 'China'");
SimpleFeatureCollection collection = featureSource.getFeatures(filter);
// 统计查询总数
int count = collection.size();
result.put("count",count);
try(FeatureIterator<SimpleFeature> features = collection.features()) {
while (features.hasNext()) {
SimpleFeature feature = features.next();
// 构造返回数据
Countries country = new Countries();
country.setGid((Integer) feature.getAttribute("gid"));
country.setAdmin((String) feature.getAttribute("admin"));
country.setSovereignt((String) feature.getAttribute("sovereignt"));
country.setSov_a3((String) feature.getAttribute("sov_a3"));
country.setAdm0_a3((String) feature.getAttribute("adm0_a3"));
Object geometry = feature.getAttribute("geom");
GeometryJSON geometryJSON = new GeometryJSON();
StringWriter writer = new StringWriter();
geometryJSON.write((Geometry) geometry,writer);
String geoJSON = writer.toString();
country.setGeom(geoJSON);
countries.add(country);
}
}catch (Exception e){
e.printStackTrace();
}
result.put("countries",countries);
return result;
}
1.5. 创建属性过滤控制层
在测试中,使用注解@CrossOrigin(origins = "*")
实现接口允许跨域,注解@GetMapping
添加请求访问路径。
package com.example.geotoolsboot.controller;
import com.example.geotoolsboot.service.impl.PgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 属性查询过滤器
*/
@CrossOrigin(origins = "*") // 允许跨域
@RestController
public class AttributeQueryController {
@Autowired
private PgService pgService;
@GetMapping("/countryList")
public Map<String,Object> getCountriesByAttribute(
@RequestParam(required = false) String filterParams) throws Exception{
return pgService.attributeFilter(filterParams);
}
}
2. 使用 OpenLayers 加载数据
具体使用情况请参考之前的文章:OpenLayers 加载GeoJSON的五种方式
本文前端使用OpenLayers
结合Layui
框架实现。主要借助Layui
表单创建属性查询结构,包括属性字段、查询条件以及查询内容数据。
<div class="query-wrap">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<label class="layui-form-label">查询字段</label>
<div class="layui-input-block">
<select name="field" lay-filter="aihao">
<option value=""></option>
<option value="gid" selected>gid(要素编号)</option>
<option value="admin">admin(国家名称)</option>
<option value="adm0_a3">adm0_a3(国家名称缩写)</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">查询条件</label>
<div class="layui-input-block">
<select name="condition" lay-filter="aihao">
<option value=""></option>
<option value="<"><</option>
<option value=">" selected>></option>
<option value="=">=</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">查询内容</label>
<div class="layui-input-block">
<input type="text" name="content" lay-verify="required" placeholder="请输入查询内容" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label for="">一共查询到:</label>
<span class="resultCount">0</span>
<span>条数据</span>
</div>
<div class="layui-form-item">
<button class="layui-btn" lay-submit lay-filter="attrQuery">确认</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</form>
</div>
CSS
结构样式:
.query-wrap {
position: absolute;
padding: 10px;
top: 80px;
left: 90px;
background: #ffffff;
width: 250px;
border-radius: 2.5px;
}
对于JS部分,在前端直接使用fetch
API请求接口数据。在每次点击请求按钮后都需要调用工具类方法removeLayerByName
清除原图层数据。使用const { field, condition, content } = data.field
解构查询表单数据。后面的代码内容都是之前写过的,也比较简单,就不令行讲解了。
layui.use(['form'], function () {
const form = layui.form;
const layer = layui.layer;
// 提交事件
form.on('submit(attrQuery)', function (data) {
removeLayerByName("country", map)
removeLayerByName("highlightLayer", map)
// 获取表单字段值
const { field, condition, content } = data.field
// 查询参数
const querfilterParams = `${field + " " + condition + " '" + content + "'"}`
// 后端服务地址
const JSON_URL = "http://127.0.0.1:8080/countryList?filterParams=" + querfilterParams
fetch(JSON_URL).then(response => response.json()
.then(result => {
// 获取查询数量
const resultCount = result.count
document.querySelector(".resultCount").textContent = resultCount
// 获取查询结果
const countries = result.countries
const features = countries.map(item => {
const feat = {}
feat.type = "Feature"
feat.geometry = JSON.parse(item.geom)
feat.properties = item
feat.properties.color = `hsl(${Math.floor(Math.random() * 360)}, 100%, 50%)`
const feature = new ol.format.GeoJSON().readFeature(feat)
return feature
})
const vectorSource = new ol.source.Vector({
features: features,
format: new ol.format.GeoJSON()
})
// 行政区矢量图层
const regionLayer = new ol.layer.Vector({
source: vectorSource,
style: {
"text-value": ["string", ['get', 'admin']],
'fill-color': ['string', ['get', 'color'], '#eee'],
}
})
regionLayer.set("layerName", "country")
map.addLayer(regionLayer)
map.getView().setCenter([108.76623301275802, 34.22939602197002])
map.getView().setZoom(4.5)
// 高亮图层
const highlightLayer = new ol.layer.Vector({
source: new ol.source.Vector({}),
map: map,
style: {
"stroke-color": '#3CF9FF',
"stroke-width": 2.5
}
})
// Popup 模板
const popupColums = [
{
name: "gid",
comment: "要素编号"
},
{
name: "admin",
comment: "国家名称"
},
{
name: "adm0_a3",
comment: "简称"
},
{
name: "color",
comment: "颜色"
}
]
// 高亮要素
let highlightFeat = undefined
function showPopupInfo(pixel) {
regionLayer.getFeatures(pixel).then(features => {
// 若未查询到要素,则退出
if (!features.length) {
if (highlightFeat) {
highlightLayer.getSource().removeFeature(highlightFeat)
highlightFeat = undefined
}
return
}
// 获取要素属性
const properties = features[0].getProperties()
// 将事件坐标转换为地图坐标
const coords = map.getCoordinateFromPixel(pixel)
if (features[0] != highlightFeat) {
// 移除高亮要素
if (highlightFeat) {
highlightLayer.getSource().removeFeature(highlightFeat)
highlightFeat = undefined
}
highlightLayer.getSource().addFeature(features[0])
highlightFeat = features[0]
}
openPopupTable(properties, popupColums, coords)
})
}
// 监听地图鼠标移动事件
map.on("pointermove", evt => {
// 若正在拖拽地图,则退出
if (evt.dragging) return
const pixel = map.getEventPixel(evt.originalEvent)
showPopupInfo(pixel)
})
// 监听地图鼠标点击事件
map.on("click", evt => {
console.log(evt.coordinate)
// 若正在拖拽地图,则退出
if (evt.dragging) return
showPopupInfo(evt.pixel)
})
})
)
return false; // 阻止默认 form 跳转
});
});
OpenLayers示例数据下载,请回复关键字:ol数据
全国信息化工程师-GIS 应用水平考试资料,请回复关键字:GIS考试
【GIS之路】 已经接入了智能助手,欢迎关注,欢迎提问。
欢迎访问我的博客网站-长谈GIS:
http://shanhaitalk.com
都看到这了,不要忘记点赞、收藏 + 关注 哦 !
本号不定时更新有关 GIS开发 相关内容,欢迎关注 !