Introduction to spatial4j

I. Basic Introduction

Spatial4j is an open source library for spatial computing written in java. It supports ASL open source protocol and geospatial computing.

Spatial4j has three main functions: 1) it supports several graphics based on plane geometry or geospatial; 2) Support distance calculation and shape calculation: calculate the relationship between bounding box, area and graphics. 3) analyze spatial description standard formats such as WKT and GeoJSON

Spatial4j makes use of some JTS capabilities (JTS is the most popular java spatial computing library). For example, polygons are implemented based on JTS. Compared with JTS, spatial4j also supports circle and geospatial computing. With JTS, polygon approximation is usually used to replace the calculation of circle, which will cause some errors to the results, and spatial4j supports circle; In addition, geospatial computing is now widely used, and it will be more convenient to use spatial 4J.

Since there are only some brief introductions on the official website, this paper introduces some practical use cases to help students in need get started faster.

Biplane geometry

2.1 pom dependency

If polygons are involved, JTS needs to be referenced; If you use GeoJSON serialization or deserialization described in Section 4, you need to rely on 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>

2.2 basic graphics

Next, go directly to the actual combat, and start with the example of plane geometry.

Firstly, it defines simple graphics such as point, circle and rectangle, and calculates the area, boundary box and the relationship between graphics.

void testEuclidean() {
    // Generate the context of plane geometry
    SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
    nonGeoContextFactory.geo = false;
    SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);

    // Define two points
    Point pointA = new PointImpl(2,6, nonGeoContext);
    Point pointB = new PointImpl(6,5, nonGeoContext);

    // Define circle
    Circle circleA = new CircleImpl(new PointImpl(6,4, nonGeoContext), 2, nonGeoContext);

    // Judge the relationship between circle and point
    System.out.println("circleA relate pointA: " + circleA.relate(pointA));
    System.out.println("circleA relate pointB: " + circleA.relate(pointB));

    // Calculate the area of the circle and the bounding box of the circle
    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()));

    // Define rectangle and calculate the relationship between rectangle and circle
    Rectangle rectangleA = new RectangleImpl(3,5,4,8,nonGeoContext);
    System.out.println("rectangleA relate circleA: " + rectangleA.relate(circleA));
}

Output results:
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 polygon

spatial4j utilizes the polygon computing power of JTS.

The following sub examples define a concave polygon and a convex polygon respectively, and calculate the area of the polygon and the relationship between the polygons.

void testPolygon() {
    // JTS based context
    JtsSpatialContextFactory jtsSpatialContextFactory = new JtsSpatialContextFactory();
    jtsSpatialContextFactory.geo = false;
    JtsSpatialContext jtsSpatialContext = jtsSpatialContextFactory.newSpatialContext();
    JtsShapeFactory jtsShapeFactory = jtsSpatialContext.getShapeFactory();

    // Define concave polygon 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();

    // Define convex polygon 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();

    // Calculate polygon area and polygon relationship
    System.out.println(String.format("polygonA area: %.2f", polygonA.getArea(jtsSpatialContext)));
    System.out.println("polygonA relate polygonB: " + polygonA.relate(polygonB));
}

Output results:
polygonA area:10.00
polygonA relate polygonB:INTERSECTS

III. geographical space

Geospatial is a sphere with dimensions of - 90, + 90, longitude of - 180, + 180. The calculation of distance and spatial position relationship are very different from plane geometry. Spatial4j supports geospatial computing, which is one of its core selling points.

3.1 tool kit

DistanceUtils provides some distance conversion tools, such as radian conversion to distance and distance conversion to radian.

void testDistanceUtils() {
    // Convert physical distance into radians
    int equatorLengthKm = 40075;
    double equatorDegree = DistanceUtils.dist2Degrees(equatorLengthKm, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
    System.out.println(String.format("equator length to degree: %.2f", equatorDegree));

    // Distance per longitude on the equator
    double distPerDegreeKm = DistanceUtils.degrees2Dist(1, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
    System.out.println(String.format("distance per degree: %.2fkm", distPerDegreeKm));
}

Output results:
equator length to degree:360.00
distance per degree:111.32km

In addition, Spatial4j also provides GeoHASH codec and other toolkits, which can be further understood by students in need

3.2 distance calculation

The distance calculation of geospatial is different from that of plane geometry. As can be seen from the following example, if the plane set algorithm is used to calculate the geospatial distance, there will be errors.

void testGeodesicDistance() {
    SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
    nonGeoContextFactory.geo = false;
    SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);

    // Distance in plane coordinate system
    Point nonGeoCenter = new PointImpl(0, 0, nonGeoContext);
    CartesianDistCalc cartesianDistCalc = new CartesianDistCalc();
    System.out.println(String.format("cartesian distance: %.2f",
            cartesianDistCalc.distance(nonGeoCenter, 30, 40)));

    // The geospatial distance (radian) is calculated by Haversine formula
    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)));
}

Output results:
cartesian distance: 50.00
geodesic distance: 48.44

3.3 relationship between graphs

The relationship between geospatial graphics and plane coordinate system is also different. In the following example, a circle crosses a 180 degree longitude. Two circles with the same parameters do not intersect in the plane coordinate system and intersect in geospatial space. If the algorithm of plane coordinate system is used, conversion is required.

void testGeodesicRelate() {
    SpatialContextFactory nonGeoContextFactory = new SpatialContextFactory();
    nonGeoContextFactory.geo = false;
    SpatialContext nonGeoContext = new SpatialContext(nonGeoContextFactory);

    // Circle in plane coordinate system
    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));

    // Circle in geospatial
    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));
}

Output results:
cartesian circleLeft relate circleRight: DISJOINT
geodesic circleWest relate circleEast: INTERSECTS

IV. parsing standard data format

Spatial4j supports serialization and deserialization of standard spatial description syntax. The following are examples of WKT and geojason. Note that both the reader and writer of geojson rely on the Noggit serialization tool.

void testReadStdFormat() {
    JtsSpatialContextFactory jtsSpatialContextFactory = new JtsSpatialContextFactory();
    jtsSpatialContextFactory.geo = false;
    JtsSpatialContext jtsSpatialContext = jtsSpatialContextFactory.newSpatialContext();

    // Read / write WKT format
    ShapeReader wktReader = jtsSpatialContext.getFormats().getReader(ShapeIO.WKT);
    ShapeWriter wktWriter = jtsSpatialContext.getFormats().getWriter(ShapeIO.WKT);
    try {
        // Note that BUFFER is a Spatial4j WKT based extension definition
        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) {
        //
    }

    // Read and write geojason format
    ShapeReader geoJsonReader = jtsSpatialContext.getFormats().getReader(ShapeIO.GeoJSON);
    ShapeWriter geoJsonWriter = jtsSpatialContext.getFormats().getWriter(ShapeIO.GeoJSON);
    try {
        // Note that parsing polygon depends on 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) {
        //
    }
}

Output results:
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}

V. citation

1 https://github.com/locationtech/spatial4j

Tags: Java

Posted on Thu, 18 Nov 2021 05:16:43 -0500 by ccalzaretta