| | |
| | | package com.flightfeather.uav.common.utils |
| | | |
| | | import kotlin.math.PI |
| | | import kotlin.math.cos |
| | | import kotlin.math.sin |
| | | import kotlin.math.* |
| | | |
| | | object MapUtil { |
| | | |
| | | private const val Ea = 6378137 //赤道半径 |
| | | private const val Eb = 6356725 //极半径 |
| | | |
| | | // 坐标转换参数 |
| | | const val a = 6378245.0; //长半轴 |
| | | const val ee = 0.00669342162296594323; //扁率/*** GCJ02 转换为 WGS84* @param lng* @param lat* @returns {*[]}*/ |
| | | |
| | | /** |
| | | * 根据坐标点、距离和角度,获取另一个坐标 |
| | |
| | | val lat = (dy / ec + pos.second * PI / 180.0) * 180.0 / PI |
| | | return Pair(lng, lat) |
| | | } |
| | | |
| | | /** |
| | | * 获取两个经纬度之间的角度(0度-360度) |
| | | */ |
| | | fun getAngle(lngA: Double, latA: Double, lngB: Double, latB: Double): Double { |
| | | val a = ((90 - latB) * Math.PI) / 180; |
| | | val b = ((90 - latA) * Math.PI) / 180; |
| | | val AOC_BOC = ((lngB - lngA) * Math.PI) / 180; |
| | | val cosc = cos(a) * Math.cos(b) + Math.sin(a) * Math.sin(b) * Math.cos(AOC_BOC); |
| | | val sinc = Math.sqrt(1 - cosc * cosc); |
| | | val sinA = (Math.sin(a) * Math.sin(AOC_BOC)) / sinc; |
| | | val A = (Math.asin(sinA) * 180) / Math.PI; |
| | | var res = 0.0; |
| | | if (lngB > lngA && latB > latA) res = A; |
| | | else if (lngB > lngA && latB < latA) res = 180 - A; |
| | | else if (lngB < lngA && latB < latA) res = 180 - A; |
| | | else if (lngB < lngA && latB > latA) res = 360 + A; |
| | | else if (lngB > lngA && latB == latA) res = 90.0; |
| | | else if (lngB < lngA && latB == latA) res = 270.0; |
| | | else if (lngB == lngA && latB > latA) res = 0.0; |
| | | else if (lngB == lngA && latB < latA) res = 180.0; |
| | | return res; |
| | | } |
| | | |
| | | /** |
| | | * 获取两经纬度间的距离 |
| | | * @return 返回两点间距离,单位:米 |
| | | */ |
| | | fun getDistance(lng1: Double, lat1: Double, lng2: Double, lat2: Double): Double { |
| | | // lat1 = lat1 || 0; |
| | | // lng1 = lng1 || 0; |
| | | // lat2 = lat2 || 0; |
| | | // lng2 = lng2 || 0; |
| | | |
| | | val rad1 = (lat1 * Math.PI) / 180.0; |
| | | val rad2 = (lat2 * Math.PI) / 180.0; |
| | | val a = rad1 - rad2; |
| | | val b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0; |
| | | val distance = |
| | | Ea * 2 * asin( |
| | | Math.sqrt( |
| | | Math.pow( |
| | | Math.sin(a / 2), |
| | | 2.0 |
| | | ) + Math.cos(rad1) * Math.cos(rad2) * Math.pow(Math.sin(b / 2), 2.0) |
| | | ) |
| | | ); |
| | | |
| | | return distance; |
| | | } |
| | | |
| | | /** |
| | | * 角度增减,确保角度处于0 - 360度之间 |
| | | * @param angle 原角度 |
| | | * @param offset 偏移量 |
| | | */ |
| | | fun plusAngle(angle: Double, offset: Double): Double { |
| | | val result = angle + offset; |
| | | return if (result > 360) { |
| | | result - 360; |
| | | } else if (result < 0) { |
| | | result + 360; |
| | | } else { |
| | | result; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算多边形的四至范围 |
| | | * @param polygon 多边形坐标点数组 |
| | | * @return 四至范围,顺序为最小经度,最大经度, 最小纬度,最大纬度 |
| | | */ |
| | | fun calFourBoundaries(polygon: List<Pair<Double, Double>>): List<Double> { |
| | | // 计算多边形顶点经度范围和纬度范围 |
| | | val xsSort = polygon.map { it.first }.sorted() |
| | | val ysSort = polygon.map { it.second }.sorted() |
| | | |
| | | val xMin = xsSort[0] |
| | | val yMin = ysSort[0] |
| | | val xMax = xsSort[xsSort.lastIndex] |
| | | val yMax = ysSort[ysSort.lastIndex] |
| | | |
| | | return listOf(xMin, xMax, yMin, yMax) |
| | | } |
| | | |
| | | /** |
| | | * 判断坐标点是否在多边形的四至范围内 |
| | | * @param point 坐标点 |
| | | * @param polygon 多边形坐标点数组 |
| | | */ |
| | | fun inBBox(point: Pair<Double, Double>, polygon: List<Pair<Double, Double>>): Boolean { |
| | | |
| | | val x = point.first |
| | | val y = point.second |
| | | // 计算多边形顶点经度范围和纬度范围 |
| | | val fb = calFourBoundaries(polygon) |
| | | // val xsSort = polygon.map { it.first }.sorted() |
| | | // val ysSort = polygon.map { it.second }.sorted() |
| | | // |
| | | // val xMin = xsSort[0] |
| | | // val yMin = ysSort[0] |
| | | // val xMax = xsSort[xsSort.lastIndex] |
| | | // val yMax = ysSort[ysSort.lastIndex] |
| | | |
| | | val xMin = fb[0] |
| | | val xMax = fb[1] |
| | | val yMin = fb[2] |
| | | val yMax = fb[3] |
| | | |
| | | return x >= xMin && x <= xMax && y >= yMin && y <= yMax |
| | | } |
| | | |
| | | /** |
| | | * 判断坐标点是否在多边形的边上 |
| | | * @param point 坐标点 |
| | | * @param polygon 多边形坐标点数组 |
| | | */ |
| | | fun onBorder(point: Pair<Double, Double>, polygon: List<Pair<Double, Double>>): Boolean { |
| | | var res = false |
| | | // 循环判断每一条边 |
| | | for (i in polygon.indices) { |
| | | val p1 = polygon[i] |
| | | val p2 = if (i + 1 == polygon.size) { |
| | | polygon[0] |
| | | } else { |
| | | polygon[i + 1] |
| | | } |
| | | // 计算边的两个顶点纬度差和经度差的比值 |
| | | val k1 = (p2.second - p1.second) / (p2.first - p1.first) |
| | | // 计算坐标点和其中一个顶点的纬度差和经度差的比值 |
| | | val k2 = (p2.second - point.second) / (p2.first - point.first) |
| | | // 如果比值相同,说明三个点在同一直线上,即坐标点在边上 |
| | | if (k1 == k2) { |
| | | res = true |
| | | break |
| | | } |
| | | } |
| | | return res |
| | | } |
| | | |
| | | /** |
| | | * 判断坐标点是否在多边形内部(射线法) |
| | | * @param point 坐标点 |
| | | * @param polygon 多边形坐标点数组 |
| | | */ |
| | | fun inPolygon(point: Pair<Double, Double>, polygon: List<Pair<Double, Double>>): Boolean { |
| | | val x = point.first |
| | | val y = point.second |
| | | var j = polygon.size - 1 |
| | | var odd = false |
| | | for (i in polygon.indices) { |
| | | if ( |
| | | ((polygon[i].second > y) != (polygon[j].second > y)) |
| | | && (x < ((polygon[j].first - polygon[i].first) * (y - polygon[i].second) |
| | | / (polygon[j].second - polygon[i].second) + polygon[i].first)) |
| | | ) { |
| | | odd = !odd; |
| | | } |
| | | j = i; |
| | | } |
| | | return odd |
| | | } |
| | | |
| | | /** |
| | | * 判断坐标点是否在多边形内部 |
| | | */ |
| | | fun isPointInPolygon(point: Pair<Double, Double>, polygon: List<Pair<Double, Double>>): Boolean { |
| | | if (polygon.size < 3) throw IllegalArgumentException("not a polygon") |
| | | |
| | | // 不在四至范围内,则一定不在多边形内 |
| | | if (!inBBox(point, polygon)) return false |
| | | // 在多边形边上,也认为在多边形内 |
| | | if (onBorder(point, polygon)) return true |
| | | // 计算是否在多边形内部 |
| | | return inPolygon(point, polygon) |
| | | } |
| | | |
| | | /** |
| | | * 判断经纬度是否在国内 |
| | | * @return true: 经纬度不在国内,false:经纬度在国内 |
| | | */ |
| | | fun outOfChina(point: Pair<Double, Double>): Boolean { |
| | | val lng = point.first |
| | | val lat = point.second |
| | | return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55) |
| | | } |
| | | |
| | | private fun transformLat(point: Pair<Double, Double>): Double { |
| | | val lng = point.first |
| | | val lat = point.second |
| | | var ret = -100.0 + |
| | | 2.0 * lng + |
| | | 3.0 * lat + |
| | | 0.2 * lat * lat + |
| | | 0.1 * lng * lat + |
| | | 0.2 * sqrt(abs(lng)) |
| | | ret += ((20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0) / 3.0 |
| | | ret += ((20.0 * sin(lat * PI) + 40.0 * sin((lat / 3.0) * PI)) * 2.0) / 3.0 |
| | | ret += ((160.0 * sin((lat / 12.0) * PI) + 320 * sin((lat * PI) / 30.0)) * 2.0) / 3.0 |
| | | return ret; |
| | | } |
| | | |
| | | private fun transformLng(point: Pair<Double, Double>): Double { |
| | | val lng = point.first |
| | | val lat = point.second |
| | | var ret = 300.0 + |
| | | lng + |
| | | 2.0 * lat + |
| | | 0.1 * lng * lng + |
| | | 0.1 * lng * lat + |
| | | 0.1 * sqrt(abs(lng)); |
| | | ret += ((20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0) / 3.0; |
| | | ret += ((20.0 * sin(lng * PI) + 40.0 * sin((lng / 3.0) * PI)) * 2.0) / 3.0; |
| | | ret += ((150.0 * sin((lng / 12.0) * PI) + 300.0 * sin((lng / 30.0) * PI)) * 2.0) / 3.0 |
| | | return ret; |
| | | } |
| | | |
| | | /** |
| | | * 火星坐标系转WGS84坐标系 |
| | | */ |
| | | fun gcj02ToWgs84(point: Pair<Double, Double>): Pair<Double, Double> { |
| | | if (outOfChina(point)) { |
| | | return point; |
| | | } else { |
| | | val lng = point.first |
| | | val lat = point.second |
| | | var dlat = transformLat(lng - 105.0 to lat - 35.0); |
| | | var dlng = transformLng(lng - 105.0 to lat - 35.0); |
| | | val radlat = (lat / 180.0) * PI; |
| | | var magic = sin(radlat); |
| | | magic = 1 - ee * magic * magic; |
| | | val sqrtmagic = sqrt(magic); |
| | | dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); |
| | | dlng = (dlng * 180.0) / ((a / sqrtmagic) * cos(radlat) * PI); |
| | | val mglat = Math.round((lat * 2 - lat - dlat) * 1000000).toDouble() / 1000000; |
| | | val mglng = Math.round((lng * 2 - lng - dlng) * 1000000).toDouble() / 1000000; |
| | | return mglng to mglat |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * WGS84坐标系转火星坐标系 |
| | | */ |
| | | fun wgs84ToGcj02(point: Pair<Double, Double>): Pair<Double, Double> { |
| | | if (outOfChina(point)) { |
| | | return point |
| | | } else { |
| | | val lng = point.first |
| | | val lat = point.second |
| | | var dLat = transformLat(lng - 105.0 to lat - 35.0); |
| | | var dLon = transformLng(lng - 105.0 to lat - 35.0); |
| | | val radLat = (lat / 180.0) * PI; |
| | | var magic = sin(radLat); |
| | | magic = 1 - ee * magic * magic; |
| | | val sqrtMagic = sqrt(magic); |
| | | dLat = (dLat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * PI); |
| | | dLon = (dLon * 180.0) / ((a / sqrtMagic) * cos(radLat) * PI); |
| | | val mgLat = lat + dLat; |
| | | val mgLon = lng + dLon; |
| | | return mgLon to mgLat |
| | | } |
| | | } |
| | | } |