在Java中基于Geotools對(duì)PostGIS數(shù)據(jù)庫(kù)的空間查詢實(shí)踐教程
前言
在當(dāng)今數(shù)字化浪潮下,空間數(shù)據(jù)的應(yīng)用價(jià)值日益凸顯,從城市規(guī)劃到環(huán)境監(jiān)測(cè),從物流配送到地理信息系統(tǒng)(GIS)開(kāi)發(fā),精準(zhǔn)、高效的空間數(shù)據(jù)查詢成為關(guān)鍵環(huán)節(jié)。而 Java 作為廣泛應(yīng)用的編程語(yǔ)言,在與地理空間技術(shù)的融合中展現(xiàn)出獨(dú)特魅力。Geotools 作為開(kāi)源的 Java GIS 庫(kù),為 Java 開(kāi)發(fā)者提供了強(qiáng)大的地理空間數(shù)據(jù)處理能力,猶如一把開(kāi)啟空間數(shù)據(jù)寶藏之門(mén)的鑰匙。PostGIS 則是 PostgreSQL 數(shù)據(jù)庫(kù)的空間擴(kuò)展,能夠存儲(chǔ)和處理復(fù)雜的空間數(shù)據(jù)類型,是空間數(shù)據(jù)存儲(chǔ)與管理的得力助手。將 Geotools 與 PostGIS 結(jié)合,意味著我們可以利用 Java 強(qiáng)大的編程生態(tài)和 Geotools 豐富的 GIS 功能,深度挖掘 PostGIS 中海量空間數(shù)據(jù)的潛力。
比如我們需要對(duì)某一個(gè)風(fēng)景區(qū)的商業(yè)開(kāi)發(fā)程度進(jìn)行評(píng)價(jià),需要做的第一件事就是根據(jù)風(fēng)景區(qū)的范圍也就是AOI數(shù)據(jù),查詢這個(gè)AOI數(shù)據(jù)的范圍內(nèi)所有的POI數(shù)據(jù),然后根據(jù)不同的POI類型進(jìn)行分類,從而計(jì)算不同的POI類型在景區(qū)內(nèi)的分布情況,加上一些空間分析和相關(guān)計(jì)算,從而可以對(duì)當(dāng)前景區(qū)的一個(gè)商業(yè)情況進(jìn)行初步的評(píng)估,從而為景區(qū)的優(yōu)質(zhì)發(fā)展提供指導(dǎo)和參考,以下圖為例:
本博客將深入探討這一實(shí)踐,從連接配置到復(fù)雜空間查詢操作,包括點(diǎn)查詢、區(qū)域范圍查詢以及空間關(guān)系判斷等,全方位展示如何在 Java 環(huán)境下借助 Geotools 駕馭 PostGIS 數(shù)據(jù)庫(kù),實(shí)現(xiàn)高效精準(zhǔn)的空間數(shù)據(jù)檢索,為相關(guān)領(lǐng)域開(kāi)發(fā)者提供實(shí)用的技術(shù)路徑,助力空間數(shù)據(jù)應(yīng)用的創(chuàng)新拓展。
一、相關(guān)技術(shù)背景介紹
這里以長(zhǎng)沙岳麓山風(fēng)景區(qū)為例,主要介紹如何得出岳麓山及風(fēng)景區(qū)內(nèi)的POI數(shù)據(jù)的分布情況。從而為下一步對(duì)該風(fēng)景區(qū)的商業(yè)及人文指數(shù)進(jìn)行評(píng)價(jià)。由此我們需要對(duì)岳麓山風(fēng)景區(qū)的空間范圍數(shù)據(jù),以及有了AOI范圍后對(duì)其范圍內(nèi)的POI數(shù)據(jù)進(jìn)行檢索的流程進(jìn)行簡(jiǎn)單說(shuō)明。
1、評(píng)價(jià)對(duì)象AOI
關(guān)于如何獲取AOI數(shù)據(jù),在之前的博文中我們?cè)?jīng)進(jìn)行過(guò)相應(yīng)的說(shuō)明。大家可以從相應(yīng)的官方渠道獲取。也可以從互聯(lián)網(wǎng)圖源來(lái)獲取,比如從百度地圖或者高德地圖中也可以獲取數(shù)據(jù)。以高德地圖為例,在下面的查詢界面中可以查看到具體的數(shù)據(jù):
我們可以在網(wǎng)絡(luò)請(qǐng)求的地方對(duì)這個(gè)空間面數(shù)據(jù)進(jìn)行調(diào)試,也可以將這個(gè)數(shù)據(jù)復(fù)制到我們的開(kāi)發(fā)環(huán)境中,從而可以實(shí)現(xiàn)離線的空間計(jì)算。如下圖所示:
上面的數(shù)據(jù)也是本博客的目標(biāo)AOI范圍,我們后續(xù)的所有工作都將圍繞這個(gè)區(qū)域來(lái)展開(kāi)。 因此,如果對(duì)AOI數(shù)據(jù)不是很了解,建議先對(duì)相關(guān)知識(shí)又一個(gè)大體的認(rèn)識(shí)以便于更好的掌握相關(guān)知識(shí)。
2、數(shù)據(jù)處理流程
為了讓大家對(duì)整個(gè)數(shù)據(jù)處理的流程有一個(gè)簡(jiǎn)單的認(rèn)識(shí),這里將整個(gè)數(shù)據(jù)的處理流程給讀者進(jìn)行分享,大致的流程步驟如下:
從整體來(lái)說(shuō),分為三個(gè)階段。第一個(gè)階段是AOI即空間查詢面的構(gòu)建,第二界面是基于AOI面的POI檢索,第三階段就是將兩個(gè)圖層進(jìn)行數(shù)據(jù)疊加后渲染出圖。 下面將結(jié)合流程圖對(duì)重要的處理節(jié)點(diǎn)的邏輯進(jìn)行詳細(xì)的介紹。
二、對(duì)AOI空間范圍查詢實(shí)踐
本節(jié)將重點(diǎn)介紹如何對(duì)AOI數(shù)據(jù)進(jìn)行空間范圍查詢的實(shí)現(xiàn)進(jìn)行說(shuō)明。對(duì)應(yīng)前文提到的三個(gè)階段來(lái)分別展開(kāi),從查詢目標(biāo)的空間查詢構(gòu)建到空間樣式創(chuàng)建,最后對(duì)整體成果進(jìn)行出圖逐級(jí)展開(kāi)。
1、空間查詢構(gòu)建
空間查詢的構(gòu)建比較簡(jiǎn)單,主要包含三個(gè)環(huán)節(jié)的工作,第一個(gè)是將字符串類型的AOI數(shù)據(jù)進(jìn)行解析,剛開(kāi)始是從高德地圖中獲取AOI字符串,由于是高德地圖的坐標(biāo),因此我們首先要對(duì)坐標(biāo)進(jìn)行轉(zhuǎn)換,將其轉(zhuǎn)換為我們熟悉的WGS84的地理坐標(biāo),最后再將轉(zhuǎn)換好的坐標(biāo)來(lái)構(gòu)建一個(gè)完整的查詢面,關(guān)鍵代碼如下所示:
/** * - 將AOI字符串轉(zhuǎn)換成Polygon對(duì)象 * @return */ public static Polygon convertAoi2Polygon(String aoistr) { String [] AOI_Str_Array = aoistr.split(";"); // 獲取GeometryFactory實(shí)例 GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null); Coordinate[] coords = {}; if( AOI_Str_Array.length > 0) { coords = new Coordinate[AOI_Str_Array.length]; } //處理坐標(biāo) for (int i = 0; i < AOI_Str_Array.length; i++) { String loc = AOI_Str_Array[i]; String [] latlon = loc.split(","); double lng = Double.parseDouble(latlon[0]); double lat = Double.parseDouble(latlon[1]); //將高德坐標(biāo)轉(zhuǎn)換成WGS84坐標(biāo) double [] gcj284 = CoordinateTransformUtil.gcj02towgs84(lng, lat); //System.out.println("高德坐標(biāo)轉(zhuǎn)wgs84坐標(biāo)" + gcj284[0] + "=" + gcj284[1]); coords[i] = new Coordinate(gcj284[0], gcj284[1]); } LinearRing shell = geometryFactory.createLinearRing(coords); Polygon polygon = geometryFactory.createPolygon(shell, null); polygon.setSRID(4326);//設(shè)置4326的坐標(biāo) return polygon; }
經(jīng)過(guò)前面的步驟,我們已經(jīng)成功的合成了我們的查詢空間目標(biāo),接下來(lái)就需要基于Geotools來(lái)構(gòu)建對(duì)PostGIS數(shù)據(jù)庫(kù)的空間查詢API, 為了演示我們本地的POI功能,這里我們使用本地的POI信息表,當(dāng)然這張表的數(shù)據(jù)可能與實(shí)際情況有一定的出入,僅做參考。關(guān)鍵代碼如下:
// 2. 創(chuàng)建PostGIS數(shù)據(jù)存儲(chǔ) DataStore dataStore = createPostgisDataStore(); // 3. 二次查詢:用該多邊形查詢點(diǎn)數(shù)據(jù)(如查詢橘子洲景區(qū)內(nèi)的POI) String poiLayerName = "biz_poi_info"; FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(); PropertyName geomProperty = ff.property("geom"); // 點(diǎn)數(shù)據(jù)的幾何列名 // 4、空間關(guān)系:點(diǎn)在多邊形內(nèi)(WITHIN)或相交(INTERSECTS) Filter spatialFilter = ff.within(geomProperty, ff.literal(aoiPolygon)); Query pointQuery = new Query(poiLayerName, spatialFilter); FeatureSource pointSource = dataStore.getFeatureSource(poiLayerName);
為了方便查看是否成功的執(zhí)行了查詢,這里我們將查詢結(jié)果進(jìn)行打印輸出??梢栽诳刂婆_(tái)看到很多的信息輸出,如下圖所示 :
System.out.println("POI數(shù)量:"+points.size()); // 6. 處理結(jié)果 try (FeatureIterator iterator = points.features()) { while (iterator.hasNext()) { Feature pointFeature = iterator.next(); Geometry point = (Geometry) pointFeature.getDefaultGeometryProperty().getValue(); System.out.println("POI坐標(biāo): " + point.getCoordinate()); printSimpleFeatureAttributes((SimpleFeature)pointFeature); // 打印屬性 System.out.println("------------------------------------------------------"); } }
運(yùn)行后在IDE的控制臺(tái)中可以看到以下輸出:
能看到以上結(jié)果說(shuō)明我們的空間查詢函數(shù)構(gòu)建正確,可以正常執(zhí)行。
2、空間樣式創(chuàng)建
為了能讓展示的效果更好,因此我們需要對(duì)獲取的AOI數(shù)據(jù)面和AOI數(shù)據(jù)面內(nèi)的POI數(shù)據(jù)進(jìn)行標(biāo)繪,這里我們需要使用SLD的方式來(lái)進(jìn)行美化,兩個(gè)生成空間樣式的方法如下:
public static Style createDashedBorderStyle() { StyleFactory sf = CommonFactoryFinder.getStyleFactory(); FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(); PolygonSymbolizer symbolizer = sf.createPolygonSymbolizer( sf.createStroke(ff.literal(Color.DARK_GRAY), ff.literal(0.8)), sf.createFill(ff.literal(Color.BLUE), ff.literal(0.8)), // 80%透明度 null ); Rule rule = sf.createRule(); rule.symbolizers().add(symbolizer); FeatureTypeStyle fts = sf.createFeatureTypeStyle(); fts.rules().add(rule); Style style = sf.createStyle(); style.featureTypeStyles().add(fts); return style; } private static Style createPoiStyle() { StyleFactory sf = CommonFactoryFinder.getStyleFactory(); FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(); // 創(chuàng)建圓形符號(hào) Mark mark = sf.createMark(); mark.setWellKnownName(ff.literal("circle")); mark.setFill(sf.createFill(ff.literal(Color.RED))); mark.setStroke(sf.createStroke(ff.literal(Color.BLACK), ff.literal(1))); Graphic graphic = sf.createDefaultGraphic(); graphic.graphicalSymbols().clear(); graphic.graphicalSymbols().add(mark); PointSymbolizer pointSym = sf.createPointSymbolizer(graphic, null); // 新增震級(jí)標(biāo)注 Font font = sf.createFont(ff.literal("楷體"),ff.literal("Regular"),ff.literal("normal"),ff.literal(18)); // 創(chuàng)建文本標(biāo)注 TextSymbolizer textSym = sf.createTextSymbolizer( sf.createFill(ff.literal(Color.WHITE)), new Font[] { font }, null, ff.property("name"), // 標(biāo)注字段 null, null ); // 標(biāo)注位置(點(diǎn)右側(cè)偏移) AnchorPoint anchor = sf.createAnchorPoint(ff.literal(-0.05), ff.literal(0.05)); Displacement displacement = sf.createDisplacement(ff.literal(0.1), ff.literal(0)); Fill textFill = sf.createFill(ff.literal(Color.RED)); Halo halo = sf.createHalo( sf.createFill(ff.literal(Color.WHITE)), ff.literal(1) ); textSym.setFont(font); textSym.setFill(textFill); textSym.setHalo(halo); //新的設(shè)置方法 PointPlacement placement = sf.createPointPlacement(anchor, displacement, ff.literal(0)); textSym.setLabelPlacement(placement); Rule rule = sf.createRule(); rule.symbolizers().add(pointSym); rule.symbolizers().add(textSym); FeatureTypeStyle fts = sf.createFeatureTypeStyle(); fts.rules().add(rule); Style style = sf.createStyle(); style.featureTypeStyles().add(fts); return style; }
創(chuàng)建了以上的樣式之后,我們就可以將樣式和數(shù)據(jù)進(jìn)行融合,這樣就能繪制出漂亮的地圖了。
3、成果出圖
在完成查詢數(shù)據(jù)的轉(zhuǎn)換以及空間查詢的實(shí)現(xiàn)等,接下來(lái)就是揭曉答案的時(shí)候,我們?cè)诖a層面實(shí)現(xiàn)了對(duì)岳麓山的AOI構(gòu)建以及其AOI包圍的POI數(shù)據(jù),關(guān)鍵代碼如下:
SimpleFeatureCollection poiCollection = (SimpleFeatureCollection) pointSource.getFeatures(pointQuery); // 7. 創(chuàng)建樣式 Style aoiStyle = createDashedBorderStyle(); Style poiStyle = createPoiStyle(); // 8. 創(chuàng)建地圖內(nèi)容 MapContent mapContent = new MapContent(); SimpleFeatureSource aoiSfs = convert(aoiPolygon); mapContent.addLayer(new FeatureLayer(aoiSfs, aoiStyle)); mapContent.addLayer(new FeatureLayer(poiCollection, poiStyle)); // 9. 設(shè)置輸出范圍和尺寸 ReferencedEnvelope mapBounds = poiCollection.getBounds(); mapBounds.expandBy(0.2); // 擴(kuò)展邊界 // 10、輸出圖像大?。ɡ纾簩挾葂高度) int width = 1920; // 可根據(jù)需求調(diào)整 // 計(jì)算地理寬高比 double aspectRatio = mapBounds.getWidth() / mapBounds.getHeight(); //根據(jù)比例計(jì)算新高度 int height = (int) Math.round(width / aspectRatio); // 渲染圖片 BufferedImage image = renderMap(mapContent, aoiSfs.getBounds(), width, height); // 保存圖片 ImageIO.write(image, "png", new File("D:/AOI及其包含POI數(shù)據(jù)示意圖.png")); System.out.println("finished"); dataStore.dispose();
使用main函數(shù)或者測(cè)試用例都可以執(zhí)行以上的代碼,程序運(yùn)行后可以在對(duì)應(yīng)的磁盤(pán)目錄下看到以下的成果:
從上圖中可以明顯看到,在岳麓上上,一些POI的分布基本還是比較集中的,比如東北方向, 中間的區(qū)域也是非常多。當(dāng)然,把POI數(shù)據(jù)展現(xiàn)在地圖上還只是一個(gè)階段,要想實(shí)現(xiàn)商業(yè)化,評(píng)估。在有了POI數(shù)據(jù)之后,還要結(jié)合數(shù)據(jù)量,聚類等方面進(jìn)行空間的分析,且聽(tīng)我們下回分解。
三、總結(jié)
以上就是本文的主要內(nèi)容,本博客將深入探討基于Geotools對(duì)PostGIS數(shù)據(jù)庫(kù)的空間查詢實(shí)踐,從連接配置到復(fù)雜空間查詢操作,包括點(diǎn)查詢、區(qū)域范圍查詢以及空間關(guān)系判斷等,全方位展示如何在 Java 環(huán)境下借助 Geotools 駕馭 PostGIS 數(shù)據(jù)庫(kù),實(shí)現(xiàn)高效精準(zhǔn)的空間數(shù)據(jù)檢索,為相關(guān)領(lǐng)域開(kāi)發(fā)者提供實(shí)用的技術(shù)路徑,助力空間數(shù)據(jù)應(yīng)用的創(chuàng)新拓展。如果您也需要對(duì)一個(gè)AOI數(shù)據(jù)進(jìn)行范圍內(nèi)的POI進(jìn)行分析查詢,并且使用的GeoTools的基礎(chǔ)PostGIS訪問(wèn)功能,那么您可以使用以上實(shí)現(xiàn)過(guò)程和代碼進(jìn)行調(diào)試。行文倉(cāng)促,定有不足之處,歡迎各位朋友在評(píng)論區(qū)批評(píng)指正,不勝感激。
到此這篇關(guān)于在Java中基于Geotools對(duì)PostGIS數(shù)據(jù)庫(kù)的空間查詢實(shí)踐的文章就介紹到這了,更多相關(guān)java Geotools PostGIS空間查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring boot啟動(dòng)時(shí)加載外部配置文件的方法
這篇文章主要給大家介紹了關(guān)于spring boot啟動(dòng)時(shí)加載外部配置文件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02SpringBoot實(shí)現(xiàn)前后端分離國(guó)際化的示例詳解
Springboot國(guó)際化可以幫助使用者在不同語(yǔ)言環(huán)境中構(gòu)建應(yīng)用程序,這樣應(yīng)用程序可以有效地適應(yīng)不同語(yǔ)言文化背景下的用戶需求。本文主要介紹了SpringBoot實(shí)現(xiàn)前后端分離國(guó)際化的方法,需要的可以參考一下2023-02-02Java常見(jiàn)報(bào)錯(cuò)類型及解決方案詳細(xì)解析(從異常處理到錯(cuò)誤排查)
這篇文章主要介紹了Java常見(jiàn)報(bào)錯(cuò)類型及解決方案的相關(guān)資料,文中結(jié)合具體案例提供針對(duì)性解決方案,幫助開(kāi)發(fā)者快速定位并修復(fù)問(wèn)題,需要的朋友可以參考下2025-05-05Java獲取調(diào)用當(dāng)前方法的類名或方法名(棧堆信息)的四種方式舉例
在Java編程中我們經(jīng)常需要在運(yùn)行時(shí)獲取當(dāng)前執(zhí)行的方法名稱,這在日志記錄、性能監(jiān)控、調(diào)試等方面非常有用,這篇文章主要給大家介紹了關(guān)于Java獲取調(diào)用當(dāng)前方法的類名或方法名(棧堆信息)的四種方式,需要的朋友可以參考下2024-09-09SpringCloud服務(wù)之間Feign調(diào)用不會(huì)帶上請(qǐng)求頭header的解決方法
在Spring?Cloud中,使用Feign進(jìn)行服務(wù)之間的調(diào)用時(shí),默認(rèn)情況下是不會(huì)傳遞header的,這篇文章給大家介紹SpringCloud服務(wù)之間Feign調(diào)用不會(huì)帶上請(qǐng)求頭header的解決方法,感興趣的朋友一起看看吧2024-01-01Java建造者模式構(gòu)建復(fù)雜對(duì)象的最佳實(shí)踐
建造者模式,是一種對(duì)象構(gòu)建模式?它可以將復(fù)雜對(duì)象的建造過(guò)程抽象出來(lái),使這個(gè)抽象過(guò)程的不同實(shí)現(xiàn)方法可以構(gòu)造出不同表現(xiàn)的對(duì)象。本文將通過(guò)示例講解建造者模式,需要的可以參考一下2023-04-04Java實(shí)現(xiàn)簡(jiǎn)單文件過(guò)濾器功能
下面小編就為大家分享一篇Java實(shí)現(xiàn)簡(jiǎn)單文件過(guò)濾器功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01zuul過(guò)濾器中轉(zhuǎn)發(fā)請(qǐng)求頭的解決方案
這篇文章主要介紹了zuul過(guò)濾器中轉(zhuǎn)發(fā)請(qǐng)求頭的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07