在Java中基于Geotools对PostGIS数据库的空间查询实践教程
目录
- 前言
- 一、相关技术背景介绍
- 1、评价对象AOI
- 2、数据处理流程
- 二、对AOI空间范围查询实践
- 1、空间查询构建
- 2、空间样式创建
- 3、成果出图
- 三、总结
前言
在当今数字化浪潮下,空间数据的应用价值日益凸显,从城市规划到环境监测,从物流配送到地理信息系统(GIS)开发,精准、高效的空间数据查询成为关键环节。而 Java 作为广泛应用的编程语言,在与地理空间技术的融合中展现出独特魅力。Geotools 作为开源的 Java GIS 库,为 Java 开发者提供了强大的地理空间数据处理能力,犹如一把开启空间数据宝藏之门的钥匙。PostGIS 则是 PostgreSQL 数据库的空间扩展,能够存储和处理复杂的空间数据类型,是空间数据存储与管理的得力助手。将 Geotools 与 PostGIS 结合,意味着我们可以利用 Java 强大的编程生态和 Geotools 丰富的 GIS 功能,深度挖掘 PostGIS 中海量空间数据的潜力。
比如我们需要对某一个风景区的商业开发程度进行评价,需要做的第一件事就是根据风景区的范围也就是AOI数据,查询这个AOI数据的范围内所有的POI数据,然后根据不同的POI类型进行分类,从而计算不同的POI类型在景区内的分布情况,加上一些空间分析和相关计算,从而可以对当前景区的一个商业情况进行初步的评估,从而为景区的优质发展提供指导和参考,以下图为例:
本博客将深入探讨这一实践,从连接配置到复杂空间查询操作,包括点查询、区域范围查询以及空间关系判断等,全方位展示如何在 Java 环境下借助 Geotools 驾驭 PostGIS 数据库,实现高效精准的空间数据检索,为相关领域开发者提供实用的技术路径,助力空间数据应用的创新拓展。
一、相关技术背景介绍
这里以长沙岳麓山风景区为例,主要介绍如何得出岳麓山及风景区内的POI数据的分布情况。从而为下一步对该风景区的商业及人文指数进行评价。由此我们需要对岳麓山风景区的空间范围数据,以及有了AOI范围后对其范围内的POI数据进行检索的流程进行简单说明。
1、评价对象AOI
关于如何获取AOI数据,在之前的博文中我们曾经进行过相应的说明。大家可以从相应的官方渠道获取。也可以从互联网图源来获取,比如从百度地图或者高德地图中也可以获取数据。以高德地图为例,在下面的查询界面中可以查看到具体的数据:
我们可以在网络请求的地方对这个空间面数据进行调试,也可以将这个数据复制到我们的开发环境中,从而可以实现离线的空间计算。如下图所示:
上面的数据也是本博客的目标AOI范围,我们后续的所有工作都将围绕这个区域来展开。 因此,如果对AOI数据不是很了解,建议先对相关知识又一个大体的认识以便于更好的掌握相关知识。
2、数据处理流程
为了让大家对整个数据处理的流程有一个简单的认识,这里将整个数据的处理流程给读者进行分享,大致的流程步骤如下:
从整体来说,分为三个阶段。第一个阶段是AOI即空间查询面的构建,第二界面是基于AOI面的POI检索,第三阶段就是将两个图层进行数据叠加后渲染出图。 下面将结合流程图对重要的处理节点的逻辑进行详细的介绍。
二、对AOI空间范围查询实践
本节将重点介绍如何对AOI数据进行空间范围查询的实现进行说明。对应前文提到的三个阶段来分别展开,从查询目标的空间查询构建到空间样式创建,最后对整体成果进行出图逐级展开。
1、空间查询构建
空间查询的构建比较简单,主要包含三个环节的工作,第一个是将字符串类型的AOI数据进行解析,刚开始是从高德地图中获取AOI字符串,由于是高德地图的坐标,因此我们首先要对坐标进行转换,将其转换为我们熟悉的WGS84的地理坐标,最后再将转换好的坐标来构建一个完整的查询面,关键代码如下所示:
/** * - 将AOI字符串转换成Polygon对象 * @return */ public static Polygon convertAoi2Polygon(String aoistr) { String [] AOI_Str_Array = aoistr.split(";"); // 获取GeometryFactory实例 GeometryFactory geometryFactory = JTSFactoryFinder.getGe编程客栈ometryFactory(null); Coordinate[] coords = {}; if( AOI_Str_Array.length > 0) { coords = new Coordinate[AOI_Str_Array.length]; } //处理坐标 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]); //将高德坐标转换成WGS84坐标 double [] gcj284 = CoordinateTransformUtil.gcj02towgs84(lng, lat); //System.out.println("高德坐标转wgs84坐标" + 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);//设置4326的坐标 return polygon; }
经过前面的步骤,我们已经成功的合成了我们的查询空间目标,接下来就需要基于Geotools来构建对PostGIS数据库的空间查询API, 为了演示我们本地的POI功能,这里我们使用本地的POI信息表,当然这张表的数据可能与实际情况有一定的出入,仅做参考。关键代码如下:
// 2. 创建PostGIS数据存储 DataStore dataStore = createPostgisDataStore(); // 3. 二次查询:用该多边形查询点数据(如查询橘子洲景区内的POI) String poiLayerName = "biz_poi_info"; FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(); PropertyName geomProperty = ff.property("geom"); // 点数据的几何列名 // 4、空间关系:点在多边形内(WITHIN)或相交(INTERSECTS) Filter spatialFilter = ff.within(geomProperty, ff.literal(aoiPolygon)); Query pointQuery = new Query(poiLayerName, spatialFilter); FeatureSource pointSource = dataStore.getFeatureSource(poiLayerName);
为了方便查看是否成功的执行了查询,这里我们将查询结果进行打印输出。可以在控制台看到很多的信息输出,如下图所示 :
System.out.println("POI数量:"+points.size()); // 6. 处理结果 try (FeatureIterator iterator = points.features()) { while (iterator.hasNext()) { Feature pointFeature = iterator.next(); Geometry point = (Geometry) pointFeature.getDefaultGeometryProperty().getValue(); System.out.println("POI坐标: " + point.getCoordinate()); printSimpleFeatureAttributes((SimpleFeature)pointFeature); // 打印属性 System.out.println("------------------------------------------------------"); } }
运行后在IDE的控制台中可以看到以下输出:
能看到以上结果说明我们的空间查询函数构建正确,可以正常执行。
2、空间样式创建
为了能让展示的效果更好,因此我们需要对获取的AOI数据面和AOI数据面内的POI数据进行标绘,这里我们需要使用SLD的方式来进行美化,两个生成空间样式的方php法如下:
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(); // 创建圆形符号 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); // 新增震级标注 Font font = sf.createFont(ff.literal("楷体"),ff.literal("Regular"),ff.literal("normal"),ff.literal(18)); // 创建文本标注 TextSymbolizer textSym = sf.createTextSymbolizer( sf.createFill(ff.literal(Color.WHITE)), new Font[] { font }, null, ff.property("name"), // 标注字段 null, null ); // 标注位置(点右侧偏移) 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.createjsHalo( sf.createFill(ff.literal(Color.WHITE)), ff.literal(1) ); android textSym.setFont(font); textSym.setFill(textFill); textSym.setHalo(halo); //新的设置方法 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; javascript }
创建了以上的样式之后,我们就可以将样式和数据进行融合,这样就能绘制出漂亮的地图了。
3、成果出图
在完成查询数据的转换以及空间查询的实现等,接下来就是揭晓答案的时候,我们在代码层面实现了对岳麓山的AOI构建以及其AOI包围的POI数据,关键代码如下:
SimpleFeatureCollection poiCollection = (SimpleFeatureCollection) pointSource.getFeatures(pointQuery); // 7. 创建样式 Style aoiStyle = createDashedBorderStyle(); Style poiStyle = createPoiStyle(); // 8. 创建地图内容 MapContent mapContent = new MapContent(); SimpleFeatureSource aoiSfs = convert(aoiPolygon); mapContent.addLayer(new FeatureLayer(aoiSfs, aoiStyle)); mapContent.addLayer(new FeatureLayer(poiCollection, poiStyle)); // 9. 设置输出范围和尺寸 ReferencedEnvelope mapBounds = poiCollection.getBounds(); mapBounds.expandBy(0.2); // 扩展边界 // 10、输出图像大小(例如:宽度x高度) int width = 1920; // 可根据需求调整 // 计算地理宽高比 double ASPectRatio = mapBounds.getWidth() / mapBounds.getHeight(); //根据比例计算新高度 int height = (int) Math.round(width / aspectRatio); // 渲染图片 BufferedImage image = renderMap(mapContent, aoiSfs.getBounds(), width, height); // 保存图片 ImageIO.write(image, "png", new File("D:/AOI及其包含POI数据示意图.png")); System.out.println("finished"); dataStore.dispose();
使用main函数或者测试用例都可以执行以上的代码,程序运行后可以在对应的磁盘目录下看到以下的成果:
从上图中可以明显看到,在岳麓上上,一些POI的分布基本还是比较集中的,比如东北方向, 中间的区域也是非常多。当然,把POI数据展现在地图上还只是一个阶段,要想实现商业化,评估。在有了POI数据之后,还要结合数据量,聚类等方面进行空间的分析,且听我们下回分解。
三、总结
以上就是本文的主要内容,本博客将深入探讨基于Geotools对PostGIS数据库的空间查询实践,从连接配置到复杂空间查询操作,包括点查询、区域范围查询以及空间关系判断等,全方位展示如何在 Java 环境下借助 Geotools 驾驭 PostGIS 数据库,实现高效精准的空间数据检索,为相关领域开发者提供实用的技术路径,助力空间数据应用的创新拓展。如果您也需要对一个AOI数据进行范围内的POI进行分析查询,并且使用的GeoTools的基础PostGIS访问功能,那么您可以使用以上实现过程和代码进行调试。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。
到此这篇关于在Java中基于Geotools对PostGIS数据库的空间查询实践的文章就介绍到这了,更多相关java Geotools PostGIS空间查询内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论