前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spatial4j入门实战

spatial4j入门实战

原创
作者头像
doaction
修改2021-11-18 17:21:10
2.7K0
修改2021-11-18 17:21:10
举报
文章被收录于专栏:java库java库

一 基本介绍

Spatial4j是一款java编写的空间计算开源库,支持ASL开源协议,支持地理空间计算。

Spatial4j主要有三个主要功能:1)支持基于平面几何或地理空间的若干图形;2)支持距离计算和形状的计算:计算边界框、面积、图形间的关系等 3)解析WKT、GeoJSON等空间描述标准格式

Spatial4j利用了部分JTS的能力(JTS是最流行的java空间计算库),例如多边形是基于JTS实现的。相比与JTS,spatial4j还支持了圆以及地理空间计算。用JTS,通常用多边形近似替代了圆的计算,对结果会造成一定误差,而Spatial4j支持了圆;另外,地理空间计算的应用现在十分广泛,用spatial4j会更加方便。

由于官网只有一些简要介绍,本文介绍了一些实战用例,旨在帮助需要的同学更快上手。

二 平面几何

2.1 pom依赖

如果涉及到多边形,需要引用JTS;如果用到第四节介绍的GeoJSON序列化或反序列化,需要依赖noggit

代码语言:txt
复制
<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>
2.2 基本图形

接下来直接进入实战,先开始平面几何的例子。

首先是点、圆、矩形等简单图形的定义,并计算了图形的面积、边界框、图形间的关系。

图片1
图片1
代码语言:txt
复制
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
2.3 多边形

spatial4j利用JTS的多边形计算能力。

下面子的例子分别定义了一个凹多边形和一个凸多边形,计算了多边形的面积和多边形间的关系。

图片2
图片2
代码语言:txt
复制
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支持地理空间的计算,是它的一个核心卖点。

3.1 工具包

DistanceUtils提供了一些距离换算的工具,例如弧度换算成距离,距离换算成弧度。

代码语言:txt
复制
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编解码等工具包,有需要的同学可以进一步了解

3.2 距离计算

地理空间的距离计算与平面几何的距离计算不同。从下面的例子可以看出,如果用平面集合的算法来计算地理空间的距离,会出现误差。

代码语言:txt
复制
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
3.3 图形间关系

地理空间图形的关系也与平面坐标系不同。在下面的例子中,圆形跨越了180度经线,同样参数的两个圆形,在平面坐标系不相交,在地理空间则是相交。如果用平面坐标系的算法,需要进行换算。

图片3
图片3
图片4
图片4
代码语言:txt
复制
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序列化工具。

代码语言:txt
复制
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

代码语言:txt
复制

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 基本介绍
  • 二 平面几何
    • 2.1 pom依赖
      • 2.2 基本图形
        • 2.3 多边形
        • 三 地理空间
          • 3.1 工具包
            • 3.2 距离计算
              • 3.3 图形间关系
              • 四 解析标准数据格式
              • 五 引用
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档