在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.getGeometryFactory(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的方式来进行美化,两个生成空间样式的方法如下:
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.createHalo(sf.createFill(ff.literal(Color.WHITE)),ff.literal(1));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;}
创建了以上的样式之后,我们就可以将样式和数据进行融合,这样就能绘制出漂亮的地图了。
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访问功能,那么您可以使用以上实现过程和代码进行调试。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。