基于Java和GeoTools的Shapefile矢量数据缩略图生成实践

目录

前言

一、关于GeoTools的图片生成

1、关于GtRenderer

2、关于 图像生成架构

3、流式计算绘制

二、全球空间预览生成实战

1、pom.xml中关于图像生成依赖

2、样式设置及地图资源绑定

3、图片生成绘制

4、图片生成测试

三、成果验证

1、全球范围生成

2、我国的范围示意图

 3、日本范围生成

四、总结


前言

        在很多的面向WebGIS的应用开发过程中,我们通常会将空间地理数据,比如Shapefile等矢量数据或者Tiff等栅格数据进行发布到GIS服务器中。为了能直观的展示这些空间矢量数据或者栅格数据的空间范围及大致的数据边界,我们需要将服务化之后的数据进行缩略图的预览展示。如果大家熟悉GeoServer或者ArcgisServer的话,对这个需求一定不陌生。如果我们是自己发布的GIS服务,怎么提供这种类似的空间数据的预览服务呢?在不引入其它的外部服务的情况下。这是个值得思考的问题。

        本文即是在上述的需求场景下出现的。本文主要使用Java语言,讲解如何使用GeoTools这个组件来进行空间Shapefile数据转换成图片,从而实现服务缩略图的功能。文章通过实例的模式讲解预览图片的生成,对于在研究Java的服务预览图片生成的同学和朋友有一定的参考价值。 

一、关于GeoTools的图片生成

        关于GeoTools这个使用Java开发的地理开发组件,它提供了许多丰富的功能和生态来帮助我们实现图片的生成操作。关于GeoTools的整体架构和相关知识,我想在后面的章节中再慢慢介绍。今天先试用它的图形渲染功能来实现空间数据的可视化渲染。本节重点讲解Geotools当中的GTRenderer功能。

1、关于GtRenderer

        关于在GeoTools中使用图像渲染和生成的功能,我们会使用到GtRenderer这个组件。它在GeoTools的官网原文中的介绍如下:

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、关于 图像生成架构

        为了实现空间对象的生成,我们来看下它的后台使用类的生成关系。这个图可以参考管网给出的一个类关系图。

        当然,上图中的相关接口和类中,尤其是在实现类中,实现类的方法和属性的定义是非常多的。但是在上图中做了一定的处理,没有展示那么多的方法。这里,我们会对StreamIngRenderer这个类比官网多一些的详细说明。 详细说明为什么使用流式计算,在实际的绘图过程中,有哪些方法是比较重要的

        在面向对象的学习过程中,我们选择从接口或者抽象类,自顶向下的方式来研究相关的类。因此,在这里,我们首先对抽象的接口GTRenderer进行研究。GTRenderer的源码不多,主要定义的都是关于如何绘制的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);
}

        在上面的方法中,我已经把相关的注释全部删除,在这个渲染接口中其实比较重要的方法其实就那么几个,第一是设置RenderingHints,第二是设置MapContent对象,第三就是执行具体的绘制,即上面代码中的Paint方法,在上面的代码中,一共提供了5个重载的方法,在实际使用的时候,大家根据自己的需要来进行灵活的选择。

3、流式计算绘制

        在定义了图片绘制的接口之后,最重要的是要实现图片的写入,这里需要来看一下GTRenderer对象的子类,即:

        可以看到在StreamingRenderer中定义了非常多的属性以及方法来支撑图片的绘制。 接下来我们看下绘制的相关类,为什么渲染的速度这么快。

 /** The thread pool used to submit the painter workers. */
private ExecutorService threadPool;private PainterThread painterThread;

        在代码中可以看到以上代码片段,在进行绘制的时候,内部会使用线程池的方式来实现,所以这也是为什么处理效率比较高效的原因。

ExecutorService localThreadPool = threadPool;
boolean localPool = false;
if (localThreadPool == null) {localThreadPool = Executors.newSingleThreadExecutor();localPool = true;
}
Future painterFuture = localThreadPool.submit(painterThread);
List<CompositingGroup> compositingGroups = null;

        从这个代码可以看出,这里的处理线程池,我们可以在外部传入一个指定的线程池来进行任务的处理。如果外部没有传入线程池,在内部也会创建出一个线程对象来,这样就保证了一定会有一个线程池来进行处理。可以看到,这里的线程池采用的是Future的方式,与我们常见的Thread的方式有所区别,请大家注意。

        下面给出一个官网提供的将shp文件写出到图片文件的java实例代码。

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 layersthrow 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);}
}

二、全球空间预览生成实战

        在之前的博文中,我们讲解了如何使用Geotools来进行shapefile的空间数据的生成。这里还是以全球的矢量数据为例,结合sld空间样式控制。我们使用GeoTools来生成一张图片,使用上面讲到的GTRenderer对象来实现图片缩略图的生成。

1、pom.xml中关于图像生成依赖

        要想实现在Geotools中进行图像生成,需要引入相关的依赖包。这里给出依赖的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、样式设置及地图资源绑定

        为了让地图更好看,需要我们对地图进行样式的设置,关于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;
}

        加载样式之后,还需要将地图的样式和地图进行绑定。如下面的代码所示:

/**
* 添加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());// 设置编码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();}
}

        上面两个方法都比较简单,而且在之前的博客中也曾经讲过,实现map的读取和样式的绑定。在介绍了上面的两个方法之后,我们来讲述如何进行图片的生成。

3、图片生成绘制

        在讲解图片的绘制时,有几个小小的知识点是需要讲述的。首先是参考坐标,我们在进行绘图时,需要获取系统的参考坐标,然后根据参考坐标,将需要绘制的地图范围进行设置,也就是相当于生成ReferencedEnvelope。然后Graphics这个java的2d图像生成对象将Map对象绘制成图片,从而实现缩略图的生成功能。

/**
* 根据四至、长、宽获取地图内容,并生成图片
* 
* @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");// 设置输出范围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、图片生成测试

        下面我们进行实际的图片生成测试,测试的代码如下:

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("缩略图生成完成,共耗时" + (System.currentTimeMillis() - start) + "ms");
}

        以上就是完成将shp数据转为图片的生成代码,下面我们就可以运行相应的程序来进行调用测试。请注意,这里的bbox是想要生成的区域的空间区域的BBOX,也就是最大矩形外包框的坐标位置。还有需要生成图片的高度和宽度,在指定好这些参数之后就可以调用相应的应用程序来生成。

三、成果验证

        下面来进行成果的验证,我们分别定义了以下的城市,首先是全世界的地图范围图片生成,然后是中国图片生成、日本及美国的地图范围生成。当然每个不同的城市的bbox是不一样的,还要传入不同的bbox值进行验证。

1、全球范围生成

        全球的bbox范围值如下:

 double[] bbox = new double[]
{-179.9999999999999716,-90.0000000188696276,180.0000000000000000,83.6274185352932022};

        接下来看一下全球的生成视图:

2、我国的范围示意图

         我国的经纬度位置范围大致是:

double[] bbox_china = new double[] { 73.409999999999716, -3.5300000188696276, 135.0623000000000000,53.6274185352932022 };

 3、日本范围生成

        日本的经纬度bbox范围如下:

double[] bbox_japan = new double[] { 122.409999999999716, 22.9300000188696276, 151.0623000000000000,47.1274185352932022 };

四、总结

        以上就是本文的主要内容,本文主要使用Java语言,讲解如何使用GeoTools这个组件来进行空间Shapefile数据转换成图片,从而实现服务缩略图的功能。文章通过实例的模式讲解预览图片的生成,对于在研究Java的服务预览图片生成的同学和朋友有一定的参考价值。 阅读本文,不仅可以学习到如何将shp数据转换成图片缩略图,还可以进一步复习geotools包的具体使用。当前,关于图像生成的很多资源不足或者介绍不够深入,这里将比较详细的解释相关知识,同时给出具体的实例。行文仓促,定有不当之处,恳请各位专家学者博友在评论区留下宝贵的意见,万分感激。

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

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

相关文章

redis随笔记

缓存穿透。key不存在。恶意攻击、代码问题。加布隆过滤器&#xff0c;或者为空就返回。 缓存失效&#xff08;击穿&#xff09;。key刚好过期。缓存时间随机数。 缓存雪崩。缓存层宕机&#xff0c;一下子袭击数据库。缓存高可用、限流熔断、提前演练。 布隆过滤器就是一个key…

Python版《超级玛丽+源码》-Python制作超级玛丽游戏

小时候最喜欢玩的小游戏就是超级玛丽了&#xff0c;有刺激有又技巧&#xff0c;通关真的很难&#xff0c;救下小公主还被抓走了&#xff0c;唉&#xff0c;心累&#xff0c;最后还是硬着头皮继续闯&#xff0c;终于要通关了&#xff0c;之后再玩还是没有那么容易&#xff0c;哈…

从并发20到并发120之laravel性能优化

调优成果 遇到问题 单台服务并发20&#xff0c;平均响应时间1124ms&#xff0c;通过htop观察&#xff0c;发现cpu占用率达到100%&#xff08;包括sleep的进程&#xff09;&#xff0c;内存几乎没怎么用。 调优后 单机最大吞吐量达到120 响应时长不超过1000ms 硬件信息 …

EfficientFormer 系列算法

1. EfficientFormer V1 模型 论文地址&#xff1a;https://proceedings.neurips.cc/paper_files/paper/2022/file/5452ad8ee6ea6e7dc41db1cbd31ba0b8-Paper-Conference.pdf EfficientFormer V1 基于 ViT 的模型中使用的网络架构和具体的算子&#xff0c;找到端侧低效的原因。然…

高性能web服务器nginx

目录 nginx简介 服务端 I/O 流程 Nginx 进程结构 Nginx启动流程 nginx的源码编译下载 nginx命令常见参数 nginx的配置文件详解 全局配置优化 nginx的平滑升级和回滚 nginx目录匹配优先级测试&#xff08;因为只支持访问文件&#xff0c;所有不比对匹配目录优先级&…

五、2 移位操作符赋值操作符

1、移位操作符 2、赋值操作符 “ ”赋值&#xff0c;“ ”判断是否相等 1&#xff09;连续赋值 2&#xff09;复合赋值符

C ++初阶:类和对象(上)

目录 &#x1f31e;0.前言 1. 面向过程和面向对象初步认识 2..类的引入与定义 2.1类的引入 2.2类的定义 3.类的访问限定符及其封装 3.1访问限定符 3.2封装 4.类的作用域 4.1加餐和发现 5.类的实例化 6.类对象大小的计算 6.1.内部的存储方式 6.2结构体对齐规则回顾…

一、什么是 mvvm? MVC、MVP、MVVM三种模式的区别与详解

简介 MVC、MVP、MVVM都是常见的软件架构模式。 MVC&#xff08;Model-View-Controller&#xff09;架构模式中&#xff0c;将应用程序分为三个主要部分&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&…

STM32自制手持小风扇实验

1.1 介绍&#xff1a; 实验功能说明&#xff1a;功能&#xff08;1&#xff09;按一下按键小风扇开启&#xff0c;再按一下关闭。 功能&#xff08;2&#xff09;按一下按键小风扇一档风速&#xff0c;再按一下二挡&#xff0c;依次三挡…关闭。 按键模块说明&#xff1a;按下…

什么是AR、VR、MR、XR?

时代背景 近年来随着计算机图形学、显示技术等的发展&#xff0c;视觉虚拟化技术得到了广泛的发展&#xff0c;并且越来越普及化&#xff0c;慢慢的也走入人们的视野。目前市场上视觉虚拟化技术的主流分为这几种 VR、AR、MR、XR。这几项技术并不是最近才出现的&#xff0c;VR的…

路由器VLAN配置(H3C)

路由器VLAN配置&#xff08;H3C&#xff09; 控制页面访问 路由器默认处于192.168.1.1网段&#xff08;可以短按reset重置&#xff09;&#xff0c;如果要直接使用需要设置静态IP处于同一网段&#xff1b; 对路由器进行配置也要将电脑IP手动设置为同一网段&#xff1b; 默…

执行rasa shell 遇到asyncio.exceptions.TimeoutError报错

在《树莓派3B运行rasa init和rasa shell遇到的tensorflow报错总结》一文中&#xff0c;我遇到的第7个报错是首次运行rasa shell时候碰到的。按照我在文中记录的解决方案&#xff0c;处理成功。 结果&#xff0c;今天我又一次遇到了asyncio - Task exception was never retrie…

91. 解码方法 -dp4

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/decode-ways/description/ 示例 1&#xff1a; 输入&#xff1a;s &…

「字符串」前缀函数|KMP匹配:规范化next数组 / LeetCode 28(C++)

概述 为什么大家总觉得KMP难&#xff1f;难的根本就不是这个算法本身。 在互联网上你可以见到八十种KMP算法的next数组定义和模式串回滚策略&#xff0c;把一切都懂得特别混乱。很多时候初学者的难点根本不在于这个算法本身&#xff0c;而是它令人痛苦的百花齐放的定义。 有…

ee trade:黄金投资与股票投资的区别

黄金和股票&#xff0c; 是金融市场中两种常见的投资工具&#xff0c; 它们拥有截然不同的特点和风险&#xff0c; 了解它们的差异&#xff0c; 可以帮助投资者制定更合理的投资策略。 一、 投资性质&#xff1a; 避险与成长&#xff0c; 两种投资方向 黄金&#xff1a; 被视…

【C++】入门篇一

【C】入门篇一 一 .缺省参数1.缺省参数的概念2. 缺省参数分类 二. 函数重载1. 函数重载概念2.函数重载代码举例 三.引用1.引用的概念2. 引用特性3. 常引用4. 使用场景(1). 做参数(2). 做返回值 5. 传值、传引用效率比较6. 引用和指针的区别7.引用和指针的不同点 一 .缺省参数 …

如何将MySQL迁移到TiDB,完成无缝业务切换?

当 MySQL 数据库的单表数据量达到了亿级&#xff0c;会发生什么&#xff1f; 这个现象表示公司的业务上了一个台阶&#xff0c;随着数据量的增加&#xff0c;公司规模也进一步扩大了&#xff0c;是非常喜人的一个改变 &#xff0c;然而随之而来的其他变化&#xff0c;就没那么…

Python | Leetcode Python题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; class Solution:def maxEnvelopes(self, envelopes: List[List[int]]) -> int:if not envelopes:return 0n len(envelopes)envelopes.sort(keylambda x: (x[0], -x[1]))f [1] * nfor i in range(n):for j in range(i):if envelopes[j]…

全液冷服务器革命:CPU、内存、PCIe高效散热新方案

在国家十四五规划大力发展数字经济的背景下&#xff0c;数据中心作为算力的核心载体&#xff0c;其基础设施成为支撑数字经济的“数字底座”&#xff0c;但同时也面临巨大的碳排放压力。随着芯片与服务器功耗的上升&#xff0c;单机柜功率密度不断增大&#xff0c;传统风冷散热…

深度学习设计模式之享元设计模式

文章目录 前言一、介绍二、特点三、详细介绍1.核心组成2.代码示例3.优缺点优点缺点 4.使用场景 总结 前言 享元设计模式主要用于减少创建对象的数量&#xff0c;以减少内存占用&#xff0c;提高性能。 一、介绍 享元设计模式&#xff08;Flyweight Pattern&#xff09;是一种…