feat: add LocoTypeUtil

This commit is contained in:
Nedifinita
2025-08-19 16:35:47 +08:00
parent c4b06f3b3c
commit 39effddfc1
5 changed files with 219 additions and 99 deletions

View File

@@ -0,0 +1,142 @@
001,解放
003,前进
005,建设
006,KD7
055,蓝箭控车
081,东风21
101,东风
102,东风2
103,东风3
104,东风4
105,东风4客
106,东风4C
107,东风5
108,东风5宽
109,东风6
110,东风7
111,东风8
112,东风9
113,东风10
114,东方红1
115,东方红2
116,东方红3
117,东方红5
118,北京
119,北京宽
120,ND2
121,ND3
122,ND4
123,ND5
124,NY5
125,NY6
126,NY7
127,轻油
128,东方红21
129,东风7B
130,东风5S
131,东风7C
132,东风7S
133,工矿1
134,工矿1F
135,东风4E
136,东风7D
137,工矿1A
138,东风11
139,天安
140,东风10F
141,东风4D
142,东风8B
143,东风12
144,东风7E
145,NYJ1
146,NZJ1
147,NZJ2
148,东风4DJ
149,新曙光
150,神州
151,NJ2
152,东风7G
153,NDJ3
157,FXN3D
158,东风11G
160,HXN3
161,HXN5
162,HXN3B
163,HXN5B
167,FXN3B
169,FXN3C
170,FXN5C
171,FXN3-J
201,8G
202,8K
203,6G
204,6K
205,韶山1
206,韶山3
207,韶山4
208,韶山5
209,韶山6
210,韶山3B
211,韶山7
212,韶山8
213,韶山7B
214,韶山7C
215,韶山6B
216,韶山9
217,韶山7D
218,DJ熊猫
219,DJ1
220,DJ2
221,DJF
222,蓝箭动车
223,先锋号
224,韶山7E
225,韶山4G
226,韶山3C
228,天梭
229,DJ4和谐
230,KTT
231,HXD1
232,HXD2
233,HXD3
234,HXD1B
235,HXD2B
236,HXD3B
237,HXD1C
238,HXD2C
239,HXD3C
240,HXD1D
241,HXD2D
242,HXD3D
243,FXD1B
244,FXD2B
245,FXD1
246,FXD3
247,FXD1-J
248,FXD3-J
249,KZ25TA
251,KZ25TB
252,HXD1D-J
254,FXD1H
300,雪域神州
301,CRH1
302,CRH2
303,CRH3
305,CRH5
306,CRH380A
307,CRH380B
308,CRH380C
309,CRH380D
310,CRH6A
311,CR400AF
312,CR400BF
313,CR300AF
314,CR300BF
315,CRH2E
316,CRH6F
330,CJ1
331,CJ2
332,CJ3
333,CJ4
334,CJ5
335,CJ6
1 001 解放
2 003 前进
3 005 建设
4 006 KD7
5 055 蓝箭控车
6 081 东风21
7 101 东风
8 102 东风2
9 103 东风3
10 104 东风4
11 105 东风4客
12 106 东风4C
13 107 东风5
14 108 东风5宽
15 109 东风6
16 110 东风7
17 111 东风8
18 112 东风9
19 113 东风10
20 114 东方红1
21 115 东方红2
22 116 东方红3
23 117 东方红5
24 118 北京
25 119 北京宽
26 120 ND2
27 121 ND3
28 122 ND4
29 123 ND5
30 124 NY5
31 125 NY6
32 126 NY7
33 127 轻油
34 128 东方红21
35 129 东风7B
36 130 东风5S
37 131 东风7C
38 132 东风7S
39 133 工矿1
40 134 工矿1F
41 135 东风4E
42 136 东风7D
43 137 工矿1A
44 138 东风11
45 139 天安
46 140 东风10F
47 141 东风4D
48 142 东风8B
49 143 东风12
50 144 东风7E
51 145 NYJ1
52 146 NZJ1
53 147 NZJ2
54 148 东风4DJ
55 149 新曙光
56 150 神州
57 151 NJ2
58 152 东风7G
59 153 NDJ3
60 157 FXN3D
61 158 东风11G
62 160 HXN3
63 161 HXN5
64 162 HXN3B
65 163 HXN5B
66 167 FXN3B
67 169 FXN3C
68 170 FXN5C
69 171 FXN3-J
70 201 8G
71 202 8K
72 203 6G
73 204 6K
74 205 韶山1
75 206 韶山3
76 207 韶山4
77 208 韶山5
78 209 韶山6
79 210 韶山3B
80 211 韶山7
81 212 韶山8
82 213 韶山7B
83 214 韶山7C
84 215 韶山6B
85 216 韶山9
86 217 韶山7D
87 218 DJ熊猫
88 219 DJ1
89 220 DJ2
90 221 DJF
91 222 蓝箭动车
92 223 先锋号
93 224 韶山7E
94 225 韶山4G
95 226 韶山3C
96 228 天梭
97 229 DJ4和谐
98 230 KTT
99 231 HXD1
100 232 HXD2
101 233 HXD3
102 234 HXD1B
103 235 HXD2B
104 236 HXD3B
105 237 HXD1C
106 238 HXD2C
107 239 HXD3C
108 240 HXD1D
109 241 HXD2D
110 242 HXD3D
111 243 FXD1B
112 244 FXD2B
113 245 FXD1
114 246 FXD3
115 247 FXD1-J
116 248 FXD3-J
117 249 KZ25TA
118 251 KZ25TB
119 252 HXD1D-J
120 254 FXD1H
121 300 雪域神州
122 301 CRH1
123 302 CRH2
124 303 CRH3
125 305 CRH5
126 306 CRH380A
127 307 CRH380B
128 308 CRH380C
129 309 CRH380D
130 310 CRH6A
131 311 CR400AF
132 312 CR400BF
133 313 CR300AF
134 314 CR300BF
135 315 CRH2E
136 316 CRH6F
137 330 CJ1
138 331 CJ2
139 332 CJ3
140 333 CJ4
141 334 CJ5
142 335 CJ6

View File

@@ -110,15 +110,13 @@ class MainActivity : ComponentActivity() {
private var historyScrollPosition by mutableStateOf(0)
private var historyScrollOffset by mutableStateOf(0)
private var historyCardMapStates by mutableStateOf<Map<String, CardMapView>>(emptyMap())
private var settingsScrollPosition by mutableStateOf(0)
private var mapCenterPosition by mutableStateOf<Pair<Double, Double>?>(null)
private var mapZoomLevel by mutableStateOf(10.0)
private var mapRailwayLayerVisible by mutableStateOf(true)
private var settingsScrollPosition by mutableStateOf(0)
private var mergeSettings by mutableStateOf(MergeSettings())
private var targetDeviceName = "LBJReceiver"
private var specifiedDeviceAddress by mutableStateOf<String?>(null)
@@ -185,10 +183,10 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TrainRecord.initializeLocoTypeUtil(this)
loadSettings()
val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -199,99 +197,23 @@ class MainActivity : ComponentActivity() {
))
} else {
permissions.addAll(arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
}
permissions.addAll(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
if (permissions.isNotEmpty()) {
requestPermissions.launch(permissions.toTypedArray())
} else {
startAutoScanAndConnect()
}
requestPermissions.launch(permissions.toTypedArray())
Configuration.getInstance().userAgentValue = packageName
bleClient.setTrainInfoCallback { jsonData ->
handleTrainInfo(jsonData)
}
bleClient.setHighFrequencyReconnect(true)
bleClient.setConnectionLostCallback {
runOnUiThread {
deviceStatus = "连接丢失,正在重连..."
showDisconnectButton = false
if (showConnectionDialog) {
foundDevices = emptyList()
startScan()
}
}
}
bleClient.setConnectionSuccessCallback { address ->
runOnUiThread {
deviceAddress = address
deviceStatus = "已连接"
showDisconnectButton = true
Log.d(TAG, "Connection success callback: address=$address")
}
}
lifecycleScope.launch {
try {
locoInfoUtil.loadLocoData()
Log.d(TAG, "Loaded locomotive data")
} catch (e: Exception) {
Log.e(TAG, "Load locomotive data failed", e)
}
}
try {
val osmCacheDir = File(cacheDir, "osm").apply { mkdirs() }
val tileCache = File(osmCacheDir, "tiles").apply { mkdirs() }
Configuration.getInstance().apply {
userAgentValue = packageName
load(this@MainActivity, getSharedPreferences("osmdroid", Context.MODE_PRIVATE))
osmdroidBasePath = osmCacheDir
osmdroidTileCache = tileCache
expirationOverrideDuration = 86400000L * 7
tileDownloadThreads = 4
tileFileSystemThreads = 4
setUserAgentValue("LBJConsole/1.0")
}
Log.d(TAG, "OSM cache configured")
} catch (e: Exception) {
Log.e(TAG, "OSM cache config failed", e)
}
saveSettings()
lifecycleScope.launch {
if (SettingsActivity.isBackgroundServiceEnabled(this@MainActivity)) {
BackgroundService.startService(this@MainActivity)
}
}
enableEdgeToEdge()
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = false
}
setContent {
LBJConsoleTheme {
val scope = rememberCoroutineScope()
@@ -334,7 +256,6 @@ class MainActivity : ComponentActivity() {
Log.d(TAG, "Auto connect enabled: $enabled")
},
latestRecord = latestRecord,
recentRecords = recentRecords,
lastUpdateTime = lastUpdateTime,
@@ -344,10 +265,11 @@ class MainActivity : ComponentActivity() {
},
onClearMonitorLog = {
recentRecords.clear()
latestRecord = null
lastUpdateTime = null
temporaryStatusMessage = null
},
allRecords = trainRecordManager.getMixedRecords(),
mergedRecords = trainRecordManager.getMergedRecords(),
recordCount = trainRecordManager.getRecordCount(),
@@ -499,7 +421,6 @@ class MainActivity : ComponentActivity() {
}
}
}
}
}
@@ -762,7 +683,7 @@ class MainActivity : ComponentActivity() {
val settings = appSettingsRepository.getSettings()
settingsDeviceName = settings.deviceName
targetDeviceName = settingsDeviceName
targetDeviceName = settings.deviceName
currentTab = settings.currentTab
historyEditMode = settings.historyEditMode

View File

@@ -1,20 +1,29 @@
package org.noxylva.lbjconsole.model
import android.content.Context
import android.util.Log
import org.json.JSONObject
import java.util.*
import org.osmdroid.util.GeoPoint
import org.noxylva.lbjconsole.util.LocationUtils
import org.noxylva.lbjconsole.util.LocationUtil
import org.noxylva.lbjconsole.util.LocoTypeUtil
class TrainRecord(jsonData: JSONObject? = null) {
companion object {
const val TAG = "TrainRecord"
private var nextId = 0L
private var LocoTypeUtil: LocoTypeUtil? = null
@Synchronized
private fun generateUniqueId(): String {
return "${System.currentTimeMillis()}_${++nextId}"
}
fun initializeLocoTypeUtil(context: Context) {
if (LocoTypeUtil == null) {
LocoTypeUtil = LocoTypeUtil(context)
}
}
}
val uniqueId: String
@@ -75,20 +84,26 @@ class TrainRecord(jsonData: JSONObject? = null) {
position = jsonData.optString("pos", "")
time = jsonData.optString("time", "")
loco = jsonData.optString("loco", "")
locoType = jsonData.optString("loco_type", "")
// 不再直接从JSON获取loco_type而是从loco字段前三位获取
locoType = if (loco.isNotEmpty()) {
val prefix = if (loco.length >= 3) loco.take(3) else loco
LocoTypeUtil?.getLocoTypeByCode(prefix) ?: ""
} else {
""
}
lbjClass = jsonData.optString("lbj_class", "")
route = jsonData.optString("route", "")
positionInfo = jsonData.optString("position_info", "")
rssi = jsonData.optDouble("rssi", 0.0)
_coordinates = null
Log.d(TAG, "Successfully parsed: train=$train, dir=$direction, speed=$speed, lbjClass='$lbjClass'")
Log.d(TAG, "Successfully parsed: train=$train, dir=$direction, speed=$speed, lbjClass='$lbjClass', locoType='$locoType'")
} catch (e: Exception) {
Log.e(TAG, "JSON parse error: ${e.message}", e)
try { train = jsonData.optString("train", "") } catch (e: Exception) { }
try { direction = jsonData.optInt("dir", 0) } catch (e: Exception) { }
try { speed = jsonData.optString("speed", "") } catch (e: Exception) { }
@@ -107,7 +122,7 @@ class TrainRecord(jsonData: JSONObject? = null) {
}
_coordinates = LocationUtils.parsePositionInfo(positionInfo)
_coordinates = LocationUtil.parsePositionInfo(positionInfo)
return _coordinates
}
private fun isValidValue(value: String): Boolean {

View File

@@ -4,8 +4,8 @@ import android.util.Log
import org.osmdroid.util.GeoPoint
object LocationUtils {
private const val TAG = "LocationUtils"
object LocationUtil {
private const val TAG = "LocationUtil"
fun parsePositionInfo(positionInfo: String): GeoPoint? {

View File

@@ -0,0 +1,42 @@
package org.noxylva.lbjconsole.util
import android.content.Context
import java.io.BufferedReader
import java.io.InputStreamReader
class LocoTypeUtil(private val context: Context) {
private val locoTypeMap = mutableMapOf<String, String>()
init {
loadLocoTypeMapping()
}
private fun loadLocoTypeMapping() {
try {
context.assets.open("loco_number_info.csv").use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.lines().forEach { line ->
val parts = line.split(",")
if (parts.size >= 2) {
val code = parts[0].trim()
val type = parts[1].trim()
locoTypeMap[code] = type
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun getLocoTypeByCode(code: String): String? {
return locoTypeMap[code]
}
fun getLocoTypeByLocoNumber(locoNumber: String): String? {
if (locoNumber.length < 3) return null
val prefix = locoNumber.take(3)
return getLocoTypeByCode(prefix)
}
}