JTS使用实践

x33g5p2x  于2022-05-20 转载在 其他  
字(12.8k)|赞(0)|评价(0)|浏览(607)

JTS使用实践

一、前言

简介

JTS Topology Suite(Java Topology Suite)是一个开源的Java软件库,它为欧几里得平面线性几何提供了一个对象模型以及一组基本的几何函数。

环境

JTS 特性:https://locationtech.github.io/jts/jts-features.html

GitHub(JTS):https://github.com/locationtech/jts

JTS java doc:http://locationtech.github.io/jts/javadoc/overview-summary.html

GeoTools(JTS):https://docs.geotools.org/latest/userguide/library/jts/index.html

wikipedia(JTS):https://en.wikipedia.org/wiki/JTS_Topology_Suite

wikipedia(WKT):https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry

PostGIS 拓扑关系:https://postgis.net/docs/reference.html#idm15481

polygon 和 multipolygon 的区别:https://gis.stackexchange.com/questions/225368/understanding-difference-between-polygon-and-multipolygon-for-shapefiles-in-qgis/225373

二、正文

基础说明

  • 输入/输出格式:WKT、WKB、GML、Java Swing/AWT Shape
  • WKT 格式
几何类型WKT
PointPOINT (30 10)
LineStringLINESTRING (30 10, 10 30, 40 40)
PolygonPOLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))
Polygon-with-holePOLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
MultiPointMULTIPOINT ((10 40), (40 30), (20 20), (30 10))
MultiPointMULTIPOINT (10 40, 40 30, 20 20, 30 10)
MultiLineStringMULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))
MultiPolygonMULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))
MultiPolygon-with-holeMULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))
GeometryCollectionGEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 40), POLYGON ((40 40, 20 45, 45 30, 40 40)))
  • 拓扑关系
关键字说明参考
equals (相等)如果两个几何在空间中包含相同的一组点,则返回 trueST_Equals
disjoint(不相交)如果两个几何在空间上不相交(它们没有共同点),则返回 trueST_Disjoint
intersects(相交)如果两个几何/地理在 2D 空间上相交(至少有一个共同点),则返回 trueST_Intersects
touches(接触)如果两个几何体至少有一个共同点,但它们的内部不相交,则返回 trueST_Touches
crosses(交叉)如果两个几何具有一些(但不是全部)共同的内部点,则返回 trueST_Crosses
within(内含)如果几何 A 完全在几何 B 内部,则返回 trueST_Within
contains (包含)当且仅当 B 的任何点都不在 A 的外部,并且 B 内部的至少一个点在 A 的内部时,才返回 trueST_Contains
overlaps(重叠)如果两个几何体相交并具有相同的维度,但彼此不完全包含,则返回 trueST_Overlaps
  • 叠加分析
关键字说明参考
buffer(缓冲区)返回一个几何图形,该几何图形涵盖距几何图形给定距离内的所有点ST_Buffer
convexHull(凸壳)计算几何的凸壳ST_ConvexHull
intersection(交集)返回表示几何 A 和 B 共享部分的几何ST_Intersection
union(合并)返回表示输入几何的点集合并的几何ST_Union
difference(差异)返回表示几何 A 中不与几何 B 相交的部分的几何ST_Difference
symDifference(对称差异)返回表示几何 A 和 B 不相交部分的几何ST_SymDifference

使用记录

  • 添加依赖
<!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
<dependency>
    <groupId>org.locationtech.jts</groupId>
    <artifactId>jts-core</artifactId>
    <version>1.18.2</version>
</dependency>

创建几何要素

package com.example;

import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import java.util.List;
import java.util.Map;

/**
 * JTS 几何模型
 * 几何类型:点( POINT )、多点( MULTIPOINT )
 *           线( LINESTRING )、多线(MULTILINESTRING )
 *           面(POLYGON )、多面(MULTIPOLYGON )
 *           圆
 * 创建方式:几何工厂 和 WKT
 * */
public class JTSGeometryUtil {

    // 几何工厂对象
    private GeometryFactory geometryFactory = new GeometryFactory();

    /**
     * 获取点
     * @param lng 经度
     * @param lat 纬度
     * */
    public Point getPoint(Double lng, Double lat){
        Coordinate coordinate = new Coordinate(lng, lat);
        return geometryFactory.createPoint(coordinate);
    }

    /**
     *  获取点,通过 WKT 创建
     * @param lng 经度
     * @param lat 纬度
     * */
    public Point getPointByWKT(Double lng, Double lat) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        // POINT(111 22)
        wktSB.append("POINT").append("(").append( lng ).append(" ").append( lat ).append(")");
        return (Point) createGeometry(wktSB.toString());
    }

    /**
     * 获取多点
     * @param coordinates 坐标集合
     * */
    public MultiPoint getMultiPoint(Coordinate [] coordinates){
        return geometryFactory.createMultiPointFromCoords(coordinates);
    }

    /**
     * 获取多点,通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public MultiPoint getMultiPointByWKT(Coordinate [] coordinates) throws ParseException {
        // MULTIPOINT(111 22 ,111 22)
        String wktStr = createWktString("MULTIPOINT", coordinates);
        return (MultiPoint) createGeometry(wktStr);
    }

    /**
     * 获取线
     * @param coordinates 坐标集合
     * */
    public LineString getLineString(Coordinate [] coordinates){
        return geometryFactory.createLineString(coordinates);
    }

    /**
     * 获取线,通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public LineString getLineStringByWKT(Coordinate [] coordinates) throws ParseException {
        String wktStr = createWktString("LINESTRING", coordinates);
        return (LineString) createGeometry(wktStr);
    }

    /**
     * 获取多线
     * @param coordList 多个坐标集合
     * */
    public MultiLineString getMultiLineString(List<Coordinate []> coordList){
        LineString [] lineStrings = new LineString[coordList.size()];
        for(int m=0; m<coordList.size(); m++){
            lineStrings[m] = getLineString(coordList.get(m));
        }
        return geometryFactory.createMultiLineString(lineStrings);
    }

    /**
     * 获取多线,通过 WKT 创建
     * @param coordList 多个坐标集合
     * */
    public MultiLineString getMultiLineStringByWKT(List<Coordinate []> coordList) throws ParseException {
        String wktStr = createMultiWktString("MULTILINESTRING", coordList);
        return (MultiLineString) createGeometry(wktStr);
    }

    /**
     * 获取面
     * @param coordinates 坐标集合
     * */
    public Polygon getPolygon(Coordinate [] coordinates){
        return geometryFactory.createPolygon(coordinates);
    }

    /**
     * 获面
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public Polygon getPolygon(Coordinate [] shellCoordinates, List<Coordinate []> holeCoordList){
        LinearRing shell = geometryFactory.createLinearRing(shellCoordinates);
        LinearRing[] holes = new LinearRing[holeCoordList.size()];
        for(int m=0; m<holeCoordList.size(); m++){
            Coordinate [] holeCoordinates = holeCoordList.get(m);
            holes[m] = geometryFactory.createLinearRing(holeCoordinates);
        }
        return geometryFactory.createPolygon(shell, holes);
    }

    /**
     * 获取面,通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public Polygon getPolygonByWKT(Coordinate [] coordinates) throws ParseException {
        String wktStr = createWktStringWithPolygon("POLYGON", coordinates, null);
        return (Polygon) createGeometry(wktStr);
    }

    /**
     * 获取面,通过 WKT 创建
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public Polygon getPolygonByWKT(Coordinate [] shellCoordinates, List<Coordinate []> holeCoordList) throws ParseException {
        String wktStr = createWktStringWithPolygon("POLYGON", shellCoordinates, holeCoordList);
        return (Polygon) createGeometry(wktStr);
    }

    /**
     * 获取多面
     * @param coordList 多个坐标集合
     * */
    public MultiPolygon getMultiPolygon(List<Coordinate []> coordList){
        Polygon [] polygons = new Polygon[ coordList.size() ];
        for(int i=0; i<coordList.size(); i++){
            polygons[i] = geometryFactory.createPolygon(coordList.get(i));
        }
        return geometryFactory.createMultiPolygon(polygons);
    }

    /**
     * 获多面
     * @param coordinateMap 面边界和孔洞坐标集合
     * */
    public MultiPolygon getMultiPolygon(Map<Coordinate [], List<Coordinate []>> coordinateMap){
        Polygon [] polygons = new Polygon[ coordinateMap.size() ];
        int index = 0;
        for(Map.Entry<Coordinate[], List<Coordinate[]>> item : coordinateMap.entrySet()){
            LinearRing shell = geometryFactory.createLinearRing(item.getKey()); // 面边界
            LinearRing[] holes = new LinearRing[item.getValue().size()]; // 孔洞
            for(int m=0; m<item.getValue().size(); m++){
                Coordinate [] holeCoordinates = item.getValue().get(m);
                holes[m] = geometryFactory.createLinearRing(holeCoordinates);
            }
            polygons[index] = geometryFactory.createPolygon(shell, holes);
            index++;
        }
        return geometryFactory.createMultiPolygon(polygons);
    }

    /**
     * 获取多面
     * @param coordList 多个坐标集合
     * */
    public MultiPolygon getMultiPolygonByWKT(List<Coordinate []> coordList) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append("MULTIPOLYGON").append("(");
        int index = 0;
        for(Coordinate [] coordinates : coordList){
            if(index > 0) { wktSB.append(","); }
            // 1. 处理面边界
            wktSB.append("(");
            wktSB.append("("); // 面边界start
            wktSB.append(coordinatesToWktStr(coordinates));
            wktSB.append(")"); // 面边界end
            wktSB.append(")");
            index++;
        }
        wktSB.append(")");
        return (MultiPolygon) createGeometry(wktSB.toString());
    }

    /**
     * 获多面
     * @param coordinateMap 面边界和孔洞坐标集合
     * */
    public MultiPolygon getMultiPolygonByWKT(Map<Coordinate [], List<Coordinate []>> coordinateMap) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append("MULTIPOLYGON").append("(");
        int index = 0;
        for(Map.Entry<Coordinate[], List<Coordinate[]>> item : coordinateMap.entrySet()){
            Coordinate [] shellCoordinates = item.getKey();
            List<Coordinate []> holeCoordList = item.getValue();
            if(index > 0) { wktSB.append(","); }
            wktSB.append("(");
            wktSB.append(coordinatesToWktStr(shellCoordinates, holeCoordList));
            wktSB.append(")");
            index++;
        }
        wktSB.append(")");
        return (MultiPolygon) createGeometry(wktSB.toString());
    }

    /**
     * 获取圆(无孔洞)
     * @param lng 经度
     * @param lat 纬度
     * @param radius 半径
     * */
    public Polygon getCircle(double lng, double lat, final double radius) {
        final int arcs = 32; // 弧线点数量
        Coordinate coords[] = new Coordinate[ arcs + 1 ];
        for (int i = 0; i < arcs; i++) { // 角度转弧度计算
            double angle = ((double) i / (double) arcs) * Math.PI * 2.0;
            double dx = Math.cos(angle) * radius;
            double dy = Math.sin(angle) * radius;
            coords[i] = new Coordinate((double) lng + dx, (double) lat + dy);
        }
        coords[arcs] = coords[0]; // 将第一个数据放在最后,形成闭环
        LinearRing ring = geometryFactory.createLinearRing(coords); // 圆环线
        Polygon polygon = geometryFactory.createPolygon(ring, null);
        return polygon;
    }

    /**
     * 通过 WKT 字符串创建几何对象
     * @param wktStr wkt 字符串
     * */
    public Geometry createGeometry(String wktStr) throws ParseException {
        WKTReader wktReader = new WKTReader(geometryFactory);
        return wktReader.read(wktStr);
    }

    /**
     * 根据几何类型和坐标集合,创建 WKT 字符串
     * @param geomType 几何类型
     * @param coordinates 坐标集合
     * */
    public String createWktString(String geomType, Coordinate [] coordinates){
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        wktSB.append(coordinatesToWktStr(coordinates));
        wktSB.append(")");
        return wktSB.toString();
    }

    /**
     * 将坐标集合转换为 wkt 需要的字符串
     * @param coordinates 坐标集合
     * */
    private String coordinatesToWktStr(Coordinate [] coordinates){
        StringBuilder wktSB = new StringBuilder();
        for(int i=0; i<coordinates.length; i++){
            Coordinate coordinate = coordinates[i];
            wktSB.append( coordinate.getX() ).append(" ").append( coordinate.getY() );
            if(coordinates.length-1 != i){ wktSB.append(","); } // 最后一个坐标不需要加逗号
        }
        return wktSB.toString();
    }

    /**
     * 将坐标集合转换为 wkt 需要的字符串
     * */
    private String coordinatesToWktStr(Coordinate [] shellCoordinates, List<Coordinate []> holeCoordList){
        StringBuilder wktSB = new StringBuilder();
        // 1. 处理面边界
        wktSB.append("("); // 面边界start
        wktSB.append(coordinatesToWktStr(shellCoordinates));
        wktSB.append(")"); // 面边界end
        // 2. 处理多个孔洞
        if(holeCoordList != null && holeCoordList.size() > 0){
            for(int n=0; n<holeCoordList.size(); n++){
                Coordinate [] holeCoordinates = holeCoordList.get(n);
                wktSB.append(",");
                wktSB.append("("); // 面边界start
                wktSB.append(coordinatesToWktStr(holeCoordinates));
                wktSB.append(")"); // 面边界end
            }
        }
        return wktSB.toString();
    }

    /**
     * 根据几何类型和坐标集合,创建 WKT 字符串(多个几何图形)
     * @param geomType 几何类型
     * @param coordList 多个坐标集合
     * */
    public String createMultiWktString(String geomType, List<Coordinate []> coordList){
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        for(int m=0; m<coordList.size(); m++){
            Coordinate [] coordinates = coordList.get(m);
            wktSB.append("(");
            for(int n=0; n<coordList.size(); n++){
                Coordinate coordinate = coordinates[n];
                wktSB.append( coordinate.getX() ).append(" ").append( coordinate.getY() );
                if(coordinates.length-1 != n){ wktSB.append(","); } // 最后一个坐标不需要加逗号
            }
            wktSB.append(")");
            if(coordList.size()-1 != m){ wktSB.append(","); } // 最后一个坐标不需要加逗号
        }
        wktSB.append(")");
        return wktSB.toString();
    }

    /**
     * 根据几何类型和坐标集合,创建 WKT 字符串
     * @param geomType 几何类型
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public String createWktStringWithPolygon(String geomType, Coordinate [] shellCoordinates, List<Coordinate []> holeCoordList){
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        wktSB.append(coordinatesToWktStr(shellCoordinates, holeCoordList));
        wktSB.append(")");
        return wktSB.toString();
    }
}

操作示例

package com.example;

import org.locationtech.jts.geom.*;

public class App
{
    public static void main( String[] args )
    {
        App app = new App();
        // 拓扑关系 contains
        app.doContains();

        // 叠加分析 intersection
        app.doIntersection();
    }

    private JTSGeometryUtil jtsGeometryUtil = new JTSGeometryUtil();

    /**
     * Contains 包含
     * */
    public void doContains(){
        // 点
        Point point = jtsGeometryUtil.getPoint(116.403406,39.93397);
        // 面
        Coordinate[] coordinates = new Coordinate[4];
        coordinates[0] = new Coordinate(116.361725,39.95676); // 首尾坐标相同
        coordinates[1] = new Coordinate(116.364887,39.908515);
        coordinates[2] = new Coordinate(116.442501,39.909622);
        coordinates[2] = new Coordinate(116.440488,39.955654);
        coordinates[3] = new Coordinate(116.361725,39.95676); // 首尾坐标相同
        Polygon polygon = jtsGeometryUtil.getPolygon(coordinates);
        // 操作
        boolean result = polygon.contains(point);
        System.out.println(result);
    }

    /**
     * Intersection 交叉分析
     * */
    public void doIntersection(){
        // 线一
        Coordinate [] coordinates1 = new Coordinate[2];
        coordinates1[0] = new Coordinate(116.361725,39.95676);
        coordinates1[1] = new Coordinate(116.442501,39.909622);
        LineString lineString1 = jtsGeometryUtil.getLineString(coordinates1);
        // 线二
        Coordinate [] coordinates2 = new Coordinate[2];
        coordinates2[0] = new Coordinate(116.364887,39.908515);
        coordinates2[1] = new Coordinate(116.440488,39.955654);
        LineString lineString2 = jtsGeometryUtil.getLineString(coordinates2);
        // 操作
        Geometry geometry =  lineString1.intersection(lineString2);
        System.out.println(geometry);
    }
}

相关文章