From cffb294bfe748471afbf5ab164aced2257a2221d Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 00:55:51 +0800 Subject: [PATCH] add CoordinateUtil --- CHANGELOG.md | 3 +- .../cn/hutool/core/util/CoordinateUtil.java | 312 ++++++++++++++++++ .../java/cn/hutool/core/util/W84Util.java | 172 ---------- .../java/cn/hutool/core/util/ZipUtil.java | 2 +- .../java/cn/hutool/core/util/ZipUtilTest.java | 26 +- 5 files changed, 331 insertions(+), 184 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java delete mode 100644 hutool-core/src/main/java/cn/hutool/core/util/W84Util.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fff71d756..6cd188fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-10-31) +# 5.7.16 (2021-11-02) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -19,6 +19,7 @@ * 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) * 【core 】 Opt增加peeks方法(pr#445@Gitee) * 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) +* 【core 】 增加CoordinateUtil(pr#446@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java new file mode 100644 index 000000000..d501ec343 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -0,0 +1,312 @@ +package cn.hutool.core.util; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 坐标系转换相关工具类,主流坐标系包括:
+ * + *

+ * 坐标转换相关参考: https://tool.lu/coordinate/
+ * 参考:https://github.com/JourWon/coordinate-transform + * + * @author hongzhe.qin(qin462328037at163.com), looly + * @since 5.7.16 + */ +public class CoordinateUtil { + + /** + * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) + */ + public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + + /** + * 坐标转换参数:π + */ + public static final double PI = 3.1415926535897932384626D; + + /** + * 地球半径(Krasovsky 1940) + */ + public static final double RADIUS = 6378245.0D; + + /** + * 修正参数(偏率ee) + */ + public static final double CORRECTION_PARAM = 0.00669342162296594323D; + + /** + * 判断坐标是否在国外
+ * 火星坐标系 (GCJ-02)只对国内有效,国外无需转换 + * + * @param lng 经度 + * @param lat 纬度 + * @return 坐标是否在国外 + */ + public static boolean outOfChina(double lng, double lat) { + return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271); + } + + //----------------------------------------------------------------------------------- WGS84 + /** + * WGS84 转换为 火星坐标系 (GCJ-02) + * + * @param lng 经度值 + * @param lat 维度值 + * @return 火星坐标 (GCJ-02) + */ + public static Coordinate wgs84ToGcj02(double lng, double lat) { + return new Coordinate(lng, lat).offset(offset(lng, lat, true)); + } + + /** + * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 + * + * @param lng 经度值 + * @param lat 维度值 + * @return bd09 坐标 + */ + public static Coordinate wgs84ToBd09(double lng, double lat) { + final Coordinate gcj02 = wgs84ToGcj02(lng, lat); + return gcj02ToBd09(gcj02.lng, gcj02.lat); + } + + //----------------------------------------------------------------------------------- GCJ-02 + /** + * 火星坐标系 (GCJ-02) 转换为 WGS84 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return WGS84 坐标 + */ + public static Coordinate gcj02ToWgs84(double lng, double lat) { + return new Coordinate(lng, lat).offset(offset(lng, lat, false)); + } + + /** + * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return BD-09 坐标 + */ + public static Coordinate gcj02ToBd09(double lng, double lat) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new Coordinate(bd_lng, bd_lat); + } + + //----------------------------------------------------------------------------------- BD-09 + /** + * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 + * 即 百度 转 谷歌、高德 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return GCJ-02 坐标 + */ + public static Coordinate bd09toGcj02(double lng, double lat) { + double x = lng - 0.0065; + double y = lat - 0.006; + double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + double gg_lng = z * Math.cos(theta); + double gg_lat = z * Math.sin(theta); + return new Coordinate(gg_lng, gg_lat); + } + + /** + * 百度坐标系 (BD-09) 与 WGS84 的转换 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return WGS84坐标 + */ + public static Coordinate bd09toWgs84(double lng, double lat) { + final Coordinate gcj02 = bd09toGcj02(lng, lat); + return gcj02ToWgs84(gcj02.lng, gcj02.lat); + } + + //----------------------------------------------------------------------------------- Private methods begin + + /** + * 转换坐标公共核心 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return 返回结果 + */ + private static double transCore(double lng, double lat) { + double ret = (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * WGS84 与 火星坐标系 (GCJ-02)转换的偏移算法(非精确) + * + * @param lng 经度值 + * @param lat 纬度值 + * @param isPlus 是否正向偏移:WGS84转GCJ-02使用正向,否则使用反向 + * @return 偏移坐标 + */ + private static Coordinate offset(double lng, double lat, boolean isPlus) { + double dlng = transLng(lng - 105.0, lat - 35.0); + double dlat = transLat(lng - 105.0, lat - 35.0); + + double magic = Math.sin(lat / 180.0 * PI); + magic = 1 - CORRECTION_PARAM * magic * magic; + double sqrtMagic = Math.sqrt(magic); + + dlng = (dlng * 180.0) / (RADIUS / sqrtMagic * Math.cos(lat / 180.0 * PI) * PI); + dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtMagic) * PI); + + if(false == isPlus){ + dlng = - dlng; + dlat = - dlat; + } + + return new Coordinate(dlng, dlat); + } + + /** + * 计算经度坐标 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return ret 计算完成后的 + */ + private static double transLng(double lng, double lat) { + double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += transCore(lng, lat); + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 计算纬度坐标 + * + * @param lng 经度 + * @param lat 维度 + * @return ret 计算完成后的 + */ + private static double transLat(double lng, double lat) { + double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += transCore(lng, lat); + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + //----------------------------------------------------------------------------------- Private methods end + + /** + * 坐标经纬度 + * + * @author looly + */ + public static class Coordinate implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 经度 + */ + private double lng; + /** + * 纬度 + */ + private double lat; + + /** + * 构造 + * + * @param lng 经度 + * @param lat 纬度 + */ + public Coordinate(double lng, double lat) { + this.lng = lng; + this.lat = lat; + } + + /** + * 获取经度 + * + * @return 经度 + */ + public double getLng() { + return lng; + } + + /** + * 设置经度 + * + * @param lng 经度 + * @return this + */ + public Coordinate setLng(double lng) { + this.lng = lng; + return this; + } + + /** + * 获取纬度 + * + * @return 纬度 + */ + public double getLat() { + return lat; + } + + /** + * 设置纬度 + * + * @param lat 纬度 + * @return this + */ + public Coordinate setLat(double lat) { + this.lat = lat; + return this; + } + + /** + * 当前坐标偏移指定坐标 + * + * @param offset 偏移量 + * @return this + */ + public Coordinate offset(Coordinate offset){ + this.lng += offset.lng; + this.lat += offset.lng; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Coordinate that = (Coordinate) o; + return Double.compare(that.lng, lng) == 0 && Double.compare(that.lat, lat) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(lng, lat); + } + + @Override + public String toString() { + return "Coordinate{" + + "lng=" + lng + + ", lat=" + lat + + '}'; + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java deleted file mode 100644 index e093d3b96..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.hutool.core.util; - -/** - * 坐标转换相关工具类
- * - * 坐标转换相关参考网址: https://tool.lu/coordinate/ - * @author hongzhe.qin - * @email qin462328037@163.com - * @since 6.0 - */ -public class W84Util { - - /** - * 坐标转换参数:(暂时位置具体名称) - */ - public static final Double X_PI = 3.14159265358979324 * 3000.0 / 180.0; - - /** - * 坐标转换参数:π - */ - public static final Double PI = 3.1415926535897932384626; - - /** - * 地球半径 - */ - public static final Double RADIUS = 6378245.0; - - /** - * 修正参数 - */ - public static final Double CORRECTION_PARAM = 0.00669342162296594323; - - - /** - * 计算维度坐标 - * - * @param lng 经度 - * @param lat 维度 - * @return ret 计算完成后的 - */ - private static Double transForMLat(double lng, double lat) { - Double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); - ret = transCore(ret, lng, lat); - ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; - return ret; - } - - /** - * 计算经度坐标 - * - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return ret 计算完成后的 - */ - private static Double transForMLng(Double lng, Double lat) { - Double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); - ret = transCore(ret, lng, lat); - ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; - return ret; - } - - /** - * 转换坐标公共核心 - * - * @param ret 计算需要返回结果 - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return 返回结果 - */ - private static Double transCore(Double ret, Double lng, Double lat) { - ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; - return ret; - } - - private static Double transW84Core(Double lat){ - return 1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI); - } - - /** - * 火星坐标系 (GCJ-02) 核心计算 - * @param lng 经度值 - * @param lat 纬度值 - * @return 坐标 - */ - private static Double[] transGCJ02Core(Double lng,Double lat){ - Double dlat = transForMLat(lng - 105.0, lat - 35.0); - Double dlng = transForMLng(lng - 105.0, lat - 35.0); - Double magic = transW84Core(lat); - Double sqrtmagic = Math.sqrt(magic); - dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); - dlng = (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); - Double mglat = lat + dlat; - Double mglng = lng + dlng; - return new Double[]{mglng,mglat}; - } - - /** - * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 - * @param lng 经度值 - * @param lat 维度值 - * @return bd09 坐标 - */ - public static Double[] wgs84tobd09(Double lng, Double lat) { - // 第一次转换 - Double dlat = transForMLat(lng - 105.0, lat - 35.0); - Double dlng = transForMLng(lng - 105.0, lat - 35.0); - Double magic = transW84Core(lat); - Double sqrtmagic = Math.sqrt(1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI)); - Double mglat = lat + (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); - Double mglng = lng + (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); - // 第二次转换 - Double z = Math.sqrt(mglng * mglng + mglat * mglat) + 0.00002 * Math.sin(mglat * X_PI); - Double theta = Math.atan2(mglat, mglng) + 0.000003 * Math.cos(mglng * X_PI); - Double bd_lng = z * Math.cos(theta) + 0.0065; - Double bd_lat = z * Math.sin(theta) + 0.006; - return new Double[]{bd_lng,bd_lat}; - } - - /** - * WGS84 转换为 火星坐标系 (GCJ-02) - * - * @param lng - * @param lat - * @returns {*[]} - */ - public static Double[] wgs84togcj02(Double lng, Double lat) { - return transGCJ02Core(lng,lat); - } - - /** - * 火星坐标系 (GCJ-02) 转换为 WGS84 - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return WGS84 坐标 - */ - public static Double[] gcj02towgs84(Double lng, Double lat) { - return transGCJ02Core(lng,lat); - } - - /** - * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 - * 即 百度 转 谷歌、高德 - * @param bd_lon 经度值 - * @param bd_lat 纬度值 - * @return GCJ-02 坐标 - */ - public static Double[] bd09togcj02(Double bd_lon, Double bd_lat) { - Double x = bd_lon - 0.0065; - Double y = bd_lat - 0.006; - Double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); - Double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); - Double gg_lng = z * Math.cos(theta); - Double gg_lat = z * Math.sin(theta); - return new Double[]{gg_lng,gg_lat}; - } - - /** - * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 - * @param lng 经度值 - * @param lat 纬度值 - * @return BD-09 坐标 - */ - public static Double[] gcj02tobd09(Double lng, Double lat) { - double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); - double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); - double bd_lng = z * Math.cos(theta) + 0.0065; - double bd_lat = z * Math.sin(theta) + 0.006; - return new Double[]{bd_lng,bd_lat}; - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 37e77b5d5..10d2ab8d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -412,7 +412,7 @@ public class ZipUtil { /** * 将文件流压缩到目标流中 * - * @param zipOutputStream 目标流,压缩完成不关闭 + * @param zipOutputStream 目标流,压缩完成自动关闭 * @param paths 流数据在压缩文件中的路径或文件名 * @param ins 要压缩的源,添加完成后自动关闭流 * @throws IORuntimeException IO异常 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index a98972e67..309baddf7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -11,6 +11,7 @@ import org.junit.Test; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; @@ -169,15 +170,20 @@ public class ZipUtilTest { String file3 = "d:/test/asn1.key"; String zip = "d:/test/test2.zip"; - try (OutputStream out = new FileOutputStream(zip)){ - //实际应用中, out 为 HttpServletResponse.getOutputStream - ZipUtil.zip(out, Charset.defaultCharset(), false, null, - new File(file1), - new File(file2), - new File(file3) - ); - } catch (IOException e) { - throw new IORuntimeException(e); - } + //实际应用中, out 为 HttpServletResponse.getOutputStream + ZipUtil.zip(FileUtil.getOutputStream(zip), Charset.defaultCharset(), false, null, + new File(file1), + new File(file2), + new File(file3) + ); + } + + @Test + @Ignore + public void zipToStreamTest(){ + String zip = "d:/test/testToStream.zip"; + OutputStream out = FileUtil.getOutputStream(zip); + ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, + new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } }