Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
p x
mycomutils
Commits
745a80a3
Commit
745a80a3
authored
Dec 31, 2025
by
p x
Browse files
first
parent
a8643e25
Changes
14
Hide whitespace changes
Inline
Side-by-side
.gitignore
0 → 100644
View file @
745a80a3
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
LICENSE
0 → 100644
View file @
745a80a3
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
build.gradle.kts
0 → 100644
View file @
745a80a3
plugins
{
alias
(
libs
.
plugins
.
android
.
library
)
alias
(
libs
.
plugins
.
kotlin
.
android
)
}
android
{
namespace
=
"commons.util"
compileSdk
=
35
defaultConfig
{
minSdk
=
29
testInstrumentationRunner
=
"androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles
(
"consumer-rules.pro"
)
}
buildTypes
{
release
{
isMinifyEnabled
=
false
proguardFiles
(
getDefaultProguardFile
(
"proguard-android-optimize.txt"
),
"proguard-rules.pro"
)
}
}
compileOptions
{
sourceCompatibility
=
JavaVersion
.
VERSION_11
targetCompatibility
=
JavaVersion
.
VERSION_11
}
kotlinOptions
{
jvmTarget
=
"11"
}
// packaging {
// jniLibs {
// excludes.add("META-INF/*******")
// }
// resources {
// excludes.addAll(
// listOf(
// "META-INF/*******",
// "META-INF/INDEX.LIST",
// "META-INF/io.netty.versions.properties"
// )
// )
// }
// }
}
dependencies
{
implementation
(
libs
.
androidx
.
core
.
ktx
)
implementation
(
libs
.
androidx
.
appcompat
)
// implementation(libs.material)
testImplementation
(
libs
.
junit
)
androidTestImplementation
(
libs
.
androidx
.
junit
)
androidTestImplementation
(
libs
.
androidx
.
espresso
.
core
)
// GeoTools
api
(
"org.geotools:gt-main:28.2"
){
exclude
(
"org.eclipse.emf"
,
"*"
)
}
}
\ No newline at end of file
src/main/AndroidManifest.xml
0 → 100644
View file @
745a80a3
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
</manifest>
\ No newline at end of file
src/main/java/commons/gis/BasicTools.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
kotlin.math.atan2
import
kotlin.math.cos
import
kotlin.math.pow
import
kotlin.math.sin
import
kotlin.math.sqrt
/**基础工具**/
object
BasicTools
{
/**
* 计算2点距离
* @return 返回米
* **/
fun
calculateHaversineDistance
(
lng1
:
Double
,
lat1
:
Double
,
lng2
:
Double
,
lat2
:
Double
):
Double
{
val
earthRadius
=
6371000.0
// 地球半径,单位:米
val
lat1
=
Math
.
toRadians
(
lat1
)
val
lon1
=
Math
.
toRadians
(
lng1
)
val
lat2
=
Math
.
toRadians
(
lat2
)
val
lon2
=
Math
.
toRadians
(
lng2
)
val
dlat
=
lat2
-
lat1
val
dlon
=
lon2
-
lon1
val
a
=
sin
(
dlat
/
2
).
pow
(
2
)
+
cos
(
lat1
)
*
cos
(
lat2
)
*
sin
(
dlon
/
2
).
pow
(
2
)
val
c
=
2
*
atan2
(
sqrt
(
a
),
sqrt
(
1
-
a
))
return
earthRadius
*
c
}
/**
* 根据两个坐标点计算航向角
* @param fromLon 起始点经度
* @param fromLat 起始点纬度
* @param toLon 终点经度
* @param toLat 终点纬度
* @return 航向角(度),范围 0-360
*/
fun
calculateBearing
(
fromLon
:
Double
,
fromLat
:
Double
,
toLon
:
Double
,
toLat
:
Double
):
Double
{
// 将度转换为弧度
val
lat1
=
Math
.
toRadians
(
fromLat
)
val
lat2
=
Math
.
toRadians
(
toLat
)
val
deltaLon
=
Math
.
toRadians
(
toLon
-
fromLon
)
// 计算航向角
val
y
=
sin
(
deltaLon
)
*
cos
(
lat2
)
val
x
=
cos
(
lat1
)
*
sin
(
lat2
)
-
sin
(
lat1
)
*
cos
(
lat2
)
*
cos
(
deltaLon
)
val
bearing
=
Math
.
toDegrees
(
atan2
(
y
,
x
))
// 确保航向角在 0-360 度范围内
return
(
bearing
+
360
)
%
360
}
}
\ No newline at end of file
src/main/java/commons/gis/GeoRectangleUtils.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
kotlin.math.asin
import
kotlin.math.atan2
import
kotlin.math.cos
import
kotlin.math.sin
import
kotlin.math.sqrt
import
kotlin.math.PI
object
GeoRectangleUtils
{
/**
* 根据中心点、宽度和高度生成矩形(米为单位)不带方向
*/
fun
createRectangleFromCenter
(
centerLng
:
Double
,
centerLat
:
Double
,
widthMeters
:
Double
=
6.0
,
heightMeters
:
Double
=
8.0
):
List
<
List
<
Double
>>
{
val
halfWidth
=
widthMeters
/
2
val
halfHeight
=
heightMeters
/
2
// 计算四个角点
val
topLeft
=
calculateDestinationPoint
(
centerLng
,
centerLat
,
315.0
,
sqrt
(
halfWidth
*
halfWidth
+
halfHeight
*
halfHeight
)
)
val
topRight
=
calculateDestinationPoint
(
centerLng
,
centerLat
,
45.0
,
sqrt
(
halfWidth
*
halfWidth
+
halfHeight
*
halfHeight
)
)
val
bottomRight
=
calculateDestinationPoint
(
centerLng
,
centerLat
,
135.0
,
sqrt
(
halfWidth
*
halfWidth
+
halfHeight
*
halfHeight
)
)
val
bottomLeft
=
calculateDestinationPoint
(
centerLng
,
centerLat
,
225.0
,
sqrt
(
halfWidth
*
halfWidth
+
halfHeight
*
halfHeight
)
)
return
listOf
(
topLeft
,
topRight
,
bottomRight
,
bottomLeft
,
topLeft
)
// 闭合
}
// 矩形尺寸:半宽1000米,半高500米
/**根据中心的生产矩形,带方向***/
fun
generateCenterRect
(
centerLng
:
Double
,
centerLat
:
Double
,
direction
:
Double
,
halfWidth
:
Double
=
5.0
,
halfHeight
:
Double
=
5.0
):
List
<
List
<
Double
>>
{
// 将方向角转换为数学角度(逆时针从x轴正方向)
val
angleRad
=
Math
.
toRadians
(
90
-
direction
)
// 计算四个顶点的偏移量(米)
val
offsets
=
listOf
(
// 右上顶点
Pair
(
halfWidth
*
cos
(
angleRad
)
-
halfHeight
*
sin
(
angleRad
),
halfWidth
*
sin
(
angleRad
)
+
halfHeight
*
cos
(
angleRad
)
),
// 右下顶点
Pair
(
halfWidth
*
cos
(
angleRad
)
+
halfHeight
*
sin
(
angleRad
),
halfWidth
*
sin
(
angleRad
)
-
halfHeight
*
cos
(
angleRad
)
),
// 左下顶点
Pair
(
-
halfWidth
*
cos
(
angleRad
)
+
halfHeight
*
sin
(
angleRad
),
-
halfWidth
*
sin
(
angleRad
)
-
halfHeight
*
cos
(
angleRad
)
),
// 左上顶点
Pair
(
-
halfWidth
*
cos
(
angleRad
)
-
halfHeight
*
sin
(
angleRad
),
-
halfWidth
*
sin
(
angleRad
)
+
halfHeight
*
cos
(
angleRad
)
),
// 右上顶点
Pair
(
halfWidth
*
cos
(
angleRad
)
-
halfHeight
*
sin
(
angleRad
),
halfWidth
*
sin
(
angleRad
)
+
halfHeight
*
cos
(
angleRad
)
),
)
// 将米偏移量转换为经纬度坐标
var
ps
=
offsets
.
map
{
(
dx
,
dy
)
->
metersToGeoPoint
(
centerLng
,
centerLat
,
dx
,
dy
)
}
return
ps
}
/**
* 将米单位的偏移量转换为经纬度坐标
*/
private
fun
metersToGeoPoint
(
centerLng
:
Double
,
centerLat
:
Double
,
dx
:
Double
,
dy
:
Double
):
List
<
Double
>
{
// 地球半径(米)
val
EARTH_RADIUS
=
6378137.0
val
latRad
=
Math
.
toRadians
(
centerLat
)
// 纬度每度对应的米数
val
latPerMeter
=
1.0
/
(
PI
*
EARTH_RADIUS
/
180.0
)
// 经度每度对应的米数(随纬度变化)
val
lonPerMeter
=
1.0
/
(
PI
*
EARTH_RADIUS
*
cos
(
latRad
)
/
180.0
)
val
newLat
=
centerLat
+
dy
*
latPerMeter
val
newLon
=
centerLng
+
dx
*
lonPerMeter
return
listOf
(
newLon
,
newLat
)
}
/**
* 根据前方点和方向生成矩形
*/
fun
createRectangleInFront
(
frontPoint
:
List
<
Double
>,
bearingDegrees
:
Double
,
// 方向角度(0-360,0表示北)
widthMeters
:
Double
=
3.0
,
depthMeters
:
Double
=
8.0
):
List
<
List
<
Double
>>
{
// 计算矩形中心点(在前方 depthMeters/2 处)
val
center
=
calculateDestinationPoint
(
frontPoint
[
0
],
frontPoint
[
1
],
bearingDegrees
,
depthMeters
/
2
)
// 垂直于方向的宽度
val
perpendicularBearing1
=
(
bearingDegrees
+
90
)
%
360
val
perpendicularBearing2
=
(
bearingDegrees
-
90
+
360
)
%
360
// 前方点
val
frontCenter
=
calculateDestinationPoint
(
center
[
0
],
center
[
1
],
bearingDegrees
,
depthMeters
/
2
)
// 计算四个角点
val
frontLeft
=
calculateDestinationPoint
(
frontCenter
[
0
],
frontCenter
[
1
],
perpendicularBearing1
,
widthMeters
/
2
)
val
frontRight
=
calculateDestinationPoint
(
frontCenter
[
0
],
frontCenter
[
1
],
perpendicularBearing2
,
widthMeters
/
2
)
val
backLeft
=
calculateDestinationPoint
(
frontPoint
[
0
],
frontCenter
[
1
],
perpendicularBearing1
,
widthMeters
/
2
)
val
backRight
=
calculateDestinationPoint
(
frontPoint
[
0
],
frontCenter
[
1
],
perpendicularBearing2
,
widthMeters
/
2
)
return
listOf
(
backLeft
,
frontLeft
,
frontRight
,
backRight
,
backLeft
)
}
/**
* 根据中心的生产矩形,带方向
* @param rotation 航向角
*/
fun
createRotatedRectangleFromCenter
(
centerLon
:
Double
,
centerLat
:
Double
,
rotation
:
Double
=
0.0
,
widthMeters
:
Double
=
100.0
,
heightMeters
:
Double
=
80.0
):
List
<
List
<
Double
>>
{
// 地球半径(米)
val
EARTH_RADIUS
=
6371000.0
// 将米转换为经纬度(近似计算)
val
latDelta
=
(
heightMeters
/
2
)
/
EARTH_RADIUS
*
(
180
/
Math
.
PI
)
val
lonDelta
=
(
widthMeters
/
2
)
/
(
EARTH_RADIUS
*
cos
(
Math
.
toRadians
(
centerLat
)))
*
(
180
/
Math
.
PI
)
var
result
=
mutableListOf
<
List
<
Double
>>()
// 左上角
result
.
add
(
listOf
(
centerLon
-
lonDelta
,
centerLat
+
latDelta
))
// 右上角
result
.
add
(
listOf
(
centerLon
+
lonDelta
,
centerLat
+
latDelta
))
// 右下角
result
.
add
(
listOf
(
centerLon
+
lonDelta
,
centerLat
-
latDelta
))
// 左下角
result
.
add
(
listOf
(
centerLon
-
lonDelta
,
centerLat
-
latDelta
))
result
.
add
(
listOf
(
centerLon
-
lonDelta
,
centerLat
+
latDelta
))
return
result
}
/**
* 计算目标点(根据起点、方向和距离)
*/
private
fun
calculateDestinationPoint
(
startLng
:
Double
,
startLat
:
Double
,
bearing
:
Double
,
distanceMeters
:
Double
):
List
<
Double
>
{
val
earthRadius
=
6371000.0
// 地球半径(米)
val
startLatRad
=
Math
.
toRadians
(
startLat
)
val
startLngRad
=
Math
.
toRadians
(
startLng
)
val
bearingRad
=
Math
.
toRadians
(
bearing
)
val
endLatRad
=
asin
(
sin
(
startLatRad
)
*
cos
(
distanceMeters
/
earthRadius
)
+
cos
(
startLatRad
)
*
sin
(
distanceMeters
/
earthRadius
)
*
cos
(
bearingRad
)
)
val
endLngRad
=
startLngRad
+
atan2
(
sin
(
bearingRad
)
*
sin
(
distanceMeters
/
earthRadius
)
*
cos
(
startLatRad
),
cos
(
distanceMeters
/
earthRadius
)
-
sin
(
startLatRad
)
*
sin
(
endLatRad
)
)
return
listOf
(
Math
.
toDegrees
(
endLngRad
),
Math
.
toDegrees
(
endLatRad
))
}
}
\ No newline at end of file
src/main/java/commons/gis/LineCrossPolygonChecker.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.withContext
import
org.locationtech.jts.geom.Coordinate
import
org.locationtech.jts.geom.LineString
import
org.locationtech.jts.geom.LinearRing
import
org.locationtech.jts.geom.Polygon
/**线面交叉判断***/
object
LineCrossPolygonChecker
{
/**
* 计算、面与线相交
* @param 路径点串
* @param area 多边形的面
* @return 线与面相交的点 起点和终点
*/
suspend
fun
cauLineAreaCross
(
routes
:
List
<
List
<
Double
>>,
area
:
List
<
List
<
Double
>>
):
List
<
List
<
Double
>>
{
return
withContext
(
Dispatchers
.
Default
)
{
// 方法1:直接使用坐标数组
var
lineString
=
MGeoTools
.
createLine
(
routes
)
//生成多边形
// 创建 LinearRing
val
lRing
:
LinearRing
=
MGeoTools
.
createLinearRing
(
area
)
// 创建多边形(无孔洞)
val
polygon
=
MGeoTools
.
createPolygonFromLinearRing
(
lRing
)
var
crossRet
=
lineCrossArea
(
polygon
,
lineString
)
// 计算相交部分
return
@withContext
crossRet
}
}
/***线和面相交部分**/
fun
lineCrossArea
(
polygon
:
Polygon
?,
lineString
:
LineString
?):
List
<
List
<
Double
>>
{
if
(
polygon
==
null
||
lineString
==
null
)
{
return
emptyList
()
}
// 计算相交部分
val
intersection
=
polygon
.
intersection
(
lineString
)
if
(
intersection
.
isEmpty
)
{
return
emptyList
()
}
// val coordinates = mutableListOf<Coordinate>()
var
crossP
=
intersection
.
coordinates
var
list
=
crossP
.
map
{
listOf
(
it
.
x
,
it
.
y
)
}
return
list
}
}
\ No newline at end of file
src/main/java/commons/gis/MGeoTools.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
org.locationtech.jts.geom.Coordinate
import
org.locationtech.jts.geom.GeometryFactory
import
org.locationtech.jts.geom.LineString
import
org.locationtech.jts.geom.LinearRing
import
org.locationtech.jts.geom.Point
import
org.locationtech.jts.geom.Polygon
object
MGeoTools
{
// 创建 GeometryFactory
private
var
geometryFactory
:
GeometryFactory
=
GeometryFactory
()
/**
* 创建点对象
* @param x 经度
*/
fun
createPoint
(
x
:
Double
,
y
:
Double
):
Point
{
val
coord
=
Coordinate
(
x
,
y
)
return
geometryFactory
.
createPoint
(
coord
)
}
/**
* 生成线段
* @param points 经纬度线段
*/
fun
createLine
(
points
:
List
<
List
<
Double
>>):
LineString
{
val
lines
=
points
.
map
{
Coordinate
(
it
[
0
],
it
[
1
])
}.
toTypedArray
()
var
lineString
=
geometryFactory
.
createLineString
(
lines
)
return
lineString
}
/**
* 创建 LinearRing
* @param points 经纬度线段
*/
fun
createLinearRing
(
points
:
List
<
List
<
Double
>>):
LinearRing
{
val
lines
=
points
.
map
{
Coordinate
(
it
[
0
],
it
[
1
])
}.
toTypedArray
()
var
linearRing
=
geometryFactory
.
createLinearRing
(
lines
)
return
linearRing
}
// 创建多边形(无孔洞)
fun
createPolygonFromLinearRing
(
linearRing
:
LinearRing
):
Polygon
{
return
geometryFactory
.
createPolygon
(
linearRing
,
null
)
}
/***根据坐标生成前方矩形***/
fun
createRectangleFont
(
lng
:
Double
,
lat
:
Double
,
heading
:
Double
,
width
:
Double
=
3.0
,
length
:
Double
=
10.0
,
):
Polygon
{
var
rectanglePoints
=
GeoRectangleUtils
.
createRectangleInFront
(
listOf
(
lng
,
lat
),
heading
,
width
,
length
)
val
coordinates
=
rectanglePoints
.
map
{
Coordinate
(
it
[
0
],
it
[
1
])
}.
toMutableList
()
var
polygon
=
geometryFactory
.
createPolygon
(
coordinates
.
toTypedArray
())
return
polygon
}
/***根据中心点生成指定方向的矩形**/
fun
createRectangleCenter
(
lng
:
Double
,
lat
:
Double
,
heading
:
Double
,
halfWidth
:
Double
=
2.0
,
halfHeight
:
Double
=
5.0
):
Polygon
{
var
rectanglePoints
=
GeoRectangleUtils
.
generateCenterRect
(
lng
,
lat
,
heading
,
halfWidth
,
halfHeight
)
val
coordinates
=
rectanglePoints
.
map
{
Coordinate
(
it
[
0
],
it
[
1
])
}.
toMutableList
()
// var one = myRectangle.corners.first()
// coordinates.add(Coordinate(one[0], one[1]))
var
polygon
=
geometryFactory
.
createPolygon
(
coordinates
.
toTypedArray
())
return
polygon
}
/**
* 计算线段里的点是否在面里
* @return 返回线段里点的下标
*/
fun
lineContainsArea
(
polygon
:
Polygon
?,
lineString
:
LineString
?):
Int
{
if
(
polygon
==
null
||
lineString
==
null
)
{
return
0
}
lineString
.
coordinates
.
forEachIndexed
{
index
,
cood
->
val
sinPoint
=
geometryFactory
.
createPoint
(
cood
)
if
(
polygon
.
contains
(
sinPoint
))
{
return
index
}
}
return
0
}
}
\ No newline at end of file
src/main/java/commons/gis/PointInPolygonChecker.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
org.locationtech.jts.geom.Point
import
org.locationtech.jts.geom.Polygon
/**点面关系判断*/
object
PointInPolygonChecker
{
//老点
// private var oldPointX = 0.0
// private var oldPointY = 0.0
private
var
oldPoint
:
Point
?
=
null
// private var oldPointY = 0.0
//生成的矩形
// private var pRectangle: Polygon? = null
/***点是否在多边形内:*/
fun
isPointInPolygon
(
point
:
Point
,
polygon
:
Polygon
):
Boolean
{
return
polygon
.
contains
(
point
)
}
/***点是否在多边形内:*/
fun
isPointInPolygon
(
lng
:
Double
,
lat
:
Double
,
polygon
:
Polygon
):
Boolean
{
// 创建点
val
point
=
MGeoTools
.
createPoint
(
lng
,
lat
)
return
polygon
.
contains
(
point
)
}
/**
* 判断点是否在多边形内
* @param cLat 当前经纬度
* @param cLng
* @param pointX 待判断点经度
* @param pointY 待判断点纬度
*/
/* fun isPointInPolygonRectCenter(
cLng: Double,
cLat: Double,
pointX: Double,
pointY: Double,
head: Double = 0.0
): Boolean {
val point = MGeoTools.createPoint(cLng, cLat)
*//* if (oldPoint != null) {
// 变化的2点距离
var pdis =
BasicTools.calculateHaversineDistance(pointX, pointY, oldPoint!!.x, oldPoint!!.y)
println("-------中心点的变化距离 pdis = ${pdis}")
if (pdis < 40) {
return true
}
}
oldPoint = point*//*
//以自己为中心点生成矩形
var pRectangle = MGeoTools.createRectangleCenter(cLng, cLat, head, 50.0, 50.0)
return pRectangle.contains(point)
}*/
}
\ No newline at end of file
src/main/java/commons/gis/TimeBasedGeoInterpolator.kt
0 → 100644
View file @
745a80a3
package
commons.gis
import
kotlin.math.PI
import
kotlin.math.acos
import
kotlin.math.asin
import
kotlin.math.atan2
import
kotlin.math.cos
import
kotlin.math.max
import
kotlin.math.min
import
kotlin.math.pow
import
kotlin.math.sin
import
kotlin.math.sqrt
/**
* 带时间戳的地理坐标+航向角模型
* @property lat 纬度
* @property lng 经度
* @property heading 航向角(0-360°,正北为0°,顺时针)
* @property timestamp 时间戳(毫秒)
*/
data class
TimedGeoPoint
(
val
lat
:
Double
,
val
lng
:
Double
,
val
heading
:
Float
,
val
timestamp
:
Long
)
/**
* 三维笛卡尔坐标数据类
* @param x x轴坐标
* @param y y轴坐标
* @param z z轴坐标
*/
data class
CartesianPoint
(
val
x
:
Double
,
val
y
:
Double
,
val
z
:
Double
)
/**
* 基于时间的地理坐标插值工具类
*
* 核心功能:根据起始点和结束点的时间戳、经纬度、航向角,计算任意时间点的插值结果
* 适用于:轨迹回放、导航预测、运动轨迹模拟等场景
*
* 支持两种插值模式:
* - 线性插值:适用于短距离(<10km)场景,计算效率高
* - 球面插值:适用于长距离(>10km)场景,考虑地球曲率,精度更高
*/
object
TimeBasedGeoInterpolator
{
// 地球半径(米)
private
const
val
EARTH_RADIUS
=
6371000.0
/**
* 角度转弧度
*/
private
fun
Double
.
toRadians
():
Double
=
this
*
PI
/
180.0
/**
* 弧度转角度
*/
private
fun
Double
.
toDegrees
():
Double
=
this
*
180.0
/
PI
/**
* 根据目标时间,插值计算对应时刻的地理坐标和航向角
*
* @param start 起始点(包含经纬度、航向角、时间戳)
* @param end 结束点(包含经纬度、航向角、时间戳)
* @param targetTime 目标时间戳(毫秒)
* @param useSpherical 是否使用球面插值(true:球面插值,false:线性插值)
* @return 目标时间对应的插值点(包含计算出的经纬度、航向角和目标时间戳)
*
* @throws IllegalArgumentException 当起止时间戳无效时(end.timestamp <= start.timestamp)
* @sample
* ```kotlin
* val startTime = System.currentTimeMillis()
* val endTime = startTime + 5000 // 5秒后
* val startPoint = TimedGeoPoint(39.908823, 116.397470, 90.0f, startTime)
* val endPoint = TimedGeoPoint(39.916404, 116.397096, 100.0f, endTime)
*
* // 计算2秒后的位置
* val interpolatedPoint = TimeBasedGeoInterpolator.interpolateAtTime(
* start = startPoint,
* end = endPoint,
* targetTime = startTime + 2000,
* useSpherical = false
* )
* ```
*/
fun
interpolateAtTime
(
start
:
TimedGeoPoint
,
end
:
TimedGeoPoint
,
targetTime
:
Long
,
useSpherical
:
Boolean
=
false
):
TimedGeoPoint
{
// 边界处理:目标时间超出范围,直接返回起止点
if
(
targetTime
<=
start
.
timestamp
)
return
start
.
copy
(
timestamp
=
targetTime
)
if
(
targetTime
>=
end
.
timestamp
)
return
end
.
copy
(
timestamp
=
targetTime
)
// 1. 计算时间比例(0~1)
val
totalDuration
=
end
.
timestamp
-
start
.
timestamp
val
timeRatio
=
(
targetTime
-
start
.
timestamp
).
toDouble
()
/
totalDuration
// 2. 经纬度插值
val
(
interpolatedLat
,
interpolatedLng
)
=
if
(
useSpherical
)
{
// interpolateSphericalLatLng(start, end, timeRatio)
slerp
(
start
,
end
,
timeRatio
)
}
else
{
interpolateLinearLatLng
(
start
,
end
,
timeRatio
)
}
// 3. 航向角插值(处理360°环绕)
val
interpolatedHeading
=
interpolateHeading
(
start
.
heading
,
end
.
heading
,
timeRatio
)
return
TimedGeoPoint
(
lat
=
interpolatedLat
,
lng
=
interpolatedLng
,
heading
=
interpolatedHeading
,
timestamp
=
targetTime
)
}
/**
* 按固定时间间隔生成等分的插值点列表
*
* @param start 起始点(包含经纬度、航向角、时间戳)
* @param end 结束点(包含经纬度、航向角、时间戳)
* @param intervalMs 时间间隔(毫秒),例如:1000表示每秒生成一个点
* @param useSpherical 是否使用球面插值
* @return 按时间升序排列的插值点列表,包含起始点和结束点
*
* @throws IllegalArgumentException 当时间间隔小于等于0或起止时间无效时
* @sample
* ```kotlin
* val startTime = System.currentTimeMillis()
* val endTime = startTime + 10000 // 10秒后
* val startPoint = TimedGeoPoint(39.908823, 116.397470, 90.0f, startTime)
* val endPoint = TimedGeoPoint(39.916404, 116.397096, 100.0f, endTime)
*
* // 每秒生成一个插值点
* val points = TimeBasedGeoInterpolator.interpolateByTimeInterval(
* start = startPoint,
* end = endPoint,
* intervalMs = 1000,
* useSpherical = false
* )
* // points.size 将是 11(包含起始点和结束点)
* ```
*/
fun
interpolateByTimeInterval
(
start
:
TimedGeoPoint
,
end
:
TimedGeoPoint
,
intervalMs
:
Long
,
useSpherical
:
Boolean
=
false
):
List
<
TimedGeoPoint
>
{
if
(
intervalMs
<=
0
||
end
.
timestamp
<=
start
.
timestamp
)
return
listOf
(
start
,
end
)
val
interpolatedPoints
=
mutableListOf
<
TimedGeoPoint
>()
interpolatedPoints
.
add
(
start
)
var
currentTime
=
start
.
timestamp
+
intervalMs
while
(
currentTime
<
end
.
timestamp
)
{
val
interpolatedPoint
=
interpolateAtTime
(
start
,
end
,
currentTime
,
useSpherical
)
interpolatedPoints
.
add
(
interpolatedPoint
)
currentTime
+=
intervalMs
}
// 确保最后一个点是结束点(避免时间误差)
if
(
interpolatedPoints
.
last
().
timestamp
!=
end
.
timestamp
)
{
interpolatedPoints
.
add
(
end
)
}
return
interpolatedPoints
}
/**
* 线性插值经纬度
*
* 适用场景:短距离(<10km),地球曲率影响可忽略
* 计算方式:直接按时间比例线性计算中间点坐标
*
* @param start 起始点
* @param end 结束点
* @param timeRatio 时间比例(0~1)
* @return 插值后的(纬度,经度)对
* @see interpolateSphericalLatLng 球面插值方法(长距离场景)
*/
private
fun
interpolateLinearLatLng
(
start
:
TimedGeoPoint
,
end
:
TimedGeoPoint
,
timeRatio
:
Double
):
Pair
<
Double
,
Double
>
{
val
lat
=
start
.
lat
+
(
end
.
lat
-
start
.
lat
)
*
timeRatio
val
lng
=
start
.
lng
+
(
end
.
lng
-
start
.
lng
)
*
timeRatio
return
Pair
(
lat
,
lng
)
}
/**
* 球面插值(SLERP)
* @param start 起始经纬度点
* @param end 结束经纬度点
* @param t 插值因子(0~1,0=起始点,1=结束点)
* @param radius 球半径(默认地球半径)
* @return 插值后的经纬度点
*/
fun
slerp
(
start
:
TimedGeoPoint
,
end
:
TimedGeoPoint
,
t
:
Double
,
radius
:
Double
=
EARTH_RADIUS
):
Pair
<
Double
,
Double
>
{
// 1. 转换为笛卡尔坐标
val
startCart
=
sphericalToCartesian
(
start
,
radius
)
val
endCart
=
sphericalToCartesian
(
end
,
radius
)
// 2. 计算向量夹角
val
theta
=
vectorAngle
(
startCart
,
endCart
)
// 处理夹角为0的情况(两点重合)
if
(
theta
<
1e-6
)
{
return
Pair
(
start
.
lat
,
start
.
lng
)
}
// 3. SLERP核心计算
val
sinTheta
=
sin
(
theta
)
val
sinTTheta
=
sin
(
t
*
theta
)
val
sin1TTheta
=
sin
((
1
-
t
)
*
theta
)
val
x
=
(
sin1TTheta
/
sinTheta
)
*
startCart
.
x
+
(
sinTTheta
/
sinTheta
)
*
endCart
.
x
val
y
=
(
sin1TTheta
/
sinTheta
)
*
startCart
.
y
+
(
sinTTheta
/
sinTheta
)
*
endCart
.
y
val
z
=
(
sin1TTheta
/
sinTheta
)
*
startCart
.
z
+
(
sinTTheta
/
sinTheta
)
*
endCart
.
z
// 4. 转回球面坐标
return
cartesianToSpherical
(
CartesianPoint
(
x
,
y
,
z
),
radius
)
}
/**
* 计算两个三维向量的夹角(弧度)
* @param a 向量a
* @param b 向量b
* @return 夹角(0 ~ π)
*/
private
fun
vectorAngle
(
a
:
CartesianPoint
,
b
:
CartesianPoint
):
Double
{
// 点积
val
dotProduct
=
a
.
x
*
b
.
x
+
a
.
y
*
b
.
y
+
a
.
z
*
b
.
z
// 向量模长
val
magA
=
sqrt
(
a
.
x
.
pow
(
2
)
+
a
.
y
.
pow
(
2
)
+
a
.
z
.
pow
(
2
))
val
magB
=
sqrt
(
b
.
x
.
pow
(
2
)
+
b
.
y
.
pow
(
2
)
+
b
.
z
.
pow
(
2
))
// 防止浮点误差导致acos参数超出[-1,1]
val
cosTheta
=
max
(
min
(
dotProduct
/
(
magA
*
magB
),
1.0
),
-
1.0
)
return
acos
(
cosTheta
)
}
/**
* 球面坐标(经纬度)转笛卡尔坐标
* @param spherical 经纬度点
* @param radius 球半径(默认地球半径)
* @return 三维笛卡尔坐标
*/
fun
sphericalToCartesian
(
spherical
:
TimedGeoPoint
,
radius
:
Double
=
EARTH_RADIUS
):
CartesianPoint
{
val
lonRad
=
spherical
.
lng
.
toRadians
()
val
latRad
=
spherical
.
lat
.
toRadians
()
val
x
=
radius
*
cos
(
latRad
)
*
cos
(
lonRad
)
val
y
=
radius
*
cos
(
latRad
)
*
sin
(
lonRad
)
val
z
=
radius
*
sin
(
latRad
)
return
CartesianPoint
(
x
,
y
,
z
)
}
/**
* 笛卡尔坐标转球面坐标(经纬度)
* @param cartesian 三维笛卡尔坐标
* @param radius 球半径(默认地球半径)
* @return 经纬度点
*/
fun
cartesianToSpherical
(
cartesian
:
CartesianPoint
,
radius
:
Double
=
EARTH_RADIUS
):
Pair
<
Double
,
Double
>
{
// 计算纬度(弧度):arcsin(z / 半径)
val
latRad
=
asin
(
cartesian
.
z
/
radius
)
// 计算经度(弧度):arctan2(y, x)
val
lonRad
=
atan2
(
cartesian
.
y
,
cartesian
.
x
)
return
Pair
(
latRad
.
toDegrees
(),
lonRad
.
toDegrees
())
}
/**
* 航向角时间插值
*
* 特殊处理:自动处理360°环绕问题
* 例如:350°到10°的插值会按350°→360°/0°→10°的路径计算,而非直接350°→10°
*
* @param startHeading 起始航向角(0-360°)
* @param endHeading 结束航向角(0-360°)
* @param timeRatio 时间比例(0~1)
* @return 插值后的航向角(0-360°)
*/
private
fun
interpolateHeading
(
startHeading
:
Float
,
endHeading
:
Float
,
timeRatio
:
Double
):
Float
{
var
diff
=
endHeading
-
startHeading
// 调整差值到[-180, 180]区间(避免350°→10°时差值为-340°)
if
(
diff
>
180
)
diff
-=
360
else
if
(
diff
<
-
180
)
diff
+=
360
// 按时间比例计算插值
var
interpolated
=
startHeading
+
diff
*
timeRatio
// 修正到0~360°范围
interpolated
%=
360
if
(
interpolated
<
0
)
interpolated
+=
360
return
interpolated
.
toFloat
()
}
/**
* 计算两点间的平均移动速度
*
* @param start 起始点
* @param end 结束点
* @return 平均速度(米/秒)
*
* @throws ArithmeticException 当起止时间相同时(除零错误)
* @sample
* ```kotlin
* val startTime = System.currentTimeMillis()
* val endTime = startTime + 10000 // 10秒
* val startPoint = TimedGeoPoint(39.908823, 116.397470, 90.0f, startTime)
* val endPoint = TimedGeoPoint(39.916404, 116.397096, 100.0f, endTime)
*
* val speed = TimeBasedGeoInterpolator.calculateSpeed(startPoint, endPoint)
* // speed 约为 8.5 米/秒(假设两点距离约85米)
* ```
*/
fun
calculateSpeed
(
start
:
TimedGeoPoint
,
end
:
TimedGeoPoint
):
Double
{
val
distance
=
calculateDistance
(
start
.
lat
,
start
.
lng
,
end
.
lat
,
end
.
lng
)
val
durationSec
=
(
end
.
timestamp
-
start
.
timestamp
)
/
1000.0
return
if
(
durationSec
==
0.0
)
0.0
else
distance
/
durationSec
}
/**
* 使用哈辛公式(Haversine Formula)计算两点间地表距离
*
* 精度:适用于大多数应用场景,误差约为0.5%
* 适用范围:全球范围内的两点距离计算
*
* @param lat1 第一点纬度(度)
* @param lng1 第一点经度(度)
* @param lat2 第二点纬度(度)
* @param lng2 第二点经度(度)
* @return 两点间距离(米)
* @see <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula on Wikipedia</a>
*/
private
fun
calculateDistance
(
lat1
:
Double
,
lng1
:
Double
,
lat2
:
Double
,
lng2
:
Double
):
Double
{
val
dLat
=
Math
.
toRadians
(
lat2
-
lat1
)
val
dLng
=
Math
.
toRadians
(
lng2
-
lng1
)
val
a
=
sin
(
dLat
/
2
)
*
sin
(
dLat
/
2
)
+
cos
(
Math
.
toRadians
(
lat1
))
*
cos
(
Math
.
toRadians
(
lat2
))
*
sin
(
dLng
/
2
)
*
sin
(
dLng
/
2
)
val
c
=
2
*
atan2
(
sqrt
(
a
),
sqrt
(
1
-
a
))
return
EARTH_RADIUS
*
c
}
}
// ====================== 使用示例 ======================
fun
main
()
{
/* // 示例1:定义起止点(带时间戳)
val startTime = System.currentTimeMillis() // 起始时间
val endTime = startTime + 5000 // 结束时间(5秒后)
val startPoint = TimedGeoPoint(
lat = 39.908823, // 北京天安门
lng = 116.397470,
heading = 90.0f, // 航向角90°(正东)
timestamp = startTime
)
val endPoint = TimedGeoPoint(
lat = 39.916404, // 北京故宫
lng = 116.397096,
heading = 100.0f, // 航向角100°
timestamp = endTime
)
// 示例2:插值计算「2秒后」的坐标+航向角
val targetTime = startTime + 2000 // 目标时间:2秒后
val interpolatedAt2s = TimeBasedGeoInterpolator.interpolateAtTime(
start = startPoint,
end = endPoint,
targetTime = targetTime,
useSpherical = false // 短距离用线性插值
)
println("2秒后插值结果:")
println("纬度:${interpolatedAt2s.lat.format(6)}")
println("经度:${interpolatedAt2s.lng.format(6)}")
println("航向角:${interpolatedAt2s.heading}°")
println("时间戳:${interpolatedAt2s.timestamp}")
// 示例3:按1秒间隔生成5秒内的所有插值点
val intervalPoints = TimeBasedGeoInterpolator.interpolateByTimeInterval(
start = startPoint,
end = endPoint,
intervalMs = 1000, // 每秒一个点
useSpherical = false
)
println("\n按1秒间隔插值结果:")
intervalPoints.forEachIndexed { index, point ->
val timeElapsed = (point.timestamp - startTime) / 1000.0
println("第${timeElapsed}秒:纬度=${point.lat.format(6)}, 经度=${point.lng.format(6)}, 航向=${point.heading}°")
}
// 示例4:计算移动速度
val speed = TimeBasedGeoInterpolator.calculateSpeed(startPoint, endPoint)
println("\n移动速度:${speed.format(2)} 米/秒")*/
}
// 扩展函数:Double保留指定位数小数
fun
Double
.
format
(
digits
:
Int
):
String
=
"%.${digits}f"
.
format
(
this
)
\ No newline at end of file
src/main/java/commons/stand/AssetsFileUtils.kt
0 → 100644
View file @
745a80a3
package
commons.stand
import
android.content.Context
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.withContext
import
java.io.BufferedReader
import
java.io.IOException
import
java.io.InputStream
import
java.io.InputStreamReader
/**Assets 文件夹工具类***/
object
AssetsFileUtils
{
/**
* 逐行读取asset 文件内容,并加入列表中
*/
suspend
fun
getAssetMock
(
context
:
Context
,
fileName
:
String
,
dst
:
MutableList
<
String
>):
Int
{
return
withContext
(
Dispatchers
.
IO
)
{
val
assetManager
=
context
.
assets
var
bf
:
BufferedReader
?
=
null
try
{
val
inputReader
=
InputStreamReader
(
assetManager
.
open
(
fileName
))
bf
=
BufferedReader
(
inputReader
)
var
line
=
""
while
(!
bf
.
run
{
line
=
readLine
()
return
@run
line
}.
isNullOrEmpty
())
{
dst
.
add
(
line
)
}
bf
.
close
()
return
@withContext
1
}
catch
(
e
:
IOException
)
{
e
.
printStackTrace
()
}
finally
{
bf
?.
close
()
}
return
@withContext
0
}
}
/**
* 一次性读取asset 文件内容
*/
suspend
fun
getAsset
(
context
:
Context
,
fileName
:
String
):
String
{
return
withContext
(
Dispatchers
.
IO
)
{
val
assetManager
=
context
.
assets
var
inputStream
:
InputStream
?
=
null
var
str
=
""
try
{
inputStream
=
assetManager
.
open
(
fileName
)
val
size
=
inputStream
.
available
()
val
bytes
=
ByteArray
(
size
)
inputStream
.
read
(
bytes
)
str
=
String
(
bytes
)
}
catch
(
e
:
IOException
)
{
e
.
printStackTrace
()
}
finally
{
inputStream
?.
close
()
}
return
@withContext
str
}
}
}
\ No newline at end of file
src/main/java/commons/stand/DisplayUtil.kt
0 → 100644
View file @
745a80a3
package
commons.stand
import
android.app.Activity
import
android.content.Context
import
android.content.res.Configuration
import
android.content.res.Resources
import
android.os.Build
import
android.provider.Settings
import
android.util.DisplayMetrics
import
android.util.TypedValue
import
android.view.View
import
android.view.View.MeasureSpec
/**
*author:pc-20171125
*data:2019/11/7 16:08
*/
object
DisplayUtil
{
fun
getDpi
():
Int
{
return
Resources
.
getSystem
().
displayMetrics
.
densityDpi
}
fun
px2dp
(
pxValue
:
Float
):
Int
{
val
scale
=
Resources
.
getSystem
().
displayMetrics
.
density
return
(
pxValue
/
scale
+
0.5f
).
toInt
()
}
fun
dp2px
(
dipValue
:
Float
):
Int
{
val
scale
=
Resources
.
getSystem
().
displayMetrics
.
density
return
(
dipValue
*
scale
+
0.5f
).
toInt
()
}
fun
px2sp
(
pxValue
:
Float
):
Int
{
val
fontScale
=
Resources
.
getSystem
().
displayMetrics
.
scaledDensity
return
(
pxValue
/
fontScale
+
0.5f
).
toInt
()
}
fun
sp2px
(
spValue
:
Float
):
Int
{
val
fontScale
=
Resources
.
getSystem
().
displayMetrics
.
scaledDensity
return
(
spValue
*
fontScale
+
0.5f
).
toInt
()
}
fun
getScreenWidthDp
():
Int
{
val
displayMetrics
=
Resources
.
getSystem
().
displayMetrics
val
widthPixels
=
displayMetrics
.
widthPixels
val
density
=
displayMetrics
.
density
return
Math
.
round
(
widthPixels
/
density
)
}
fun
getScreenWidthPx
():
Int
{
val
displayMetrics
=
Resources
.
getSystem
().
displayMetrics
return
displayMetrics
.
widthPixels
}
fun
getScreenHeightDp
():
Int
{
val
displayMetrics
=
Resources
.
getSystem
().
displayMetrics
val
heightPixels
=
displayMetrics
.
heightPixels
val
density
=
displayMetrics
.
density
return
Math
.
round
(
heightPixels
/
density
)
}
fun
getScreenHeightPx
():
Int
{
val
displayMetrics
=
Resources
.
getSystem
().
displayMetrics
return
displayMetrics
.
heightPixels
}
fun
forceMeasure
(
view
:
View
)
{
val
widthMeasureSpec
=
MeasureSpec
.
makeMeasureSpec
(
0
,
MeasureSpec
.
UNSPECIFIED
)
val
heightMeasureSpec
=
MeasureSpec
.
makeMeasureSpec
(
0
,
MeasureSpec
.
UNSPECIFIED
)
view
.
measure
(
widthMeasureSpec
,
heightMeasureSpec
)
}
private
val
mTmpValue
=
TypedValue
()
fun
getXmlDef
(
context
:
Context
,
id
:
Int
):
Int
{
synchronized
(
mTmpValue
)
{
val
value
:
TypedValue
=
mTmpValue
context
.
resources
.
getValue
(
id
,
value
,
true
)
return
TypedValue
.
complexToFloat
(
value
.
data
).
toInt
()
}
}
fun
getNavigationBarHeight
(
context
:
Context
):
Int
{
val
mInPortrait
=
context
.
resources
.
configuration
.
orientation
==
Configuration
.
ORIENTATION_PORTRAIT
val
result
=
0
if
(
hasNavBar
(
context
as
Activity
))
{
val
key
:
String
if
(
mInPortrait
)
{
key
=
"navigation_bar_height"
}
else
{
key
=
"navigation_bar_height_landscape"
}
return
getInternalDimensionSize
(
context
,
key
)
}
return
result
}
private
fun
hasNavBar
(
activity
:
Activity
):
Boolean
{
//判断小米手机是否开启了全面屏,开启了,直接返回false
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
JELLY_BEAN_MR1
)
{
if
(
Settings
.
Global
.
getInt
(
activity
.
contentResolver
,
"force_fsg_nav_bar"
,
0
)
!=
0
)
{
return
false
}
}
//其他手机根据屏幕真实高度与显示高度是否相同来判断
val
windowManager
=
activity
.
windowManager
val
d
=
windowManager
.
defaultDisplay
val
realDisplayMetrics
=
DisplayMetrics
()
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
JELLY_BEAN_MR1
)
{
d
.
getRealMetrics
(
realDisplayMetrics
)
}
val
realHeight
=
realDisplayMetrics
.
heightPixels
val
realWidth
=
realDisplayMetrics
.
widthPixels
val
displayMetrics
=
DisplayMetrics
()
d
.
getMetrics
(
displayMetrics
)
val
displayHeight
=
displayMetrics
.
heightPixels
val
displayWidth
=
displayMetrics
.
widthPixels
return
realWidth
-
displayWidth
>
0
||
realHeight
-
displayHeight
>
0
}
private
fun
getInternalDimensionSize
(
context
:
Context
,
key
:
String
):
Int
{
var
result
=
0
try
{
val
resourceId
=
context
.
resources
.
getIdentifier
(
key
,
"dimen"
,
"android"
)
if
(
resourceId
>
0
)
{
result
=
Math
.
round
(
context
.
resources
.
getDimensionPixelSize
(
resourceId
)
*
Resources
.
getSystem
().
displayMetrics
.
density
/
context
.
resources
.
displayMetrics
.
density
)
}
}
catch
(
ignored
:
Resources
.
NotFoundException
)
{
return
0
}
return
result
}
}
\ No newline at end of file
src/main/java/commons/stand/FileSdCardUtils.kt
0 → 100644
View file @
745a80a3
package
commons.stand
import
android.os.Environment
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.withContext
import
java.io.File
import
java.io.IOException
object
FileSdCardUtils
{
/**
* 写文件到Download目录,需要配置FileProvider和申请权限
* 追加模式
*/
suspend
fun
writeFileToDownload
(
message
:
String
,
fileName
:
String
)
{
withContext
(
Dispatchers
.
IO
)
{
try
{
val
file
=
File
(
Environment
.
getExternalStoragePublicDirectory
(
Environment
.
DIRECTORY_DOWNLOADS
),
fileName
)
// 核心:appendText 默认为追加模式,指定编码更安全
file
.
appendText
(
"$message\n"
,
Charsets
.
UTF_8
)
}
catch
(
e
:
IOException
)
{
e
.
printStackTrace
()
}
}
/**
* 写文件到Download目录,需要配置FileProvider和申请权限
* 追加模式
*/
suspend
fun
writeFileToDownload
(
message
:
String
,
file
:
File
)
{
withContext
(
Dispatchers
.
IO
)
{
try
{
// 核心:appendText 默认为追加模式,指定编码更安全
file
.
appendText
(
"$message\n"
,
Charsets
.
UTF_8
)
}
catch
(
e
:
IOException
)
{
e
.
printStackTrace
()
}
}
}
}
}
\ No newline at end of file
src/main/java/commons/stand/StringUtils.kt
0 → 100644
View file @
745a80a3
package
commons.stand
object
StringUtils
{
//极简方案(无边界处理,适合已知合法输入)
fun
simpleHexToByteArray
(
hexStr
:
String
):
ByteArray
{
require
(
hexStr
.
length
%
2
==
0
)
{
"16进制字符串长度必须为偶数"
}
return
hexStr
.
chunked
(
2
)
.
map
{
it
.
toInt
(
16
).
toByte
()
}
.
toByteArray
()
}
//兼容所有输入格式,无崩溃风险
fun
hexStringToByteArray
(
hexStr
:
String
):
ByteArray
{
// 1. 预处理:去除前缀(0x/0X)、空格,转为大写
val
cleanedHex
=
hexStr
.
trim
()
.
replaceFirst
(
"^0x|^0X"
.
toRegex
(),
""
)
// 移除前缀
.
replace
(
"\\s+"
.
toRegex
(),
""
)
// 移除所有空格(如 "A1 B2" → "A1B2")
.
uppercase
()
// 2. 处理空字符串
if
(
cleanedHex
.
isEmpty
())
return
ByteArray
(
0
)
// 3. 处理奇数长度:末尾补0(如 "A1B" → "A1B0")
val
length
=
cleanedHex
.
length
val
paddedHex
=
if
(
length
%
2
!=
0
)
cleanedHex
+
"0"
else
cleanedHex
// 4. 批量转换:每2个字符为一组,转成1个字节
return
paddedHex
.
chunked
(
2
)
// 按2个字符分割(如 "A1B0" → ["A1", "B0"])
.
map
{
chunk
->
// 16进制字符串转字节(radix=16),toByte() 会自动处理 0-255 范围
chunk
.
toInt
(
radix
=
16
).
toByte
()
}
.
toByteArray
()
}
// 性能优化(大数据量场景)
fun
largeHexToByteArray
(
hexStr
:
String
):
ByteArray
{
val
cleanedHex
=
hexStr
.
trim
().
replaceFirst
(
"^0x|^0X"
.
toRegex
(),
""
).
uppercase
()
if
(
cleanedHex
.
isEmpty
())
return
ByteArray
(
0
)
val
length
=
cleanedHex
.
length
val
resultLength
=
(
length
+
1
)
/
2
// 向上取整(奇数长度也适配)
val
byteArray
=
ByteArray
(
resultLength
)
for
(
i
in
0
until
resultLength
)
{
val
high
=
cleanedHex
[
i
*
2
].
digitToInt
(
16
)
// 高位字符(第1个)
val
low
=
if
(
i
*
2
+
1
<
length
)
cleanedHex
[
i
*
2
+
1
].
digitToInt
(
16
)
else
0
// 低位字符(第2个,无则补0)
byteArray
[
i
]
=
(
high
*
16
+
low
).
toByte
()
}
return
byteArray
}
/**
* ByteArray 转 16进制字符串
* @param withPrefix 是否带 0x 前缀(默认 false)
* @param withSpace 是否用空格分隔字节(默认 true,如 "A1 B2")
* @param uppercase 是否大写(默认 true)
*/
fun
byteToHexString
(
byteArray
:
ByteArray
?,
withPrefix
:
Boolean
=
false
,
withSpace
:
Boolean
=
false
,
uppercase
:
Boolean
=
true
):
String
{
if
(
byteArray
==
null
)
return
""
val
hexChars
=
byteArray
.
joinToString
(
separator
=
if
(
withSpace
)
" "
else
""
)
{
val
hex
=
"%02x"
.
format
(
it
)
// 每个字节转 2 位 16进制(不足补 0)
if
(
uppercase
)
hex
.
uppercase
()
else
hex
}
return
if
(
withPrefix
)
"0x$hexChars"
else
hexChars
/* val sb = StringBuilder()
for (b in bytes) {
sb.append(String.format("%02x", b))
}
return sb.toString()*/
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment