基于Java和GeoTools的Shapefile矢量數(shù)據(jù)縮略圖生成實(shí)踐
前言
在很多的面向WebGIS的應(yīng)用開發(fā)過程中,我們通常會將空間地理數(shù)據(jù),比如Shapefile等矢量數(shù)據(jù)或者Tiff等柵格數(shù)據(jù)進(jìn)行發(fā)布到GIS服務(wù)器中。為了能直觀的展示這些空間矢量數(shù)據(jù)或者柵格數(shù)據(jù)的空間范圍及大致的數(shù)據(jù)邊界,我們需要將服務(wù)化之后的數(shù)據(jù)進(jìn)行縮略圖的預(yù)覽展示。如果大家熟悉GeoServer或者ArcgisServer的話,對這個(gè)需求一定不陌生。如果我們是自己發(fā)布的GIS服務(wù),怎么提供這種類似的空間數(shù)據(jù)的預(yù)覽服務(wù)呢?在不引入其它的外部服務(wù)的情況下。這是個(gè)值得思考的問題。
本文即是在上述的需求場景下出現(xiàn)的。本文主要使用Java語言,講解如何使用GeoTools這個(gè)組件來進(jìn)行空間Shapefile數(shù)據(jù)轉(zhuǎn)換成圖片,從而實(shí)現(xiàn)服務(wù)縮略圖的功能。文章通過實(shí)例的模式講解預(yù)覽圖片的生成,對于在研究Java的服務(wù)預(yù)覽圖片生成的同學(xué)和朋友有一定的參考價(jià)值。
一、關(guān)于GeoTools的圖片生成
關(guān)于GeoTools這個(gè)使用Java開發(fā)的地理開發(fā)組件,它提供了許多豐富的功能和生態(tài)來幫助我們實(shí)現(xiàn)圖片的生成操作。關(guān)于GeoTools的整體架構(gòu)和相關(guān)知識,我想在后面的章節(jié)中再慢慢介紹。今天先試用它的圖形渲染功能來實(shí)現(xiàn)空間數(shù)據(jù)的可視化渲染。本節(jié)重點(diǎn)講解Geotools當(dāng)中的GTRenderer功能。
1、關(guān)于GtRenderer
關(guān)于在GeoTools中使用圖像渲染和生成的功能,我們會使用到GtRenderer這個(gè)組件。它在GeoTools的官網(wǎng)原文中的介紹如下:
GTRenderer
renderer is the reason why you signed up for this whole GeoTools experience; you want to see a Map.
GTRenderer
is actually an interface; currently there are two implementations:
2、關(guān)于 圖像生成架構(gòu)
為了實(shí)現(xiàn)空間對象的生成,我們來看下它的后臺使用類的生成關(guān)系。這個(gè)圖可以參考管網(wǎng)給出的一個(gè)類關(guān)系圖。
當(dāng)然,上圖中的相關(guān)接口和類中,尤其是在實(shí)現(xiàn)類中,實(shí)現(xiàn)類的方法和屬性的定義是非常多的。但是在上圖中做了一定的處理,沒有展示那么多的方法。這里,我們會對StreamIngRenderer這個(gè)類比官網(wǎng)多一些的詳細(xì)說明。 詳細(xì)說明為什么使用流式計(jì)算,在實(shí)際的繪圖過程中,有哪些方法是比較重要的
在面向?qū)ο蟮膶W(xué)習(xí)過程中,我們選擇從接口或者抽象類,自頂向下的方式來研究相關(guān)的類。因此,在這里,我們首先對抽象的接口GTRenderer進(jìn)行研究。GTRenderer的源碼不多,主要定義的都是關(guān)于如何繪制的API,這里直接給出它的代碼:
package org.geotools.renderer; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.util.Map; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.MapContent; import org.locationtech.jts.geom.Envelope; public interface GTRenderer { public void stopRendering(); public void addRenderListener(RenderListener listener); public void removeRenderListener(RenderListener listener); public void setJava2DHints(RenderingHints hints); public RenderingHints getJava2DHints(); public void setRendererHints(Map<?, ?> hints); public Map<Object, Object> getRendererHints(); public void setMapContent(MapContent mapContent); public MapContent getMapContent(); public void paint(Graphics2D graphics, Rectangle paintArea, AffineTransform worldToScreen); public void paint(Graphics2D graphics, Rectangle paintArea, Envelope mapArea); public void paint(Graphics2D graphics, Rectangle paintArea, ReferencedEnvelope mapArea); public void paint( Graphics2D graphics,Rectangle paintArea,Envelope mapArea,AffineTransform worldToScreen); public void paint(Graphics2D graphics, Rectangle paintArea,ReferencedEnvelope mapArea,AffineTransform worldToScreen); }
在上面的方法中,我已經(jīng)把相關(guān)的注釋全部刪除,在這個(gè)渲染接口中其實(shí)比較重要的方法其實(shí)就那么幾個(gè),第一是設(shè)置RenderingHints,第二是設(shè)置MapContent對象,第三就是執(zhí)行具體的繪制,即上面代碼中的Paint方法,在上面的代碼中,一共提供了5個(gè)重載的方法,在實(shí)際使用的時(shí)候,大家根據(jù)自己的需要來進(jìn)行靈活的選擇。
3、流式計(jì)算繪制
在定義了圖片繪制的接口之后,最重要的是要實(shí)現(xiàn)圖片的寫入,這里需要來看一下GTRenderer對象的子類,即:
可以看到在StreamingRenderer中定義了非常多的屬性以及方法來支撐圖片的繪制。 接下來我們看下繪制的相關(guān)類,為什么渲染的速度這么快。
/** The thread pool used to submit the painter workers. */ private ExecutorService threadPool; private PainterThread painterThread;
在代碼中可以看到以上代碼片段,在進(jìn)行繪制的時(shí)候,內(nèi)部會使用線程池的方式來實(shí)現(xiàn),所以這也是為什么處理效率比較高效的原因。
ExecutorService localThreadPool = threadPool; boolean localPool = false; if (localThreadPool == null) { localThreadPool = Executors.newSingleThreadExecutor(); localPool = true; } Future painterFuture = localThreadPool.submit(painterThread); List<CompositingGroup> compositingGroups = null;
從這個(gè)代碼可以看出,這里的處理線程池,我們可以在外部傳入一個(gè)指定的線程池來進(jìn)行任務(wù)的處理。如果外部沒有傳入線程池,在內(nèi)部也會創(chuàng)建出一個(gè)線程對象來,這樣就保證了一定會有一個(gè)線程池來進(jìn)行處理??梢钥吹剑@里的線程池采用的是Future的方式,與我們常見的Thread的方式有所區(qū)別,請大家注意。
下面給出一個(gè)官網(wǎng)提供的將shp文件寫出到圖片文件的java實(shí)例代碼。
public void saveImage(final MapContent map, final String file, final int imageWidth) { GTRenderer renderer = new StreamingRenderer(); renderer.setMapContent(map); Rectangle imageBounds = null; ReferencedEnvelope mapBounds = null; try { mapBounds = map.getMaxBounds(); double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0); imageBounds = new Rectangle( 0, 0, imageWidth, (int) Math.round(imageWidth * heightToWidth)); } catch (Exception e) { // failed to access map layers throw new RuntimeException(e); } BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_RGB); Graphics2D gr = image.createGraphics(); gr.setPaint(Color.WHITE); gr.fill(imageBounds); try { renderer.paint(gr, imageBounds, mapBounds); File fileToSave = new File(file); ImageIO.write(image, "jpeg", fileToSave); } catch (IOException e) { throw new RuntimeException(e); } }
二、全球空間預(yù)覽生成實(shí)戰(zhàn)
在之前的博文中,我們講解了如何使用Geotools來進(jìn)行shapefile的空間數(shù)據(jù)的生成。這里還是以全球的矢量數(shù)據(jù)為例,結(jié)合sld空間樣式控制。我們使用GeoTools來生成一張圖片,使用上面講到的GTRenderer對象來實(shí)現(xiàn)圖片縮略圖的生成。
1、pom.xml中關(guān)于圖像生成依賴
要想實(shí)現(xiàn)在Geotools中進(jìn)行圖像生成,需要引入相關(guān)的依賴包。這里給出依賴的maven配置,請注意,這是基本的pom.xml,其它的geotools的依賴,請自行引入。
<dependency> <groupId>org.geotools</groupId> <artifactId>gt-referencing</artifactId> <version>${geotools.version}</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-epsg-hsql</artifactId> <version>${geotools.version}</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-epsg-extension</artifactId> <version>${geotools.version}</version> </dependency>
2、樣式設(shè)置及地圖資源綁定
為了讓地圖更好看,需要我們對地圖進(jìn)行樣式的設(shè)置,關(guān)于SLD的解析和使用,我們不再贅述,直接提供代碼。大家可以直接拷貝過去使用。
private Style createStyleFromSld(String uri) throws XPathExpressionException, IOException, SAXException, ParserConfigurationException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document xmlDocument = db.parse(uri); XPath xPath = XPathFactory.newInstance().newXPath(); String version = xPath.compile("/StyledLayerDescriptor/@version").evaluate(xmlDocument); Configuration sldConf; if (version != null && version.startsWith("1.1")) { sldConf = new org.geotools.sld.v1_1.SLDConfiguration(); } else { sldConf = new org.geotools.sld.SLDConfiguration(); } StyledLayerDescriptor sld = (StyledLayerDescriptor) new org.geotools.xsd.DOMParser(sldConf, xmlDocument) .parse(); NamedLayer l = (NamedLayer) sld.getStyledLayers()[0]; Style style = l.getStyles()[0]; return style; }
加載樣式之后,還需要將地圖的樣式和地圖進(jìn)行綁定。如下面的代碼所示:
/** * 添加shp文件 * * @param shpPath */ @SuppressWarnings("deprecation") public void addShapeLayer(String shpPath, String sldPath) { try { File file = new File(shpPath); ShapefileDataStore shpDataStore = null; shpDataStore = new ShapefileDataStore(file.toURL()); // 設(shè)置編碼 Charset charset = Charset.forName("GBK"); shpDataStore.setCharset(charset); String typeName = shpDataStore.getTypeNames()[0]; SimpleFeatureSource featureSource = null; featureSource = shpDataStore.getFeatureSource(typeName); // SLD的方式 Style style = null; try { style = createStyleFromSld(sldPath); } catch (Exception e) { } Layer layer = new FeatureLayer(featureSource, style); map.addLayer(layer); } catch (Exception e) { e.printStackTrace(); } }
上面兩個(gè)方法都比較簡單,而且在之前的博客中也曾經(jīng)講過,實(shí)現(xiàn)map的讀取和樣式的綁定。在介紹了上面的兩個(gè)方法之后,我們來講述如何進(jìn)行圖片的生成。
3、圖片生成繪制
在講解圖片的繪制時(shí),有幾個(gè)小小的知識點(diǎn)是需要講述的。首先是參考坐標(biāo),我們在進(jìn)行繪圖時(shí),需要獲取系統(tǒng)的參考坐標(biāo),然后根據(jù)參考坐標(biāo),將需要繪制的地圖范圍進(jìn)行設(shè)置,也就是相當(dāng)于生成ReferencedEnvelope。然后Graphics這個(gè)java的2d圖像生成對象將Map對象繪制成圖片,從而實(shí)現(xiàn)縮略圖的生成功能。
/** * 根據(jù)四至、長、寬獲取地圖內(nèi)容,并生成圖片 * * @param paras * @param imgPath */ public void getMapContent(Map<String, Object> paras, String imgPath) { try { double[] bbox = (double[]) paras.get("bbox"); double x1 = bbox[0], y1 = bbox[1], x2 = bbox[2], y2 = bbox[3]; int width = (int) paras.get("width"), height = (int) paras.get("height"); // 設(shè)置輸出范圍 CoordinateReferenceSystem crs = DefaultGeographicCRS.WGS84; // CoordinateReferenceSystem crs = CRS.decode("EPSG:4326"); ReferencedEnvelope mapArea = new ReferencedEnvelope(x1, x2, y1, y2, crs); // 初始化渲染器 StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(map); // 初始化輸出圖像 BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics g = bi.getGraphics(); ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); Rectangle rect = new Rectangle(0, 0, width, height); // 繪制地圖 sr.paint((Graphics2D) g, rect, mapArea); // 將BufferedImage變量寫入文件中。 ImageIO.write(bi, "png", new File(imgPath)); } catch (Exception e) { e.printStackTrace(); } }
4、圖片生成測試
下面我們進(jìn)行實(shí)際的圖片生成測試,測試的代碼如下:
public static void main(String[] args) { long start = System.currentTimeMillis(); Shp2Image shp2img = new Shp2Image(); String shpPath = "F:/wzh_workspace_20210320/geotools-fx/src/main/resources/maps/countries.shp"; String sldPath = "F:/wzh_workspace_20210320/geotools-fx/src/main/resources/maps/countries1.1.0-2.sld"; String imgPath = "D:/countries0819-美國.png"; Map<String, Object> paras = new HashMap<String, Object>(); double[] bbox_usa = new double[] { -145.40999, 9.93976, -65.062300,81.12722 }; paras.put("bbox", bbox_usa); paras.put("width", 1020); paras.put("height", 800); shp2img.addShapeLayer(shpPath, sldPath); shp2img.getMapContent(paras, imgPath); System.out.println("縮略圖生成完成,共耗時(shí)" + (System.currentTimeMillis() - start) + "ms"); }
以上就是完成將shp數(shù)據(jù)轉(zhuǎn)為圖片的生成代碼,下面我們就可以運(yùn)行相應(yīng)的程序來進(jìn)行調(diào)用測試。請注意,這里的bbox是想要生成的區(qū)域的空間區(qū)域的BBOX,也就是最大矩形外包框的坐標(biāo)位置。還有需要生成圖片的高度和寬度,在指定好這些參數(shù)之后就可以調(diào)用相應(yīng)的應(yīng)用程序來生成。
三、成果驗(yàn)證
下面來進(jìn)行成果的驗(yàn)證,我們分別定義了以下的城市,首先是全世界的地圖范圍圖片生成,然后是中國圖片生成、日本及美國的地圖范圍生成。當(dāng)然每個(gè)不同的城市的bbox是不一樣的,還要傳入不同的bbox值進(jìn)行驗(yàn)證。
1、全球范圍生成
全球的bbox范圍值如下:
double[] bbox = new double[]
{-179.9999999999999716,-90.0000000188696276,180.0000000000000000,83.6274185352932022};
接下來看一下全球的生成視圖:
2、我國的范圍示意圖
我國的經(jīng)緯度位置范圍大致是:
double[] bbox_china = new double[] { 73.409999999999716, -3.5300000188696276, 135.0623000000000000,53.6274185352932022 };
3、日本范圍生成
日本的經(jīng)緯度bbox范圍如下:
double[] bbox_japan = new double[] { 122.409999999999716, 22.9300000188696276, 151.0623000000000000,47.1274185352932022 };
四、總結(jié)
以上就是本文的主要內(nèi)容,本文主要使用Java語言,講解如何使用GeoTools這個(gè)組件來進(jìn)行空間Shapefile數(shù)據(jù)轉(zhuǎn)換成圖片,從而實(shí)現(xiàn)服務(wù)縮略圖的功能。文章通過實(shí)例的模式講解預(yù)覽圖片的生成,對于在研究Java的服務(wù)預(yù)覽圖片生成的同學(xué)和朋友有一定的參考價(jià)值。 閱讀本文,不僅可以學(xué)習(xí)到如何將shp數(shù)據(jù)轉(zhuǎn)換成圖片縮略圖,還可以進(jìn)一步復(fù)習(xí)geotools包的具體使用。當(dāng)前,關(guān)于圖像生成的很多資源不足或者介紹不夠深入,這里將比較詳細(xì)的解釋相關(guān)知識,同時(shí)給出具體的實(shí)例。行文倉促,定有不當(dāng)之處,懇請各位專家學(xué)者博友在評論區(qū)留下寶貴的意見,萬分感激。
到此這篇關(guān)于基于Java和GeoTools的Shapefile矢量數(shù)據(jù)縮略圖生成實(shí)踐的文章就介紹到這了,更多相關(guān)Java和GeoTools的Shapefile矢量數(shù)據(jù)縮略圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解spring mvc4使用及json 日期轉(zhuǎn)換解決方案
本篇文章主要介紹了spring mvc4使用及json 日期轉(zhuǎn)換解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01Spring Boot 整合 MyBatis 連接數(shù)據(jù)庫及常見問題
MyBatis 是一個(gè)優(yōu)秀的持久層框架,支持定制化 SQL、存儲過程以及高級映射,下面詳細(xì)介紹如何在 Spring Boot 項(xiàng)目中整合 MyBatis 并連接數(shù)據(jù)庫,感興趣的朋友一起看看吧2025-03-03Springboot+aop實(shí)現(xiàn)配置多數(shù)據(jù)源的示例代碼
本文介紹了如何使用SpringAOP和注解實(shí)現(xiàn)動態(tài)數(shù)據(jù)源切換,通過自定義注解和ThreadLocal存儲數(shù)據(jù)上下文信息,重寫AbstractRoutingDataSource類并使用自定義切面來實(shí)現(xiàn)動態(tài)數(shù)據(jù)源的切換,感興趣的可以了解一下2024-11-11java.io.EOFException: Unexpected end of
本文主要介紹了java.io.EOFException: Unexpected end of ZLIB input stream異常解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Spring中IoC優(yōu)點(diǎn)與缺點(diǎn)解析
這篇文章主要為大家詳細(xì)解析了Spring中IoC優(yōu)點(diǎn)與缺點(diǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Redis中String字符串和sdshdr結(jié)構(gòu)體超詳細(xì)講解
這篇文章主要介紹了Redis中String字符串和sdshdr結(jié)構(gòu)體,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-04-04