项目中遇到要用经纬度计算长度,周长,面积,电子栅栏的情况,可以使用geotools
准备工作
首先我们引入jar依赖
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geometry</artifactId>
<version>24.2</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>24.2</version>
</dependency>
很多同学会说下载不下来,我也遇到了这种情况,是马云的仓库里没有,这里我们换个仓库下载,
要贴在repositories标签里
<!-- 下载 geotools用的 -->
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
<snapshots><enabled>false</enabled></snapshots>
<releases><enabled>true</enabled></releases>
</repository>
<repository>
<id>osgeo-snapshot</id>
<name>OSGeo Snapshot Repository</name>
<url>https://repo.osgeo.org/repository/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
<!-- 下载 geotools用的 -->
依赖搞定了,我们试试API
场景1:判断点是否在电子围栏中
我这次所有API用到的import如下
值得注意的是我的代码大多数来自ChatGPT,但是他给的不一定完全正确,他告诉我要引入24.2版本的,但是24.2版本的org.locationtech.jts这个包名和旧版本的不一样了
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.io.WKTReader;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
// 判断点是否在电子围栏中
public static boolean isPointInFence(double lon, double lat, String fenceWkt) throws Exception {
GeometryFactory gf = new GeometryFactory();
WKTReader reader = new WKTReader(gf);
// 解析电子围栏的边界点列表
Coordinate[] coords = parseCoordinates(fenceWkt);
LinearRing ring = new LinearRing(new CoordinateArraySequence(coords), gf);
Polygon fence = new Polygon(ring, null, gf);
// 创建目标点
Point point = gf.createPoint(new Coordinate(lon, lat));
// 判断点是否在围栏内
return fence.contains(point);
}
// 解析WKT格式的坐标串
private static Coordinate[] parseCoordinates(String wkt) throws Exception {
WKTReader reader = new WKTReader();
Geometry geom = reader.read(wkt);
return geom.getCoordinates();
}
// 测试--点是否在电子围栏中
String fenceWkt = "POLYGON((116.412928 39.930636,116.413198 39.928658,116.417348 39.928713,116.417043 39.930678,116.412928 39.930636))";
double lon = 116.414797;
double lat = 39.929792;
boolean isInFence = GeoToolsUtils.isPointInFence(lon, lat, fenceWkt);
System.out.println(isInFence);
场景2:经纬度点与点之间的距离
public static double geotoolsDistance(double lon1, double lat1, double lon2, double lat2) throws Exception {
CoordinateReferenceSystem crs = org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;
GeodeticCalculator gc = new GeodeticCalculator(crs);
JTSFactoryFinder.getGeometryFactory(null);
Coordinate coord1 = new Coordinate(lon1, lat1);
Coordinate coord2 = new Coordinate(lon2, lat2);
gc.setStartingPosition(org.geotools.geometry.jts.JTS.toDirectPosition(coord1, crs));
gc.setDestinationPosition(org.geotools.geometry.jts.JTS.toDirectPosition(coord2, crs));
return gc.getOrthodromicDistance() / 1000; // 单位为千米
}
一开始ChatGPT给的代码是下面的
public static final double R = 6371.0; // 地球平均半径,单位为千米
public static double haversine(double lon1, double lat1, double lon2, double lat2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = R * c;
return d;
}
//经纬度点与点之间的距离
double distance = GeoToolsUtils.geotoolsDistance(116.509442, 39.98279, 116.509442, 39.982556);
System.out.println(distance);
double distance2 = GeoToolsUtils.haversine(116.509442, 39.98279, 116.509442, 39.982556);
System.out.println(distance2);
GeoTools计算的:0.025982026110380408
数学计算的:0.026019612834710798
场景三:判断点是否在圆内
就是给定圆心和半径
public static boolean isInCircle(double lon1, double lat1, double lon2, double lat2, double radius) {
double distance = haversine(lon1, lat1, lon2, lat2); // 计算两点之间的距离
return distance <= radius;
}
场景四:计算多边形面积
这个是结合地球半径,把多边形拆成多个三角形计算得出结果
// 地球半径
private static final double EARTH_RADIUS = 6378137.0; // meters
// 计算多边形面积
public static double calculatePolygonArea(List<Coordinate> coordinates) {
double area = 0.0;
int numPoints = coordinates.size();
if (numPoints > 2) {
for (int i = 0; i < numPoints; i++) {
Coordinate p1 = coordinates.get(i);
Coordinate p2 = coordinates.get((i + 1) % numPoints);
area += Math.toRadians(p2.getX() - p1.getX()) *
(2 + Math.sin(Math.toRadians(p1.getY())) +
Math.sin(Math.toRadians(p2.getY())));
}
area = area * EARTH_RADIUS * EARTH_RADIUS / 2.0;
}
return Math.abs(area);
}
测试
List<Coordinate> polygonCoordinates = new ArrayList<>();
polygonCoordinates.add(new Coordinate(116.412928, 39.930636)); // 左上角经纬度坐标
polygonCoordinates.add(new Coordinate(116.413198, 39.928658)); // 左下角经纬度坐标
polygonCoordinates.add(new Coordinate(116.417348, 39.928713)); // 右下角经纬度坐标
polygonCoordinates.add(new Coordinate(116.417043, 39.930678)); // 右上角经纬度坐标
polygonCoordinates.add(new Coordinate(116.412928, 39.930636)); // 右上角经纬度坐标
double v = GeoToolsUtils.calculatePolygonArea(polygonCoordinates);
System.out.println("多面积为:" + v);
"Mercator投影"。Mercator投影是一种圆柱投影,这个算法和通过球表面积计算的结果有很大出入,推荐用上的的解法。
public static double calculateArea(List<Coordinate> coordinates) throws Exception {
// 定义源坐标系和目标坐标系
// 创建多边形对象
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
LinearRing linearRing = geometryFactory.createLinearRing(coordinates.toArray(new Coordinate[0]));
Polygon polygon = geometryFactory.createPolygon(linearRing, null);
// 创建坐标参考系对象
CoordinateReferenceSystem sourceCRS = DefaultGeographicCRS.WGS84;
CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
// 创建坐标转换对象
MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);
// 对多边形进行坐标转换
Geometry geometry = JTS.transform(polygon, transform);
System.out.println(geometry.getLength());
// 计算多边形的面积
return geometry.getArea();
}
判断点是否在多边形内其他解法
// 检查点是否位于多边形内
public static boolean isPointInPolygon(Point2D.Double point, List<Point2D.Double> polygonCoords) {
Path2D.Double polygon = new Path2D.Double();
for (int i = 0; i < polygonCoords.size(); i++) {
Point2D.Double coord = polygonCoords.get(i);
if (i == 0) {
polygon.moveTo(coord.getX(), coord.getY());
} else {
polygon.lineTo(coord.getX(), coord.getY());
}
}
polygon.closePath();
return polygon.contains(point);
}
public static void main(String[] args) {
// 电子栅栏多边形的经纬度坐标列表
List<Point2D.Double> polygonCoords = new ArrayList<>();
polygonCoords.add(new Point2D.Double(39.930636, 116.412928));
polygonCoords.add(new Point2D.Double(39.928658, 116.413198));
polygonCoords.add(new Point2D.Double(39.928713, 116.417348));
polygonCoords.add(new Point2D.Double(39.930678, 116.417043));
polygonCoords.add(new Point2D.Double(39.930636, 116.412928));
// 点的经纬度坐标列表
List<Point2D.Double> pointCoords = new ArrayList<>();
pointCoords.add(new Point2D.Double(39.929792, 116.414797));
pointCoords.add(new Point2D.Double(39.929798, 116.414797));
pointCoords.add(new Point2D.Double(37.400, -122.700));
for (Point2D.Double point : pointCoords) {
if (isPointInPolygon(point, polygonCoords)) {
System.out.println("Point (" + point.getX() + ", " + point.getY() + ") is inside the polygon.");
} else {
System.out.println("Point (" + point.getX() + ", " + point.getY() + ") is outside the polygon.");
}
}
}