Spatial4j是一款java编写的空间计算开源库,支持ASL开源协议,支持地理空间计算。
Spatial4j主要有三个主要功能:1)支持基于平面几何或地理空间的若干图形;2)支持距离计算和形状的计算:计算边界框、面积、图形间的关系等 3)解析WKT、GeoJSON等空间描述标准格式
Spatial4j利用了部分JTS的能力(JTS是最流行的java空间计算库),例如多边形是基于JTS实现的。相比与JTS,spatial4j还支持了圆以及地理空间计算。用JTS,通常用多边形近似替代了圆的计算,对结果会造成一定误差,而Spatial4j支持了圆;另外,地理空间计算的应用现在十分广泛,用spatial4j会更加方便。
由于官网只有一些简要介绍,本文介绍了一些实战用例,旨在帮助需要的同学更快上手。
如果涉及到多边形,需要引用JTS;如果用到第四节介绍的GeoJSON序列化或反序列化,需要依赖noggit
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
<version>0.8</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>org.noggit</groupId>
<artifactId>noggit</artifactId>
<version>0.8</version>
</dependency>
接下来直接进入实战,先开始平面几何的例子。
首先是点、圆、矩形等简单图形的定义,并计算了图形的面积、边界框、图形间的关系。
void testEuclidean() {
// 生成平面几何的context
SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
nonGeoContextFactory.geo = false;
SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);
// 定义两个点
Point pointA = new PointImpl(2,6, nonGeoContext);
Point pointB = new PointImpl(6,5, nonGeoContext);
// 定义圆
Circle circleA = new CircleImpl(new PointImpl(6,4, nonGeoContext), 2, nonGeoContext);
// 判断圆与点的关系
System.out.println("circleA relate pointA: " + circleA.relate(pointA));
System.out.println("circleA relate pointB: " + circleA.relate(pointB));
// 计算圆的面积和圆的边界框
System.out.println(String.format("circleA area: %.2f", circleA.getArea(nonGeoContext)));
Rectangle boundingBoxA = circleA.getBoundingBox();
System.out.println(String.format("circleA bounding box leftDown(%.2f, %.2f), rightUp:(%.2f, %.2f)",
boundingBoxA.getMinX(), boundingBoxA.getMinY(), boundingBoxA.getMaxX(), boundingBoxA.getMaxY()));
// 定义矩形,计算矩形与圆的关系
Rectangle rectangleA = new RectangleImpl(3,5,4,8,nonGeoContext);
System.out.println("rectangleA relate circleA: " + rectangleA.relate(circleA));
}
输出结果:
circleA relate pointA:DISJOINT
circleA relate pointB:CONTAINS
circleA area:12.57
circleA bounding box leftDown(4.00, 2.00), rightUp:(8.00, 6.00)
rectangleA relate circleA:INTERSECTS
spatial4j利用JTS的多边形计算能力。
下面子的例子分别定义了一个凹多边形和一个凸多边形,计算了多边形的面积和多边形间的关系。
void testPolygon() {
// 基于JTS的context
JtsSpatialContextFactory jtsSpatialContextFactory = new JtsSpatialContextFactory();
jtsSpatialContextFactory.geo = false;
JtsSpatialContext jtsSpatialContext = jtsSpatialContextFactory.newSpatialContext();
JtsShapeFactory jtsShapeFactory = jtsSpatialContext.getShapeFactory();
// 定义凹多边形A
ShapeFactory.PolygonBuilder polygonBuilderA = jtsShapeFactory.polygon();
Shape polygonA = polygonBuilderA
.pointXY(1, 1)
.pointXY(2, 2)
.pointXY(3, 1)
.pointXY(5,3)
.pointXY(3,5)
.pointXY(2, 4)
.pointXY(1,5)
.pointXY(1, 1)
.build();
// 定义凸多边形B
ShapeFactory.PolygonBuilder polygonBuilderB = jtsShapeFactory.polygon();
Shape polygonB = polygonBuilderB
.pointXY(3, 3)
.pointXY(5, 1)
.pointXY(6, 3)
.pointXY(5, 5)
.pointXY(3, 3)
.build();
// 计算多边形面积,多边形的关系
System.out.println(String.format("polygonA area: %.2f", polygonA.getArea(jtsSpatialContext)));
System.out.println("polygonA relate polygonB: " + polygonA.relate(polygonB));
}
输出结果:
polygonA area:10.00
polygonA relate polygonB:INTERSECTS
地理空间是一个球面,范围是维度-90,+90,经度-180,+180,距离的计算以及空间位置关系,与平面几何都有很大的差异。Spatial4j支持地理空间的计算,是它的一个核心卖点。
DistanceUtils提供了一些距离换算的工具,例如弧度换算成距离,距离换算成弧度。
void testDistanceUtils() {
// 物理距离换算成弧度
int equatorLengthKm = 40075;
double equatorDegree = DistanceUtils.dist2Degrees(equatorLengthKm, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
System.out.println(String.format("equator length to degree: %.2f", equatorDegree));
// 赤道线上,每经度的距离
double distPerDegreeKm = DistanceUtils.degrees2Dist(1, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
System.out.println(String.format("distance per degree: %.2fkm", distPerDegreeKm));
}
输出结果:
equator length to degree:360.00
distance per degree:111.32km
另外,Spatial4j还提供了GeoHASH编解码等工具包,有需要的同学可以进一步了解
地理空间的距离计算与平面几何的距离计算不同。从下面的例子可以看出,如果用平面集合的算法来计算地理空间的距离,会出现误差。
void testGeodesicDistance() {
SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
nonGeoContextFactory.geo = false;
SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);
// 平面坐标系中的距离
Point nonGeoCenter = new PointImpl(0, 0, nonGeoContext);
CartesianDistCalc cartesianDistCalc = new CartesianDistCalc();
System.out.println(String.format("cartesian distance: %.2f",
cartesianDistCalc.distance(nonGeoCenter, 30, 40)));
// 地理空间的距离(弧度),采用Haversine公式计算
Point geoCenter = new PointImpl(0,0, SpatialContext.GEO);
GeodesicSphereDistCalc geodesicSphereDistCalc = new GeodesicSphereDistCalc.Haversine();
System.out.println(String.format("geodesic distance: %.2f",
geodesicSphereDistCalc.distance(geoCenter, 30, 40)));
}
输出结果:
cartesian distance: 50.00
geodesic distance: 48.44
地理空间图形的关系也与平面坐标系不同。在下面的例子中,圆形跨越了180度经线,同样参数的两个圆形,在平面坐标系不相交,在地理空间则是相交。如果用平面坐标系的算法,需要进行换算。
void testGeodesicRelate() {
SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
nonGeoContextFactory.geo = false;
SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);
// 平面坐标系中的圆
Point pointLeft = new PointImpl(-179, 0, nonGeoContext);
Point pointRight = new PointImpl(179, 0, nonGeoContext);
Circle circleLeft = new CircleImpl(pointLeft, 10, nonGeoContext);
Circle circleRight = new CircleImpl(pointRight, 10, nonGeoContext);
System.out.println("cartesian circleLeft relate circleRight: " + circleLeft.relate(circleRight));
// 地理空间中的圆
Point geoCenterWest = new PointImpl(-179, 0, SpatialContext.GEO);
Point geoCenterEast = new PointImpl(179, 0, SpatialContext.GEO);
Circle geoCircleWest = new CircleImpl(geoCenterWest, 10, SpatialContext.GEO);
Circle geoCircleEast = new CircleImpl(geoCenterEast, 10, SpatialContext.GEO);
System.out.println("geodesic circleWest relate circleEast: " + geoCircleWest.relate(geoCircleEast));
}
输出结果:
cartesian circleLeft relate circleRight: DISJOINT
geodesic circleWest relate circleEast: INTERSECTS
Spatial4j支持对标准空间描述语法的序列化和反序列化,下面是WKT和GeoJson的例子。注意GeoJSON的reader或writer都依赖Noggit序列化工具。
void testReadStdFormat() {
JtsSpatialContextFactory jtsSpatialContextFactory = new JtsSpatialContextFactory();
jtsSpatialContextFactory.geo = false;
JtsSpatialContext jtsSpatialContext = jtsSpatialContextFactory.newSpatialContext();
// 读写WKT格式
ShapeReader wktReader = jtsSpatialContext.getFormats().getReader(ShapeIO.WKT);
ShapeWriter wktWriter = jtsSpatialContext.getFormats().getWriter(ShapeIO.WKT);
try {
// 注意BUFFER是Spatial4j基于WKT的扩展定义
Circle circle = (Circle) wktReader.read("BUFFER(POINT(0 0), 1)");
System.out.println(String.format("read WKT shape area: %.2f", circle.getArea(jtsSpatialContext)));
Rectangle rectangle = new RectangleImpl(1,10,3,8, jtsSpatialContext);
System.out.println("WKT format string: " + wktWriter.toString(rectangle));
} catch (Exception e) {
//
}
// 读写GeoJson格式
ShapeReader geoJsonReader = jtsSpatialContext.getFormats().getReader(ShapeIO.GeoJSON);
ShapeWriter geoJsonWriter = jtsSpatialContext.getFormats().getWriter(ShapeIO.GeoJSON);
try {
// 注意解析polygon依赖JTS
Shape polygon = geoJsonReader.read("{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,2],[3,1],[5,3],[3,5],[2,4],[1,5],[1,1]]]}");
System.out.println(String.format("read GeoJson polygon area: %.2f", polygon.getArea(jtsSpatialContext)));
Circle circle = new CircleImpl(new PointImpl(0, 0, jtsSpatialContext), 10, jtsSpatialContext);
System.out.println("GeoJSON format string: " + geoJsonWriter.toString(circle));
} catch (Exception e) {
//
}
}
输出结果:
read WKT shape area: 3.14
WKT format string: ENVELOPE (1, 10, 8, 3)
read GeoJson polygon area: 10.00
GeoJSON format string: {"type":"Circle","coordinates":[0,0],"radius":10}
1 https://github.com/locationtech/spatial4j
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。