GeoTools 结合 OpenLayers 实现属性查询

前言

在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文件中添加开发所需依赖,其中jdbcpostgresql依赖用于连接数据库。

<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@Getterpublic Integer gid; // 要素id@Setter@Getterpublic String sovereignt; // 国家名称@Setter@Getterpublic String sov_a3; // 国家名称缩写@Setter@Getterpublic String admin; // 国家名称@Setter@Getterpublic String adm0_a3; // 国家名称缩写@Setter@Getterpublic 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"); // 明确指定schemapgParams.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 {@Autowiredprivate 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="<">&lt</option><option value=">" selected>&gt</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部分,在前端直接使用fetchAPI请求接口数据。在每次点击请求按钮后都需要调用工具类方法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=" + querfilterParamsfetch(JSON_URL).then(response => response.json().then(result => {// 获取查询数量const resultCount = result.countdocument.querySelector(".resultCount").textContent = resultCount// 获取查询结果const countries = result.countriesconst features = countries.map(item => {const feat = {}feat.type = "Feature"feat.geometry = JSON.parse(item.geom)feat.properties = itemfeat.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 = undefinedfunction 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) returnconst pixel = map.getEventPixel(evt.originalEvent)showPopupInfo(pixel)})// 监听地图鼠标点击事件map.on("click", evt => {console.log(evt.coordinate)// 若正在拖拽地图,则退出if (evt.dragging) returnshowPopupInfo(evt.pixel)})}))return false; // 阻止默认 form 跳转});});

OpenLayers示例数据下载,请回复关键字:ol数据

全国信息化工程师-GIS 应用水平考试资料,请回复关键字:GIS考试

【GIS之路】 已经接入了智能助手,欢迎关注,欢迎提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏 + 关注

本号不定时更新有关 GIS开发 相关内容,欢迎关注 !

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86701.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86701.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue-27(实践练习:将现有组件重构为使用组合式 API)

实践练习:将现有组件重构为使用组合式 API 理解重构过程 重构是任何开发者的关键技能,尤其是在采用新范式如 Vue.js 中的 Composition API 时。它涉及在不改变外部行为的情况下重新组织现有代码,旨在提高可读性、可维护性和可重用性。在从 Options API 迁移到 Composition…

基于Uniapp+SpringBoot+Vue 的在线商城小程序

开发系统:Windows10 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,jquery,html,vue 角色:用户 商家 管理员 用户菜单:首页:商…

华为云Flexus+DeepSeek征文|利用华为云一键部署的Dify平台构建高效智能电商客服系统实战

目录 前言 1 华为云快速搭建 Dify-LLM 应用平台 1.1 一键部署简介 1.2 设置管理员账号登录dify平台 2 接入 DeepSeek 大模型与 Reranker 模型 2.1 接入自定义 LLM 模型 2.2 设置 Reranker 模型 3 构建电商知识库 3.1 数据源选择 3.2 分段设置与清洗 3.3 处理并完成 …

python应用day07---pyechars模块详解

1.pyecharts安装: pip install pyecharts 2.pyecharts入门: # 1.导入模块 from pyecharts.charts import Line# 2.创建Line对象 line Line() # 添加数据 line.add_xaxis(["中国", "美国", "印度"]) line.add_yaxis("GDP数据", [30…

高档背景色

https://andi.cn/page/622250.html

教学视频画中画播放(PICTURE-IN-PICTURE)效果

视频平台的画中画&#xff08;PIP&#xff09;功能通过小窗播放提升用户体验&#xff1a;1&#xff09;支持多任务处理&#xff0c;如边看教程边操作文档&#xff1b;2&#xff09;减少应用跳出率&#xff0c;增强用户粘性&#xff1b;3&#xff09;优化屏幕空间利用&#xff1…

MySQL (一):数据类型,完整性约束和表间关系

在当今数据驱动的时代&#xff0c;数据库作为数据存储与管理的核心工具&#xff0c;其重要性不言而喻。MySQL 作为一款广泛应用的开源数据库&#xff0c;凭借其高性能、高可靠性和丰富的功能&#xff0c;深受开发者喜爱。本文作为 MySQL 系列博客的开篇&#xff0c;将带你深入了…

【软考高项论文】信息系统项目的资源管理

摘要 本文围绕信息系统项目的资源管理展开论述。首先阐述了项目资源管理的基本过程&#xff0c;包括资源规划、估算、获取、配置、监控和释放等关键步骤&#xff0c;并给出资源分解结构示例。接着结合2024年参与管理的某信息系统项目实际情况&#xff0c;详细说明资源管理的具…

阿里云Ubuntu服务器上安装MySQL并配置远程连接

1. 安装MySQL 首先连接到你的Ubuntu服务器&#xff0c;然后执行&#xff1a; # 更新软件包列表 sudo apt update# 安装MySQL服务器 sudo apt install mysql-server# 启动MySQL服务 sudo systemctl start mysql# 设置MySQL开机自启 sudo systemctl enable mysql# 检查MySQL状态…

STM32HAL 旋转编码器教程

配置时钟编码模式读取方法&#xff1a; if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1) 0){count - __HAL_TIM_GET_COUNTER(&htim1);}else{count __HAL_TIM_GET_COUNTER(&htim1);}bsp_dtUInt32_show(count);__HAL_TIM_SET_COUNTER(&htim1, 0); 通过 __HAL_TIM…

激光束修复手机屏任意层不良区域,实现液晶线路激光修复原理

摘要 手机屏结构多层复合&#xff0c;任意层线路不良严重影响显示质量。激光束凭借高能量密度与可调控性&#xff0c;能够穿透不同介质精准作用于目标层。本文基于激光与多层材料相互作用机制&#xff0c;解析激光束对手机屏各层不良区域的修复原理&#xff0c;为全层液晶线路…

【软件开发】架构与架构师

文章目录 一、前言二、关于系统架构1. 保障用户访问速度2. 支持大规模并发请求3. 应对复杂业务逻辑4. 应对海量数据的存储与读写三、关于系统架构师四、关于安全架构1. 产品安全架构2. 安全体系架构五、关于安全架构师一、前言 在系统建设与技术架构实践不断推进的背景下,关于…

Blender速成班-基础篇2

视频教程&#xff1a;【第一章】基础操作_哔哩哔哩_bilibili 目录 编辑模式 1.1侧边属性 挤出选区——E 挤出方式选择——AltE ​编辑 内插面——I 倒角——CtrlB 环切——CtrlR 旋绕 本片继续基于视频教程介绍Blender的一些基础操作 勾选Cavity使物体边线更清晰 编…

对象进阶与扩展

目录 创建对象 Object.create&#xff08;&#xff09; 原型 原型操作 原型污染 对象属性 属性特征 枚举属性 Object.keys&#xff08;&#xff09; Object.getOwnPropertyNames&#xff08;&#xff09; Object.getOwnPropertyDescriptor&#xff08;&#xff09; O…

理解图像的随机噪声

图像灰度信息很难精确测量&#xff0c;一般情况下测量值总在真实值附近晃动&#xff0c;使用概率模型可以对该随机性建模&#xff0c;大致如下&#xff1a; 1 概率密度函数 1&#xff09;随机变量 x 的概率密度函数 p(x) 定义为&#xff1a;当 趋近于 0 时&#xff0c;在区间 上…

华为云镜像仓库下载 selenium/standalone-chrome 镜像

你可以使用以下步骤从华为云镜像仓库下载 selenium/standalone-chrome 镜像&#xff1a; 1. 登录华为云镜像仓库&#xff08;如果需要认证&#xff09; bash sudo docker login -u <用户名> -p <密码> swr.cn-north-4.myhuaweicloud.com 如果没有华为云账号&…

Push-T, AloHa, Rlbench三个仿真环境信息

1.Push-T 很好兄弟&#xff0c;你问得很关键&#xff1a;你给我的三段代码其实是一套完整的推理录像 pipeline&#xff0c;它们之间既有独立功能&#xff0c;又有顺序依赖关系。我来帮你分段解释&#xff0c;每段是什么功能、三段之间怎么配合&#xff0c;让你彻底搞明白。 &a…

Linux信号机制:从入门到精通

嘿&#xff0c;小伙伴们&#xff01;今天我要和大家聊一个Linux系统中非常有趣又重要的话题——信号机制。别担心&#xff0c;虽然信号听起来有点高深&#xff0c;但我会用最通俗易懂的语言&#xff0c;配合清晰的图表&#xff0c;带你彻底搞懂这个概念&#xff01; 什么是信号…

Vue3项目引入高德地图【超详细教程】

前言 在 Vue 3 项目中集成高德地图&#xff08;AMap&#xff09;是一个常见的需求。本文将详细介绍如何在 Vue 3 项目中使用高德地图&#xff0c;包括安装配置、基本使用以及一些进阶功能的实现。 一、环境准备 1.1 vue3项目初始化 步骤 1&#xff1a;初始化项目 npm crea…

blender mcp安装(完全免费的ai建模)

1.最关键的一步&#xff0c;建议最早执行(就是安装uvx) mac系统执行 brew install uvwindows执行 powershell -c "irm https://astral.sh/uv/install.ps1 | iex" 出现这一步就成功安装uvx了&#xff0c;因为mcp需要使用uvx 2.第二步骤 github地址: https://gith…