Merge branch 'master' into 'master'

Master

See merge request qiji4215/OneMapQS!3
This commit is contained in:
齐济04215 2023-04-19 17:24:32 +08:00
commit 50462651d8
18 changed files with 360 additions and 23 deletions

View File

@ -75,6 +75,9 @@ dependencies {
implementation 'org.apache.poi:poi:5.2.3'
implementation 'org.apache.poi:poi-ooxml:5.2.3'
// spatialite文件
implementation 'com.github.sevar83:android-spatialite:2.0.1'
}
//
kapt {

View File

@ -7,9 +7,10 @@ import com.navinfo.omqs.tools.FileManager
import dagger.hilt.android.HiltAndroidApp
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
@HiltAndroidApp
class OMQSApplication : Application() {
@ -17,16 +18,26 @@ class OMQSApplication : Application() {
super.onCreate()
FileManager.initRootDir(this)
Realm.init(this)
val password = "password".encodeToByteArray().copyInto(ByteArray(64))
// 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// Log.d("", "密码是: ${BigInteger(1, password).toString(2).padStart(64, '0')}")
val password = "encryp".encodeToByteArray().copyInto(ByteArray(64))
// 70617373776f72640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Log.d("OMQSApplication", "密码是: ${byteArrayToHexString(password)}")
val config = RealmConfiguration.Builder()
.directory(File(Constant.DATA_PATH))
.name("HDData")
.name("OMQS.realm")
.encryptionKey(password)
.modules(Realm.getDefaultModule(), MyRealmModule())
.schemaVersion(1)
// .encryptionKey(password)
.build()
Realm.setDefaultConfiguration(config)
}
private fun getKey(inputString: String): String {
val messageDigest = MessageDigest.getInstance("SHA-256")
val hashBytes = messageDigest.digest(inputString.toByteArray())
return hashBytes.joinToString("") { "%02x".format(it) };
}
fun byteArrayToHexString(byteArray: ByteArray): String {
return byteArray.joinToString("") { "%02x".format(it) }
}
}

View File

@ -0,0 +1,5 @@
package com.navinfo.omqs.bean
class ImportConfig {
var tables: MutableList<String> = mutableListOf()
}

View File

@ -0,0 +1,118 @@
package com.navinfo.omqs.db
import android.content.Context
import android.database.Cursor.*
import androidx.core.database.getBlobOrNull
import androidx.core.database.getFloatOrNull
import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull
import com.google.gson.Gson
import com.navinfo.omqs.bean.ImportConfig
import com.navinfo.omqs.hilt.ImportOMDBHiltFactory
import com.navinfo.omqs.hilt.OMDBDataBaseHiltFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import org.spatialite.database.SQLiteDatabase
import java.io.File
import javax.inject.Inject
import kotlin.streams.toList
/**
* 导入omdb数据的帮助类
* */
class ImportOMDBHelper @AssistedInject constructor(@Assisted("context") val context: Context,@Assisted("omdbFile") val omdbFile: File,@Assisted("configFile") val configFile: File) {
@Inject
lateinit var omdbHiltFactory: OMDBDataBaseHiltFactory
@Inject
lateinit var gson: Gson
private val database by lazy { omdbHiltFactory.obtainOmdbDataBaseHelper(context, omdbFile.absolutePath, 1).writableDatabase }
/**
* 读取config的配置文件
* */
fun openConfigFile(): ImportConfig {
val configStr = configFile.readText()
return gson.fromJson(configStr, ImportConfig::class.java)
}
/**
* 读取指定数据表的数据集
* */
suspend fun getOMDBTableData(table: String): Flow<List<Map<String, Any>>> =
withContext(Dispatchers.IO) {
val listResult: MutableList<Map<String, Any>> = mutableListOf()
flow<List<Map<String, Any>>> {
if (database.isOpen) {
val comumns = mutableListOf<String>()
// 获取要读取的列名
val columns = getColumns(database, table)
// 处理列名如果列名是GEOMETRY则使用spatialite函数ST_AsText读取blob数据
val finalColumns = columns.stream().map {
val column = it.replace("\"", "", true)
if ("GEOMETRY".equals(column, ignoreCase = true)) {
"ST_AsText($column)"
} else {
column
}
}.toList()
val cursor = database.query(table, finalColumns.toTypedArray(), "1=1",
mutableListOf<String>().toTypedArray(), null, null, null, null)
with(cursor) {
if (moveToFirst()) {
while (moveToNext()) {
val rowMap = mutableMapOf<String, Any>()
for (columnIndex in 0 until columnCount) {
var columnName = getColumnName(columnIndex)
if (columnName.startsWith("ST_AsText(")) {
columnName = columnName.replace("ST_AsText(", "").substringBeforeLast(")")
}
when(getType(columnIndex)) {
FIELD_TYPE_NULL -> rowMap[columnName] = ""
FIELD_TYPE_INTEGER -> rowMap[columnName] = getInt(columnIndex)
FIELD_TYPE_FLOAT -> rowMap[columnName] = getFloat(columnIndex)
FIELD_TYPE_BLOB -> rowMap[columnName] = String(getBlob(columnIndex), Charsets.UTF_8)
else -> rowMap[columnName] = getString(columnIndex)
}
}
listResult.add(rowMap)
}
}
}
emit(listResult)
cursor.close()
}
}
}
// 获取指定数据表的列名
fun getColumns(db: SQLiteDatabase, tableName: String): List<String> {
val columns = mutableListOf<String>()
// 查询 sqlite_master 表获取指定数据表的元数据信息
val cursor = db.query("sqlite_master", arrayOf("sql"), "type='table' AND name=?", arrayOf(tableName), null, null, null)
// 从元数据信息中解析出列名
if (cursor.moveToFirst()) {
val sql = cursor.getString(0)
val startIndex = sql.indexOf("(") + 1
val endIndex = sql.lastIndexOf(")")
val columnDefs = sql.substring(startIndex, endIndex).split(",")
for (columnDef in columnDefs) {
val columnName = columnDef.trim().split(" ")[0]
if (!columnName.startsWith("rowid", true)) { // 排除 rowid 列
columns.add(columnName)
}
}
}
cursor.close()
return columns
}
}

View File

@ -0,0 +1,20 @@
package com.navinfo.omqs.db
import android.content.Context
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.spatialite.database.SQLiteDatabase
import org.spatialite.database.SQLiteOpenHelper
class OmdbDataBaseHelper @AssistedInject constructor(@Assisted("context")context: Context, @Assisted("dbName") dbName: String, @Assisted("dbVersion") dbVersion: Int) :
SQLiteOpenHelper(context, dbName, null, dbVersion) {
override fun onCreate(db: SQLiteDatabase?) {
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
override fun onOpen(db: SQLiteDatabase?) {
super.onOpen(db)
}
}

View File

@ -0,0 +1,12 @@
package com.navinfo.omqs.hilt
import android.content.Context
import com.navinfo.omqs.db.ImportOMDBHelper
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import java.io.File
@AssistedFactory
interface ImportOMDBHiltFactory {
fun obtainImportOMDBHelper(@Assisted("context")context: Context, @Assisted("omdbFile") omdbFile: File, @Assisted("configFile") dbVersion: File): ImportOMDBHelper
}

View File

@ -0,0 +1,11 @@
package com.navinfo.omqs.hilt
import android.content.Context
import com.navinfo.omqs.db.OmdbDataBaseHelper
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@AssistedFactory
interface OMDBDataBaseHiltFactory {
fun obtainOmdbDataBaseHelper(@Assisted("context")context: Context, @Assisted("dbName") dbName: String, @Assisted("dbVersion") dbVersion: Int): OmdbDataBaseHelper
}

View File

@ -0,0 +1,29 @@
package com.navinfo.omqs.tools
import android.app.ProgressDialog
import android.content.Context
import com.google.android.material.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
object CoroutineUtils {
fun <T> launchWithLoading(
context: Context,
coroutineContext: CoroutineContext = Dispatchers.Main,
loadingMessage: String? = null,
task: suspend CoroutineScope.() -> T
): Job {
val progressDialog = MaterialAlertDialogBuilder(
context, R.style.MaterialAlertDialog_Material3).setMessage(loadingMessage).setCancelable(false).show()
return CoroutineScope(coroutineContext).launch {
try {
withContext(Dispatchers.IO) {
task.invoke(this)
}
} finally {
progressDialog.dismiss()
}
}
}
}

View File

@ -1,16 +1,38 @@
package com.navinfo.omqs.ui.activity
import android.app.Dialog
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
/**
* 基类
*/
open class BaseActivity : AppCompatActivity() {
private var loadingDialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE//横屏
super.onCreate(savedInstanceState)
}
/**
* 显示loading dialog
*/
fun showLoadingDialog(message: String) {
loadingDialog?.dismiss()
loadingDialog = MaterialAlertDialogBuilder(
this@BaseActivity, R.style.MaterialAlertDialog_Material3).setMessage(message).setCancelable(false).show()
}
/**
* 隐藏loading dialog
* */
fun hideLoadingDialog() {
loadingDialog?.dismiss()
loadingDialog = null
}
}

View File

@ -3,19 +3,36 @@ package com.navinfo.omqs.ui.fragment.personalcenter
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.blankj.utilcode.util.UriUtils
import com.github.k1rakishou.fsaf.FileChooser
import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
import com.github.k1rakishou.fsaf.callback.FileChooserCallback
import com.navinfo.collect.library.data.RealmUtils
import com.navinfo.collect.library.data.entity.OMDBEntity
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
import com.navinfo.omqs.db.ImportOMDBHelper
import com.navinfo.omqs.hilt.ImportOMDBHiltFactory
import com.navinfo.omqs.tools.CoroutineUtils
import com.navinfo.omqs.ui.activity.BaseActivity
import dagger.hilt.android.AndroidEntryPoint
import io.realm.Realm
import io.realm.RealmDictionary
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.UUID
import javax.inject.Inject
/**
* 个人中心
@ -27,6 +44,8 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
private val binding get() = _binding!!
private val fileChooser by lazy { FileChooser(requireContext()) }
private val viewModel by lazy { viewModels<PersonalCenterViewModel>().value }
@Inject
lateinit var importOMDBHiltFactory: ImportOMDBHiltFactory
override fun onCreateView(
@ -52,7 +71,11 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
override fun onResult(uri: Uri) {
val file = UriUtils.uri2File(uri)
// 开始导入数据
viewModel.importOmdbData(file)
// 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
val job = CoroutineUtils.launchWithLoading(requireContext(), loadingMessage = "导入数据...") {
val importOMDBHelper: ImportOMDBHelper = importOMDBHiltFactory.obtainImportOMDBHelper(requireContext(), file, File(file.parentFile, "config.json"))
viewModel.importOMDBData(importOMDBHelper)
}
}
})
}

View File

@ -1,38 +1,60 @@
package com.navinfo.omqs.ui.fragment.personalcenter
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.UriUtils
import com.navinfo.collect.library.data.entity.OMDBEntity
import com.navinfo.omqs.bean.ScProblemTypeBean
import com.navinfo.omqs.bean.ScRootCauseAnalysisBean
import com.navinfo.omqs.db.ImportOMDBHelper
import com.navinfo.omqs.db.RoomAppDatabase
import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.Realm
import io.realm.RealmDictionary
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.apache.poi.ss.usermodel.Cell
import org.apache.poi.ss.usermodel.Row
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.WorkbookFactory
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.util.*
import javax.inject.Inject
@HiltViewModel
class PersonalCenterViewModel @Inject constructor(
private val roomAppDatabase: RoomAppDatabase
) : ViewModel() {
fun importOmdbData(omdbFile: File) {
// 检查File是否为sqlite数据库
if (omdbFile == null || !omdbFile.exists()) {
throw Exception("文件不存在")
}
if (!omdbFile.name.endsWith(".sqlite") and !omdbFile.name.endsWith("db")) {
throw Exception("文件不存在")
/**
* 导入OMDB数据
* */
suspend fun importOMDBData(importOMDBHelper: ImportOMDBHelper) {
Log.d("OMQSApplication", "开始导入数据")
// Realm.getDefaultInstance().beginTransaction()
for (table in importOMDBHelper.openConfigFile().tables/*listOf<String>("HAD_LINK")*/) {
importOMDBHelper.getOMDBTableData(table).collect {
for (map in it) {
val properties = RealmDictionary<String?>()
for (entry in map.entries) {
properties.putIfAbsent(entry.key, entry.value.toString())
}
// 将读取到的sqlite数据插入到Realm中
Realm.getDefaultInstance().insert(OMDBEntity(table, properties))
// 将读取到的数据写入到json中
}
}
}
// Realm.getDefaultInstance().commitTransaction()
// 数据导入结束后开始生成渲染表所需的json文件并生成压缩包
Log.d("OMQSApplication", "导入数据完成")
}
fun importScProblemData(uri: Uri) {

View File

@ -18,6 +18,10 @@
android:id="@+id/personal_center_menu_import_yuan_data"
android:icon="@drawable/ic_baseline_import_export_24"
android:title="导入元数据" />
<item
android:id="@+id/personal_center_menu_realm_data_backup"
android:icon="@drawable/ic_baseline_import_export_24"
android:title="备份数据" />
</group>
<group
android:id="@+id/group2"

View File

@ -0,0 +1,17 @@
package com.navinfo.collect.library.data.entity
import io.realm.RealmDictionary
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class OMDBEntity(): RealmObject() {
@PrimaryKey
var id: Long = 0
lateinit var table: String
lateinit var properties: RealmDictionary<String?>
constructor(table: String, properties: RealmDictionary<String?>): this() {
this.table = table
this.properties = properties
}
}

View File

@ -0,0 +1,38 @@
package com.navinfo.collect.library.data.entity
import com.navinfo.collect.library.system.Constant
import com.navinfo.collect.library.utils.GeometryTools
import com.navinfo.collect.library.utils.GeometryToolsKt
import io.realm.RealmDictionary
import io.realm.RealmObject
import io.realm.RealmSet
import io.realm.annotations.PrimaryKey
import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.Geometry
import org.oscim.core.MercatorProjection
/**
* 渲染要素对应的实体
* */
open class RenderEntity(): RealmObject() {
@PrimaryKey
var id: Long = 0 // id
lateinit var name: String //要素名
var code: Int = 0 // 要素编码
var geometry: String = ""
get() = field
set(value) {
field = value
// 根据geometry自动计算当前要素的x-tile和y-tile
GeometryToolsKt.getTileXByGeometry(value, tileX)
GeometryToolsKt.getTileYByGeometry(value, tileY)
}
lateinit var properties: RealmDictionary<String?>
val tileX: RealmSet<Int> = RealmSet() // x方向的tile编码
val tileY: RealmSet<Int> = RealmSet() // y方向的tile编码
constructor(name: String, properties: RealmDictionary<String?>): this() {
this.name = name
this.properties = properties
}
}

View File

@ -36,6 +36,7 @@ class NIMapController {
measureLayerHandler = MeasureLayerHandler(context, mapView)
mMapView = mapView
mapView.setOptions(options)
mMapView.vtmMap.viewport().maxZoomLevel = Constant.MAX_ZOOM // 设置地图的最大级别
}

View File

@ -29,6 +29,7 @@ public class Constant {
HAD_LAYER_INVISIABLE_ARRAY = HD_LAYER_VISIABLE_MAP.keySet().toArray(new String[HD_LAYER_VISIABLE_MAP.keySet().size()]);
}
public static String[] HAD_LAYER_INVISIABLE_ARRAY;
public static final int OVER_ZOOM = 23;
public static final int OVER_ZOOM = 21;
public static final int MAX_ZOOM = 25;
}

View File

@ -173,7 +173,6 @@ public class GeometryTools {
} catch (Exception e) {
}
return createMultiPoint;
}

View File

@ -1,5 +1,6 @@
package com.navinfo.collect.library.utils
import com.navinfo.collect.library.system.Constant
import io.realm.RealmSet
import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.io.WKTReader
@ -10,7 +11,7 @@ class GeometryToolsKt {
/**
* 根据给定的geometry计算其横跨的20级瓦片Y值
*/
fun getTileYByGeometry(wkt: String, tileYSet: MutableSet<Int?>){
fun getTileYByGeometry(wkt: String, tileYSet: MutableSet<Int?>) {
val reader = WKTReader()
val geometry = reader.read(wkt);
@ -37,8 +38,8 @@ class GeometryToolsKt {
}
}
// 分别计算最大和最小x值对应的tile号
val tileY0 = MercatorProjection.latitudeToTileY(minMaxY[0], 20.toByte())
val tileY1 = MercatorProjection.latitudeToTileY(minMaxY[1], 20.toByte())
val tileY0 = MercatorProjection.latitudeToTileY(minMaxY[0], Constant.OVER_ZOOM.toByte())
val tileY1 = MercatorProjection.latitudeToTileY(minMaxY[1], Constant.OVER_ZOOM.toByte())
val minTileY = if (tileY0 <= tileY1) tileY0 else tileY1
val maxTileY = if (tileY0 <= tileY1) tileY1 else tileY0
println("getTileYByGeometry$envelope===$minTileY===$maxTileY")
@ -81,8 +82,8 @@ class GeometryToolsKt {
}
}
// 分别计算最大和最小x值对应的tile号
val tileX0 = MercatorProjection.longitudeToTileX(minMaxX[0], 20.toByte())
val tileX1 = MercatorProjection.longitudeToTileX(minMaxX[1], 20.toByte())
val tileX0 = MercatorProjection.longitudeToTileX(minMaxX[0], Constant.OVER_ZOOM.toByte())
val tileX1 = MercatorProjection.longitudeToTileX(minMaxX[1], Constant.OVER_ZOOM.toByte())
val minTileX = if (tileX0 <= tileX1) tileX0 else tileX1
val maxTileX = if (tileX0 <= tileX1) tileX1 else tileX0
println("getTileXByGeometry$envelope$minTileX===$maxTileX")