diff --git a/app/src/main/assets/omdb.config b/app/src/main/assets/omdb.config
new file mode 100644
index 00000000..a6737381
--- /dev/null
+++ b/app/src/main/assets/omdb.config
@@ -0,0 +1,35 @@
+{
+	"tables" : [
+		{
+			"table": "OMDB_RD_LINK",
+			"code": 2001,
+			"name": "道路线"
+		},
+		{
+			"table": "OMDB_RD_LINK_KIND",
+			"code": 2008,
+			"name": "道路种别"
+		},
+		{
+			"table": "OMDB_RD_LINK_DIRECT",
+			"code": 2001,
+			"name": "道路方向"
+		},
+		{
+			"table": "OMDB_SPEEDLIMIT_COND",
+			"code": 2001,
+			"name": "条件点限速"
+		},
+		{
+			"table": "OMDB_SPEEDLIMIT_VAR",
+			"code": 2001,
+			"name": "可变点限速"
+		},
+		{
+			"table": "OMDB_SPEEDLIMIT",
+			"code": 2001,
+			"name": "常规点限速"
+		}
+		
+	]
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt
index 4bf4f215..fe075a01 100644
--- a/app/src/main/java/com/navinfo/omqs/Constant.kt
+++ b/app/src/main/java/com/navinfo/omqs/Constant.kt
@@ -19,6 +19,11 @@ class Constant {
          */
         lateinit var DATA_PATH: String
 
+        /**
+         * 当前用户ID
+         */
+        lateinit var CURRENT_USER_ID: String
+
         /**
          * 离线地图目录
          */
@@ -41,7 +46,7 @@ class Constant {
         const val message_version_right_off = "1" //立即发送
 
         const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
-        lateinit var realm: Realm
+        const val OMDB_CONFIG = "omdb.config"
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
index 1531a3d9..8937ad93 100644
--- a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
+++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
@@ -22,18 +22,6 @@ class OMQSApplication : Application() {
         NetUtils.getInstance().init(this)
         TakePhotoManager.getInstance().init(this, 1)
         FileManager.initRootDir(this)
-        Realm.init(this)
-        val password = "encryp".encodeToByteArray().copyInto(ByteArray(64))
-        // 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-        Log.d("OMQSApplication", "密码是: ${byteArrayToHexString(password)}")
-        val config = RealmConfiguration.Builder()
-            .directory(File(Constant.DATA_PATH))
-            .name("OMQS.realm")
-            .encryptionKey(password)
-            .modules(Realm.getDefaultModule(), MyRealmModule())
-            .schemaVersion(1)
-            .build()
-        Realm.setDefaultConfiguration(config)
     }
 
     private fun getKey(inputString: String): String {
@@ -42,7 +30,4 @@ class OMQSApplication : Application() {
         return hashBytes.joinToString("") { "%02x".format(it) };
     }
 
-    fun byteArrayToHexString(byteArray: ByteArray): String {
-        return byteArray.joinToString("") { "%02x".format(it) }
-    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/db/ImportOMDBHelper.kt b/app/src/main/java/com/navinfo/omqs/db/ImportOMDBHelper.kt
index adef0eaa..666679c0 100644
--- a/app/src/main/java/com/navinfo/omqs/db/ImportOMDBHelper.kt
+++ b/app/src/main/java/com/navinfo/omqs/db/ImportOMDBHelper.kt
@@ -11,11 +11,13 @@ import com.blankj.utilcode.util.ZipUtils
 import com.google.gson.Gson
 import com.google.gson.reflect.TypeToken
 import com.navinfo.collect.library.data.entity.RenderEntity
+import com.navinfo.omqs.Constant
 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 io.realm.Realm
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flow
@@ -28,13 +30,13 @@ import kotlin.streams.toList
 /**
  * 导入omdb数据的帮助类
  * */
-class ImportOMDBHelper @AssistedInject constructor(@Assisted("context") val context: Context,@Assisted("omdbFile") val omdbFile: File,@Assisted("configFile") val configFile: File) {
+class ImportOMDBHelper @AssistedInject constructor(@Assisted("context") val context: Context,@Assisted("omdbFile") val omdbFile: File) {
     @Inject
     lateinit var omdbHiltFactory: OMDBDataBaseHiltFactory
     @Inject
     lateinit var gson: Gson
     private val database by lazy { omdbHiltFactory.obtainOmdbDataBaseHelper(context, omdbFile.absolutePath, 1).writableDatabase }
-
+    private val configFile: File = File("${Constant.DATA_PATH}/${Constant.CURRENT_USER_ID}", Constant.OMDB_CONFIG)
 
     /**
      * 读取config的配置文件
@@ -99,10 +101,10 @@ class ImportOMDBHelper @AssistedInject constructor(@Assisted("context") val cont
      * @param omdbZipFile omdb数据抽取生成的Zip文件
      * @param configFile 对应的配置文件
      * */
-    suspend fun importOmdbZipFile(omdbZipFile: File): Flow<List<Map<String, Any>>> = withContext(Dispatchers.IO) {
+    suspend fun importOmdbZipFile(omdbZipFile: File): Flow<String> = withContext(Dispatchers.IO) {
         val importConfig = openConfigFile()
         val unZipFolder = File(omdbZipFile.parentFile, "result")
-        flow<List<Map<String, Any>>> {
+        flow<String> {
             if (unZipFolder.exists()) {
                 unZipFolder.deleteRecursively()
             }
@@ -110,26 +112,45 @@ class ImportOMDBHelper @AssistedInject constructor(@Assisted("context") val cont
             // 开始解压zip文件
             val unZipFiles = ZipUtils.unzipFile(omdbZipFile, unZipFolder)
             // 遍历解压后的文件,读取该数据返回
-            for (txtFile in unZipFiles) {
-                val listResult: MutableList<Map<String, Any>> = mutableListOf()
-                // 根据文件名称获取对应的配置
-                val currentConfig=importConfig.tables.find {
-                    txtFile.name.substring(0, txtFile.name.lastIndexOf("."))==it.table
+            for ((index, currentConfig) in importConfig.tables.withIndex()) {
+                val txtFile = unZipFiles.find {
+                    it.name == currentConfig.table
                 }
-                val list = FileIOUtils.readFile2List(txtFile, "UTF-8")
-                // 将list数据转换为map
-                for (line in list) {
-                    val map = gson.fromJson<Map<String, Any>>(line, object : TypeToken<MutableMap<String, Any>>() {}.type)
-                        .toMutableMap()
-                    currentConfig?.let {
-                        map["QItable"] = currentConfig.table
-                        map["QIname"] = currentConfig.name
-                        map["QIcode"] = currentConfig.code
-                        listResult.add(map)
+
+                val listResult: MutableList<Map<String, Any>> = mutableListOf()
+                currentConfig?.let {
+                    val list = FileIOUtils.readFile2List(txtFile, "UTF-8")
+                    if (list!=null) {
+                        // 将list数据转换为map
+                        for (line in list) {
+                            val map = gson.fromJson<Map<String, Any>>(line, object : TypeToken<MutableMap<String, Any>>() {}.type)
+                                .toMutableMap()
+                            map["QItable"] = currentConfig.table
+                            map["QIname"] = currentConfig.name
+                            map["QIcode"] = currentConfig.code
+                            listResult.add(map)
+                        }
                     }
                 }
+                // 将listResult数据插入到Realm数据库中
+                Realm.getDefaultInstance().beginTransaction()
+                for (map in listResult) { // 每一个map就是Realm的一条数据
+                    // 先查询这个mesh下有没有数据,如果有则跳过即可
+//                    val meshEntity = Realm.getDefaultInstance().where(RenderEntity::class.java).equalTo("properties['mesh']", map["mesh"].toString()).findFirst()
+                    val renderEntity = RenderEntity()
+                    renderEntity.code = map["QIcode"].toString().toInt()
+                    renderEntity.name = map["QIname"].toString()
+                    renderEntity.table = map["QItable"].toString()
+                    // 其他数据插入到Properties中
+                    renderEntity.geometry = map["geometry"].toString()
+                    for (entry in map) {
+                        renderEntity.properties[entry.key] = entry.value.toString()
+                    }
+                    Realm.getDefaultInstance().insert(renderEntity)
+                }
+                Realm.getDefaultInstance().commitTransaction()
                 // 1个文件发送一次flow流
-                emit(listResult)
+                emit("${index+1}/${importConfig.tables.size}")
             }
         }
     }
diff --git a/app/src/main/java/com/navinfo/omqs/hilt/ImportOMDBHiltFactory.kt b/app/src/main/java/com/navinfo/omqs/hilt/ImportOMDBHiltFactory.kt
index 5f3ff4fd..a1036dc0 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/ImportOMDBHiltFactory.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/ImportOMDBHiltFactory.kt
@@ -8,5 +8,5 @@ import java.io.File
 
 @AssistedFactory
 interface ImportOMDBHiltFactory {
-    fun obtainImportOMDBHelper(@Assisted("context")context: Context, @Assisted("omdbFile") omdbFile: File, @Assisted("configFile") dbVersion: File): ImportOMDBHelper
+    fun obtainImportOMDBHelper(@Assisted("context")context: Context, @Assisted("omdbFile") omdbFile: File): ImportOMDBHelper
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
index 36ec694e..cf18f7fb 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
@@ -10,7 +10,7 @@ import com.hjq.permissions.XXPermissions
 /**
  * 权限申请Activity
  */
-open class PermissionsActivity : BaseActivity() {
+open abstract class PermissionsActivity : BaseActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         val permissionList = mutableListOf<String>()
@@ -50,10 +50,10 @@ open class PermissionsActivity : BaseActivity() {
                             Toast.LENGTH_SHORT
                         )
                             .show()
-                        onPermissionsGranted()
+                        onPermissionsDenied()
                         return
                     } else {
-                        onPermissionsDenied()
+                        onPermissionsGranted()
                     }
                     // 在SD卡创建项目目录
                 }
@@ -79,14 +79,10 @@ open class PermissionsActivity : BaseActivity() {
     /**
      * 权限全部同意
      */
-    open fun onPermissionsGranted() {
-
-    }
+    open abstract fun onPermissionsGranted()
 
     /**
      * 权限
      */
-    open fun onPermissionsDenied() {
-
-    }
+    open abstract fun onPermissionsDenied()
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
index d0bc2101..20bb6afb 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
@@ -7,14 +7,19 @@ import android.widget.Toast
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.blankj.utilcode.util.ResourceUtils
+import com.navinfo.omqs.Constant
 import com.navinfo.omqs.bean.LoginUserBean
 import com.navinfo.omqs.db.RoomAppDatabase
 import com.navinfo.omqs.http.NetResult
 import com.navinfo.omqs.http.NetworkService
 import com.navinfo.omqs.tools.FileManager
 import dagger.hilt.android.lifecycle.HiltViewModel
+import io.realm.Realm
+import io.realm.RealmConfiguration
 import kotlinx.coroutines.*
 import okio.IOException
+import java.io.File
 import javax.inject.Inject
 
 enum class LoginStatus {
@@ -110,8 +115,7 @@ class LoginViewModel @Inject constructor(
         //文件夹初始化
         try {
             loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
-            createUserFolder(context)
-            // 初始化Realm
+            createUserFolder(context, "1")
         } catch (e: IOException) {
             loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
         }
@@ -151,8 +155,28 @@ class LoginViewModel @Inject constructor(
      * 创建用户目录
      */
     @Throws(IOException::class)
-    private fun createUserFolder(context: Context) {
+    private fun createUserFolder(context: Context, userId: String) {
         // 在SD卡创建用户目录,解压资源等
+        val userFolder = File("${Constant.DATA_PATH}/${userId}")
+        Constant.CURRENT_USER_ID = userId
+        // 初始化Realm
+        Realm.init(context.applicationContext)
+        val password = "encryp".encodeToByteArray().copyInto(ByteArray(64))
+        // 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+        Log.d("OMQSApplication", "密码是: ${byteArrayToHexString(password)}")
+        val config = RealmConfiguration.Builder()
+            .directory(userFolder)
+            .name("OMQS.realm")
+            .encryptionKey(password)
+//            .modules(Realm.getDefaultModule(), MyRealmModule())
+            .schemaVersion(1)
+            .build()
+        Realm.setDefaultConfiguration(config)
+        // 拷贝配置文件到用户目录下
+        val omdbConfigFile = File(userFolder.absolutePath, Constant.OMDB_CONFIG);
+        if (!omdbConfigFile.exists()) {
+            ResourceUtils.copyFileFromAssets(Constant.OMDB_CONFIG, omdbConfigFile.absolutePath)
+        }
     }
 
     /**
@@ -169,4 +193,8 @@ class LoginViewModel @Inject constructor(
         super.onCleared()
         cancelLogin()
     }
+
+    private fun byteArrayToHexString(byteArray: ByteArray): String {
+        return byteArray.joinToString("") { "%02x".format(it) }
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
index 77e85f60..b39d5cc0 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
@@ -17,6 +17,7 @@ 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.collect.library.map.NIMapController
 import com.navinfo.omqs.R
 import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
 import com.navinfo.omqs.db.ImportOMDBHelper
@@ -31,6 +32,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.FlowCollector
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.oscim.core.GeoPoint
 import java.io.File
 import java.util.UUID
 import javax.inject.Inject
@@ -48,6 +50,8 @@ class PersonalCenterFragment : BaseFragment(), FSAFActivityCallbacks {
 
     @Inject
     lateinit var importOMDBHiltFactory: ImportOMDBHiltFactory
+    @Inject
+    lateinit var niMapController: NIMapController
 
 
     override fun onCreateView(
@@ -73,15 +77,14 @@ class PersonalCenterFragment : BaseFragment(), FSAFActivityCallbacks {
                             val file = UriUtils.uri2File(uri)
                             // 开始导入数据
                             // 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-                            val job = CoroutineUtils.launchWithLoading(
+                            CoroutineUtils.launchWithLoading(
                                 requireContext(),
                                 loadingMessage = "生成数据..."
                             ) {
                                 val importOMDBHelper: ImportOMDBHelper =
                                     importOMDBHiltFactory.obtainImportOMDBHelper(
                                         requireContext(),
-                                        file,
-                                        File(file.parentFile, "config.json")
+                                        file
                                     )
                                 viewModel.obtainOMDBZipData(importOMDBHelper)
                             }
@@ -95,19 +98,19 @@ class PersonalCenterFragment : BaseFragment(), FSAFActivityCallbacks {
 
                         override fun onResult(uri: Uri) {
                             val file = UriUtils.uri2File(uri)
-                            // 开始导入数据
-                            CoroutineUtils.launchWithLoading(
-                                requireContext(),
-                                loadingMessage = "导入数据..."
-                            ) {
-                                val importOMDBHelper: ImportOMDBHelper =
-                                    importOMDBHiltFactory.obtainImportOMDBHelper(
-                                        requireContext(),
-                                        file,
-                                        File(file.parentFile, "config.json")
-                                    )
+                            val importOMDBHelper: ImportOMDBHelper =
+                                importOMDBHiltFactory.obtainImportOMDBHelper(
+                                    requireContext(),
+                                    file
+                                )
                                 viewModel.importOMDBData(importOMDBHelper)
-                            }
+//                            // 开始导入数据
+//                            CoroutineUtils.launchWithLoading(
+//                                requireContext(),
+//                                loadingMessage = "导入数据..."
+//                            ) {
+//
+//                            }
                         }
                     })
                 }
@@ -125,6 +128,8 @@ class PersonalCenterFragment : BaseFragment(), FSAFActivityCallbacks {
                 }
                 R.id.personal_center_menu_test -> {
                     viewModel.readRealmData()
+                    // 定位到指定位置
+                    niMapController.mMapView.vtmMap.animator().animateTo(GeoPoint(28.608398, 115.67901))
                 }
                 R.id.personal_center_menu_task_list -> {
                     findNavController().navigate(R.id.TaskListFragment)
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
index 24a5bdb2..19106b80 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
@@ -10,6 +10,7 @@ import com.blankj.utilcode.util.UriUtils
 import com.blankj.utilcode.util.ZipUtils
 import com.google.gson.Gson
 import com.navinfo.collect.library.data.entity.*
+import com.navinfo.collect.library.map.NIMapController
 import com.navinfo.collect.library.utils.GeometryTools
 import com.navinfo.omqs.bean.ScProblemTypeBean
 import com.navinfo.omqs.bean.ScRootCauseAnalysisBean
@@ -135,39 +136,15 @@ class PersonalCenterViewModel @Inject constructor(
     /**
      * 导入OMDB数据
      * */
-    suspend fun importOMDBData(importOMDBHelper: ImportOMDBHelper) {
-        Log.d("OMQSApplication", "开始导入数据")
-//        Realm.getDefaultInstance().beginTransaction()
-        importOMDBHelper.importOmdbZipFile(importOMDBHelper.omdbFile).collect {
-            Realm.getDefaultInstance().beginTransaction()
-            for (map in it) { // 每一个map就是Realm的一条数据
-                val renderEntity = RenderEntity()
-                renderEntity.code = map["QIcode"].toString().toInt()
-                renderEntity.name = map["QIname"].toString()
-                renderEntity.table = map["QItable"].toString()
-                // 其他数据插入到Properties中
-                renderEntity.geometry = map["GEOMETRY"].toString()
-                for (entry in map) {
-                    renderEntity.properties[entry.key] = entry.value.toString()
-                }
-                Realm.getDefaultInstance().insert(renderEntity)
+    fun importOMDBData(importOMDBHelper: ImportOMDBHelper) {
+        viewModelScope.launch(Dispatchers.IO) {
+            Log.d("OMQSApplication", "开始导入数据")
+            importOMDBHelper.importOmdbZipFile(importOMDBHelper.omdbFile).collect {
+                Log.d("importOMDBData", it)
             }
-            Realm.getDefaultInstance().commitTransaction()
+            Log.d("OMQSApplication", "导入数据完成")
         }
-//        Realm.getDefaultInstance().commitTransaction()
 
-//        val gson = Gson()
-//        // 数据导入结束后,开始生成渲染表所需的json文件,并生成压缩包
-//        for (table in importOMDBHelper.openConfigFile().tables/*listOf<String>("HAD_LINK")*/) {
-//            val omdbList = Realm.getDefaultInstance().where(OMDBEntity::class.java).equalTo("table", table.table).findAll()
-//            val outputFile = File(importOMDBHelper.omdbFile, "${table.table}.txt")
-//            // 将读取到的数据转换为json数据文件
-//            for (omdb in omdbList) {
-//                FileIOUtils.writeFileFromString(outputFile, gson.toJson(omdb))
-//            }
-//        }
-
-        Log.d("OMQSApplication", "导入数据完成")
     }
 
     fun importScProblemData(uri: Uri) {
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/RenderEntity.kt b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/RenderEntity.kt
index b4842cb2..3f7f0df0 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/RenderEntity.kt
+++ b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/RenderEntity.kt
@@ -23,7 +23,10 @@ open class RenderEntity(): RealmObject() {
     lateinit var table: String //要素表名
     var code: Int = 0 // 要素编码
     var geometry: String = ""
-        get() = field
+        get() {
+            wkt = GeometryTools.createGeometry(field)
+            return field
+        }
         set(value) {
             field = value
             // 根据geometry自动计算当前要素的x-tile和y-tile
@@ -39,8 +42,8 @@ open class RenderEntity(): RealmObject() {
     @Ignore
     var wkt: Geometry? = null
     var properties: RealmDictionary<String?> = RealmDictionary()
-    val tileX: RealmSet<Int> = RealmSet() // x方向的tile编码
-    val tileY: RealmSet<Int> = RealmSet()  // y方向的tile编码
+    var tileX: RealmSet<Int> = RealmSet() // x方向的tile编码
+    var tileY: RealmSet<Int> = RealmSet()  // y方向的tile编码
 
     constructor(name: String, properties: RealmDictionary<String?>): this() {
         this.name = name
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
index 86fde18a..3b419ed5 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
@@ -16,6 +16,7 @@ import com.navinfo.collect.library.map.cluster.ClusterMarkerRenderer
 import com.navinfo.collect.library.map.layers.MyItemizedLayer
 import com.navinfo.collect.library.map.source.MapLifeNiLocationTileSource
 import com.navinfo.collect.library.map.source.NavinfoMultiMapFileTileSource
+import com.navinfo.collect.library.map.source.OMDBTileSource
 import com.navinfo.collect.library.system.Constant
 import com.navinfo.collect.library.utils.GeometryTools
 import io.realm.Realm
@@ -40,6 +41,7 @@ import org.oscim.layers.tile.buildings.BuildingLayer
 import org.oscim.layers.tile.vector.VectorTileLayer
 import org.oscim.layers.tile.vector.labeling.LabelLayer
 import org.oscim.layers.tile.vector.labeling.LabelTileLoaderHook
+import org.oscim.map.Map
 import org.oscim.map.Map.UpdateListener
 import org.oscim.tiling.source.OkHttpEngine.OkHttpFactory
 import org.oscim.tiling.source.mapfile.MapFileTileSource
@@ -69,6 +71,15 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView,tr
      */
     private lateinit var labelNiLocationLayer: LabelLayer
 
+    /**
+     * 显示待测评OMDB数据的图层
+     * */
+    private lateinit var omdbVectorTileLayer: VectorTileLayer
+    private lateinit var omdbLabelLayer: LabelLayer
+    /**
+     * 文字大小
+     */
+    private val NUM_13 = 13
 
     init {
         initMap()
@@ -81,6 +92,8 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView,tr
 
         loadBaseMap()
 
+        initOMDBVectorTileLayer()
+
         mapLifeNiLocationTileSource = MapLifeNiLocationTileSource(mContext, mTracePath)
 
         vectorNiLocationTileLayer = VectorTileLayer(mMapView.vtmMap, mapLifeNiLocationTileSource)
@@ -105,6 +118,17 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView,tr
 
     }
 
+    private fun initOMDBVectorTileLayer() {
+        val omdbTileSource: OMDBTileSource = OMDBTileSource()
+        omdbVectorTileLayer = VectorTileLayer(mMapView.vtmMap, omdbTileSource)
+        omdbLabelLayer = LabelLayer(mMapView.vtmMap, omdbVectorTileLayer, LabelTileLoaderHook(), Constant.OMDB_MIN_ZOOM)
+        if(omdbVectorTileLayer!=null){
+            addLayer(omdbVectorTileLayer,NIMapView.LAYER_GROUPS.VECTOR_TILE)
+        }
+        if(omdbLabelLayer!=null){
+            addLayer(omdbLabelLayer, NIMapView.LAYER_GROUPS.VECTOR_TILE)
+        }
+    }
 
     /**
      * 切换基础底图样式
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBDataDecoder.java b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBDataDecoder.java
new file mode 100644
index 00000000..17111dc6
--- /dev/null
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBDataDecoder.java
@@ -0,0 +1,190 @@
+package com.navinfo.collect.library.map.source;
+
+import static org.oscim.core.MercatorProjection.latitudeToY;
+import static org.oscim.core.MercatorProjection.longitudeToX;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.navinfo.collect.library.data.entity.GeometryFeatureEntity;
+import com.navinfo.collect.library.data.entity.RenderEntity;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKBReader;
+import org.oscim.core.MapElement;
+import org.oscim.core.Tag;
+import org.oscim.core.Tile;
+import org.oscim.tiling.ITileDataSink;
+import org.oscim.tiling.source.mvt.TileDecoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class OMDBDataDecoder extends TileDecoder {
+    private final String mLocale;
+
+    private static final float REF_TILE_SIZE = 4096.0f;
+
+    private final GeometryFactory mGeomFactory;
+    private final MapElement mMapElement;
+    private ITileDataSink mTileDataSink;
+    private double mTileY, mTileX, mTileScale;
+
+    public OMDBDataDecoder() {
+        super();
+        mLocale = "";
+        mGeomFactory = new GeometryFactory();
+        mMapElement = new MapElement();
+        mMapElement.layer = 5;
+    }
+
+    @Override
+    public boolean decode(Tile tile, ITileDataSink sink, InputStream is) throws IOException {
+        mTileDataSink = sink;
+        mTileScale = 1 << tile.zoomLevel;
+        mTileX = tile.tileX / mTileScale;
+        mTileY = tile.tileY / mTileScale;
+        mTileScale *= Tile.SIZE;
+        return true;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public boolean decode(Tile tile, ITileDataSink sink, List<RenderEntity> listResult) {
+        mTileDataSink = sink;
+        mTileScale = 1 << tile.zoomLevel;
+        mTileX = tile.tileX / mTileScale;
+        mTileY = tile.tileY / mTileScale;
+        mTileScale *= Tile.SIZE;
+
+        listResult.stream().iterator().forEachRemaining(new Consumer<RenderEntity>() {
+            @Override
+            public void accept(RenderEntity renderEntity) {
+                Log.d("RealmDBTileDataSource", renderEntity.getGeometry());
+                Map<String, Object> properties= new HashMap<>(renderEntity.getProperties().size());
+                properties.putAll(renderEntity.getProperties());
+                parseGeometry(renderEntity.getTable(), renderEntity.getWkt(), properties);
+            }
+        });
+        return true;
+    }
+
+    public void parseGeometry(String layerName, Geometry geometry, Map<String, Object> tags) {
+        mMapElement.clear();
+        mMapElement.tags.clear();
+
+        parseTags(tags, layerName);
+        if (mMapElement.tags.size() == 0) {
+            return;
+        }
+
+        boolean err = false;
+        if (geometry instanceof Point) {
+            mMapElement.startPoints();
+            processCoordinateArray(geometry.getCoordinates(), false);
+        } else if (geometry instanceof MultiPoint) {
+            MultiPoint multiPoint = (MultiPoint) geometry;
+            for (int i = 0; i < multiPoint.getNumGeometries(); i++) {
+                mMapElement.startPoints();
+                processCoordinateArray(multiPoint.getGeometryN(i).getCoordinates(), false);
+            }
+        } else if (geometry instanceof LineString) {
+            processLineString((LineString) geometry);
+        } else if (geometry instanceof MultiLineString) {
+            MultiLineString multiLineString = (MultiLineString) geometry;
+            for (int i = 0; i < multiLineString.getNumGeometries(); i++) {
+                processLineString((LineString) multiLineString.getGeometryN(i));
+            }
+        } else if (geometry instanceof Polygon) {
+            Polygon polygon = (Polygon) geometry;
+            processPolygon(polygon);
+        } else if (geometry instanceof MultiPolygon) {
+            MultiPolygon multiPolygon = (MultiPolygon) geometry;
+            for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
+                processPolygon((Polygon) multiPolygon.getGeometryN(i));
+            }
+        } else {
+            err = true;
+        }
+
+        if (!err) {
+            mTileDataSink.process(mMapElement);
+        }
+    }
+
+    private void processLineString(LineString lineString) {
+        mMapElement.startLine();
+        processCoordinateArray(lineString.getCoordinates(), false);
+    }
+
+    private void processPolygon(Polygon polygon) {
+        mMapElement.startPolygon();
+        processCoordinateArray(polygon.getExteriorRing().getCoordinates(), true);
+        for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
+            mMapElement.startHole();
+            processCoordinateArray(polygon.getInteriorRingN(i).getCoordinates(), true);
+        }
+    }
+
+    private void processCoordinateArray(Coordinate[] coordinates, boolean removeLast) {
+        int length = removeLast ? coordinates.length - 1 : coordinates.length;
+        for (int i = 0; i < length; i++) {
+            mMapElement.addPoint((float) ((longitudeToX(coordinates[i].x) - mTileX) * mTileScale),
+                    (float) ((latitudeToY(coordinates[i].y) - mTileY) * mTileScale));
+        }
+
+//        int length = removeLast ? coordinates.length - 1 : coordinates.length;
+//        // 初始化3D数据类型
+//        float[] point3D = new float[coordinates.length*3];
+//        for (int i = 0; i < length; i++) {
+//            point3D[i*3] = (float) coordinates[i].x;
+//            point3D[(i*3)+1] = (float) coordinates[i].y;
+//            point3D[(i*3)+2] = (float) coordinates[i].z;
+//        }
+//        mMapElement.points = point3D;
+//        mMapElement.pointNextPos = mMapElement.points.length;
+//        mMapElement.type = GeometryBuffer.GeometryType.TRIS;
+    }
+
+    private void parseTags(Map<String, Object> map, String layerName) {
+        mMapElement.tags.add(new Tag("layer", layerName));
+        boolean hasName = false;
+        String fallbackName = null;
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            String val = (value instanceof String) ? (String) value : String.valueOf(value);
+            if (key.startsWith(Tag.KEY_NAME)) {
+                int len = key.length();
+                if (len == 4) {
+                    fallbackName = val;
+                    continue;
+                }
+                if (len < 7)
+                    continue;
+                if (mLocale.equals(key.substring(5))) {
+                    hasName = true;
+                    mMapElement.tags.add(new Tag(Tag.KEY_NAME, val, false));
+                }
+            } else {
+                mMapElement.tags.add(new Tag(key, val));
+            }
+        }
+        if (!hasName && fallbackName != null)
+            mMapElement.tags.add(new Tag(Tag.KEY_NAME, fallbackName, false));
+    }
+}
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileDataSource.java b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileDataSource.java
new file mode 100644
index 00000000..31f2291a
--- /dev/null
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileDataSource.java
@@ -0,0 +1,74 @@
+package com.navinfo.collect.library.map.source;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.navinfo.collect.library.data.RealmUtils;
+import com.navinfo.collect.library.data.entity.GeometryFeatureEntity;
+import com.navinfo.collect.library.data.entity.RenderEntity;
+import com.navinfo.collect.library.system.Constant;
+
+import org.oscim.layers.tile.MapTile;
+import org.oscim.tiling.ITileDataSink;
+import org.oscim.tiling.ITileDataSource;
+import org.oscim.tiling.QueryResult;
+
+import java.util.List;
+
+import io.realm.Realm;
+import io.realm.RealmQuery;
+
+public class OMDBTileDataSource implements ITileDataSource {
+    private final ThreadLocal<OMDBDataDecoder> mThreadLocalDecoders = new ThreadLocal<OMDBDataDecoder>() {
+        @Override
+        protected OMDBDataDecoder initialValue() {
+            return new OMDBDataDecoder();
+        }
+    };
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @Override
+    public void query(MapTile tile, ITileDataSink mapDataSink) {
+        // 获取tile对应的坐标范围
+        if (tile.zoomLevel>=Constant.OMDB_MIN_ZOOM&&tile.zoomLevel<=Constant.OVER_ZOOM) {
+            int m = Constant.OVER_ZOOM-tile.zoomLevel;
+            int xStart = (int)tile.tileX<<m;
+            int xEnd = (int)((tile.tileX+1)<<m);
+            int yStart = (int)tile.tileY<<m;
+            int yEnd = (int)((tile.tileY+1)<<m);
+
+            RealmQuery<RenderEntity> realmQuery = Realm.getDefaultInstance().where(RenderEntity.class)
+                    .rawPredicate("tileX>="+xStart+" and tileX<="+xEnd+" and tileY>="+yStart+" and tileY<="+yEnd);
+//            // 筛选不显示的数据
+//            if (Constant.HAD_LAYER_INVISIABLE_ARRAY!=null&&Constant.HAD_LAYER_INVISIABLE_ARRAY.length>0) {
+//                realmQuery.beginGroup();
+//                for (String type: Constant.HAD_LAYER_INVISIABLE_ARRAY) {
+//                    realmQuery.notEqualTo("name", type);
+//                }
+//                realmQuery.endGroup();
+//            }
+            List<RenderEntity> listResult = realmQuery/*.distinct("id")*/.findAll();
+            if (!listResult.isEmpty()) {
+                mThreadLocalDecoders.get().decode(tile, mapDataSink, listResult);
+            }
+            mapDataSink.completed(QueryResult.SUCCESS);
+            Log.d("RealmDBTileDataSource", "tile:"+tile.getBoundingBox().toString());
+        } else {
+            mapDataSink.completed(QueryResult.SUCCESS);
+        }
+    }
+
+    @Override
+    public void dispose() {
+
+    }
+
+    @Override
+    public void cancel() {
+        if (Realm.getDefaultInstance().isInTransaction()) {
+            Realm.getDefaultInstance().cancelTransaction();
+        }
+    }
+}
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileSource.java b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileSource.java
new file mode 100644
index 00000000..2ac45ac9
--- /dev/null
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/source/OMDBTileSource.java
@@ -0,0 +1,25 @@
+package com.navinfo.collect.library.map.source;
+
+import com.navinfo.collect.library.system.Constant;
+
+import org.oscim.tiling.ITileDataSource;
+import org.oscim.tiling.OverzoomTileDataSource;
+import org.oscim.tiling.TileSource;
+
+public class OMDBTileSource extends TileSource {
+
+    @Override
+    public ITileDataSource getDataSource() {
+        return new OverzoomTileDataSource(new OMDBTileDataSource(), Constant.OVER_ZOOM);
+    }
+
+    @Override
+    public OpenResult open() {
+        return OpenResult.SUCCESS;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
index ab5e683d..104b9932 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
+++ b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
@@ -31,5 +31,6 @@ public class Constant {
     public static String[] HAD_LAYER_INVISIABLE_ARRAY;
     public static final int OVER_ZOOM = 21;
     public static final int MAX_ZOOM = 25;
+    public static final int OMDB_MIN_ZOOM = 18;
 }