前言
在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@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="<"><</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=" + 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之路】 已經接入了智能助手,歡迎關注,歡迎提問。
歡迎訪問我的博客網站-長談GIS:
http://shanhaitalk.com
都看到這了,不要忘記點贊、收藏 + 關注 哦 !
本號不定時更新有關 GIS開發 相關內容,歡迎關注 !