Kaynağa Gözat

refactor(更新) :
- 修改电机状态图逻辑
- 优化地图点位加载和显示逻辑
- 修改获取设备序列号的方式
- 修改地图接口和数据结构
- 修改地图底色和部分文案

周文健 2 ay önce
ebeveyn
işleme
abf7f053c0

+ 2 - 7
app/src/main/java/com/grkj/iscs_mars/extentions/Context.kt

@@ -9,6 +9,7 @@ import androidx.core.app.ActivityCompat
 import androidx.core.content.ContextCompat
 import androidx.lifecycle.Observer
 import com.grkj.iscs_mars.util.NetManager
+import com.sik.sikcore.device.DeviceUtils
 import java.util.Locale
 
 /**
@@ -26,13 +27,7 @@ fun Context.removeNetObserver(observer: Observer<Boolean>) {
 
 @SuppressLint("MissingPermission")
 fun Context.serialNo(): String {
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-        && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
-        == PackageManager.PERMISSION_GRANTED
-    ) {
-        return Build.getSerial().uppercase(Locale.ROOT)
-    }
-    return Build.SERIAL.uppercase(Locale.ROOT)
+    return DeviceUtils.getDeviceId(length = 10)
 }
 
 /**

+ 6 - 1
app/src/main/java/com/grkj/iscs_mars/model/UrlConsts.kt

@@ -217,6 +217,11 @@ object UrlConsts {
      */
     const val MAP_INFO = "/iscs/map/selectIsMapById"
 
+    /**
+     * 电机地图
+     */
+    const val MOTOR_MAP_INFO = "/iscs/motor/getIsMotorListByLotoId"
+
     /**
      * 查询地图点位数据-分页
      */
@@ -366,5 +371,5 @@ object UrlConsts {
     /**
      * 获取地图列表
      */
-    const val GET_MAP_PAGE = "/iscs/switchmap/getIsLotoSwitchMapPage"
+    const val GET_MAP_PAGE = "/iscs/station/getIsLotoStationPage"
 }

+ 3 - 7
app/src/main/java/com/grkj/iscs_mars/model/vo/map/LotoSwitchMapPageRespVO.kt

@@ -11,14 +11,10 @@ data class LotoSwitchMapPageRespVO(
     val total: Int
 ) {
     data class Record(
-        val switchMapId: Long?,
+        val motorMapId: Long?,
 
-        val mapId: String?,
+        val lotoId: String?,
 
-        val mapName: String?,
-
-        val switchMapName: String?,
-
-        val workstationId: Long?,
+        val motorMapName: String?,
     )
 }

+ 38 - 0
app/src/main/java/com/grkj/iscs_mars/model/vo/map/MotorMapInfoRespVO.kt

@@ -0,0 +1,38 @@
+package com.grkj.iscs_mars.model.vo.map
+
+data class MotorMapInfoRespVO(
+    val data: List<IsMotorMapPoint>?
+) {
+    data class IsMotorMapPoint(
+        val id: Long?,
+
+        val mapId: Long?,
+
+        val mapName: String?,
+
+        val mapType: String?,
+
+        val motorId: Long?,
+        val pointId: Long?,
+        val pointName: String?,
+        val motorCode: String?,
+        val motorName: String?,
+        val motorType: String?,
+
+        val x: String?,
+
+        val y: String?,
+
+        val delFlag: String?,
+
+        val pointIcon: String?,
+
+        val pointPicture: String?,
+
+        val pointNfc: String?,
+
+        val pointSerialNumber: String?,
+
+        val switchStatus: String?,
+    )
+}

+ 21 - 0
app/src/main/java/com/grkj/iscs_mars/util/NetApi.kt

@@ -35,6 +35,7 @@ import com.grkj.iscs_mars.model.vo.machinery.MachineryPageRespVO
 import com.grkj.iscs_mars.model.vo.map.LotoSwitchMapPageRespVO
 import com.grkj.iscs_mars.model.vo.map.MapInfoRespVO
 import com.grkj.iscs_mars.model.vo.map.MapPointPageRespVO
+import com.grkj.iscs_mars.model.vo.map.MotorMapInfoRespVO
 import com.grkj.iscs_mars.model.vo.sop.SopInfoRespVO
 import com.grkj.iscs_mars.model.vo.sop.SopPageRespVO
 import com.grkj.iscs_mars.model.vo.system.SystemAttributeByKeyRespVO
@@ -892,6 +893,26 @@ object NetApi {
         )
     }
 
+    /**
+     * 获取地图参数详细信息
+     *
+     * @param id 机柜固定首页传1,锁定站传2,物资柜固定传4
+     */
+    fun getMotorMapInfo(id: Long, callBack: (MotorMapInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.MOTOR_MAP_INFO,
+            false,
+            mapOf(
+                "lotoId" to id
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(it.toBean(MotorMapInfoRespVO::class.java))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
     /**
      * 查询地图点位数据-分页
      *

+ 32 - 29
app/src/main/java/com/grkj/iscs_mars/view/activity/SwitchStatusActivity.kt

@@ -19,7 +19,7 @@ import com.grkj.iscs_mars.databinding.ItemSwitchBinding
 import com.grkj.iscs_mars.modbus.ModBusController
 import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_SWITCH_COLLECTION_UPDATE
 import com.grkj.iscs_mars.model.vo.map.LotoSwitchMapPageRespVO
-import com.grkj.iscs_mars.model.vo.map.MapInfoRespVO.IsMapPoint
+import com.grkj.iscs_mars.model.vo.map.MotorMapInfoRespVO
 import com.grkj.iscs_mars.util.CommonUtils
 import com.grkj.iscs_mars.util.ToastUtils
 import com.grkj.iscs_mars.util.log.LogUtil
@@ -37,8 +37,8 @@ class SwitchStatusActivity :
     private var stationLayer: CustomSwitchStationLayer? = null
     private lateinit var gestureDetector: GestureDetector
     private lateinit var switchInfoDialog: SwitchInfoDialog
-    private var currentSwitchMapId = 0L
-    private var currentMapId = ""
+    private var currentMotorMapId = 0L
+    private var currentLotoId = ""
 
     override fun initPresenter(): SwitchStatusPresenter {
         return SwitchStatusPresenter()
@@ -70,7 +70,7 @@ class SwitchStatusActivity :
         }
         mBinding?.mapview?.setBackgroundColorInt(getColor(R.color.color_map_base))
         mBinding?.rvList?.linear()?.dividerSpace(10, DividerOrientation.GRID)?.setup {
-            addType<IsMapPoint>(R.layout.item_switch)
+            addType<MotorMapInfoRespVO.IsMotorMapPoint>(R.layout.item_switch)
             onBind {
                 onRVListBinding()
             }
@@ -80,12 +80,12 @@ class SwitchStatusActivity :
             onBind {
                 val item = getModel<LotoSwitchMapPageRespVO.Record>()
                 val itemBinding = getBinding<ItemMapBinding>()
-                itemBinding.mapName.text = item.switchMapName
-                itemBinding.mapName.isSelected = item.switchMapId == currentSwitchMapId
+                itemBinding.mapName.text = item.motorMapName
+                itemBinding.mapName.isSelected = item.motorMapId == currentMotorMapId
                 itemBinding.mapName.setDebouncedClickListener {
-                    getMap(item.mapId.toString())
-                    currentSwitchMapId = item.switchMapId ?: 0
-                    currentMapId = item.mapId.toString()
+                    getMap(item.motorMapId.toString(), item.lotoId.toString())
+                    currentMotorMapId = item.motorMapId ?: 0
+                    currentLotoId = item.lotoId.toString()
                     adapter.notifyDataSetChanged()
                 }
             }
@@ -95,9 +95,9 @@ class SwitchStatusActivity :
 
     private fun BindingAdapter.BindingViewHolder.onRVListBinding() {
         val itemBinding = getBinding<ItemSwitchBinding>()
-        val item = getModel<IsMapPoint>()
+        val item = getModel<MotorMapInfoRespVO.IsMotorMapPoint>()
         val switchData = ModBusController.getSwitchData()
-        itemBinding.switchName.text = item.entityName
+        itemBinding.switchName.text = item.motorName
         itemBinding.switchId.text = context.getString(R.string.switch_id, item.pointNfc)
         val switchStatus = switchData
             .find { it.idx == item.pointSerialNumber?.toInt() }?.enabled
@@ -119,7 +119,7 @@ class SwitchStatusActivity :
             }
         }
         itemBinding.root.setDebouncedClickListener {
-            stationLayer?.selectPoint(item.entityId)
+            stationLayer?.selectPoint(item.motorId)
         }
     }
 
@@ -128,37 +128,40 @@ class SwitchStatusActivity :
         BusinessManager.mEventBus.observe(this) {
             when (it.code) {
                 MSG_EVENT_SWITCH_COLLECTION_UPDATE -> {
-                    getMap(currentMapId)
+                    getMap(currentMotorMapId.toString(), currentLotoId)
                     mBinding?.rvList?.adapter?.notifyDataSetChanged()
                 }
             }
         }
         presenter?.getMapPage {
             if (it?.records?.isNotEmpty() == true) {
-                currentSwitchMapId = it.records[0].switchMapId ?: 0
-                currentMapId = it.records[0].mapId.toString()
+                currentMotorMapId = it.records[0].motorMapId ?: 0
+                currentLotoId = it.records[0].lotoId.toString()
                 LogUtil.i("地图数据:${it.records}")
                 mBinding?.mapRv?.models = it.records
-                getMap(it.records[0].mapId.toString())
+                getMap(it.records[0].motorMapId.toString(), it.records[0].lotoId.toString())
             }
         }
     }
 
-    private fun getMap(mapId: String) {
-        if (mapId.isEmpty()) {
+    private fun getMap(mapId: String, lotoId: String) {
+        if (lotoId.isEmpty() || mapId.isEmpty()) {
             return
         }
         presenter?.getMapInfo(mapId.toLong()) { itMapInfo ->
-            mBinding?.rvList?.models = itMapInfo?.pointList
-            ThreadUtils.runOnIO {
-                presenter?.mapDataHandle(
-                    this@SwitchStatusActivity,
-                    itMapInfo,
-                    { mBinding?.mapview },
-                    stationLayer
-                ) { mapBmp ->
-                    ThreadUtils.runOnMain {
-                        mBinding?.mapview?.loadMap(mapBmp)
+            presenter?.getMotorMapInfo(lotoId.toLong()) { itMotorMapInfo ->
+                mBinding?.rvList?.models = itMotorMapInfo?.data
+                ThreadUtils.runOnIO {
+                    presenter?.mapDataHandle(
+                        this@SwitchStatusActivity,
+                        itMapInfo,
+                        itMotorMapInfo,
+                        { mBinding?.mapview },
+                        stationLayer
+                    ) { mapBmp ->
+                        ThreadUtils.runOnMain {
+                            mBinding?.mapview?.loadMap(mapBmp)
+                        }
                     }
                 }
             }
@@ -186,7 +189,7 @@ class SwitchStatusActivity :
                     )
                     stationLayer?.onLongPressListener = { point, screenX, screenY, _, _ ->
                         switchInfoDialog.setSwitchInfo(
-                            point.entityName,
+                            point.motorName ?: "",
                             "${point.pointNfc}",
                             point.status
                         )

+ 62 - 65
app/src/main/java/com/grkj/iscs_mars/view/presenter/SwitchStatusPresenter.kt

@@ -1,17 +1,10 @@
 package com.grkj.iscs_mars.view.presenter
 
 import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.PointF
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
-import com.grkj.iscs_mars.MyApplication
 import com.grkj.iscs_mars.R
-import com.grkj.iscs_mars.extentions.serialNo
-import com.grkj.iscs_mars.model.SimData
-import com.grkj.iscs_mars.model.vo.BaseVO
 import com.grkj.iscs_mars.model.vo.map.LotoSwitchMapPageRespVO
 import com.grkj.iscs_mars.model.vo.map.MapInfoRespVO
+import com.grkj.iscs_mars.model.vo.map.MotorMapInfoRespVO
 import com.grkj.iscs_mars.util.BitmapUtil
 import com.grkj.iscs_mars.util.Executor
 import com.grkj.iscs_mars.util.NetApi
@@ -55,6 +48,17 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
         }
     }
 
+    /**
+     * 获取电机开关图
+     */
+    fun getMotorMapInfo(lotoId: Long, callBack: (MotorMapInfoRespVO?) -> Unit) {
+        NetApi.getMotorMapInfo(lotoId) {
+            Executor.runOnMain {
+                callBack(it)
+            }
+        }
+    }
+
     /**
      * 处理地图大图 + 点位(后端点位为“格子左上角”,前端以“中心锚点”绘制)
      *
@@ -66,95 +70,88 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
      * @param cellPx       后端“格子”的像素大小(与后端约定,默认 50f)
      * @param yAxisFlip    后端若以左下为 (0,0) 则需要翻转 Y;通常是左上为 (0,0) → false
      */
+    // 放在你原有的位置(保持签名不变)
     suspend fun mapDataHandle(
         context: Context,
         itMapInfo: MapInfoRespVO?,
+        itMotorMapInfo: MotorMapInfoRespVO?,
         mapViewRef: () -> MapView?,
         stationLayer: CustomSwitchStationLayer?,
-        cellPx: Float = 50f,
+        cellPx: Float = 10f,
         yAxisFlip: Boolean = false,
-        onPreview: (Bitmap) -> Unit
+        onPreview: (android.graphics.Bitmap) -> Unit
     ) {
         val imageUrl = itMapInfo?.imageUrl ?: run {
             LogUtil.e("Map: empty imageUrl"); return
         }
-        if (lastMapUrl != imageUrl) {
-            lastMapUrl = imageUrl
-            needReloadMap = true
-        }
-        if (needReloadMap) {
-            /* --- 1) 下载原图文件(不解码) --- */
-            val tempImageFile = BitmapUtil.downloadToFile(context, imageUrl) ?: run {
-                LogUtil.e("Map download failed → $imageUrl")
-                return
-            }
-            imgFile = tempImageFile
-            imgFile?.let {
-                /* --- 2) 首屏快速预览(低清) --- */
-                BitmapUtil.decodePreview(
-                    it,
-                    backgroundColor = context.getColor(R.color.color_map_base)
-                ).also(onPreview)
-                val size = BitmapUtil.readImageSize(it)
-                lastActualW = size.first
-                lastActualH = size.second
-            }
-        }
 
-        /* --- 3) 读取原图实际尺寸,计算缩放比(XY 分离) --- */
-        val backendW = itMapInfo.width?.toFloat()
-        val backendH = itMapInfo.height?.toFloat()
-        if (backendW == null || backendH == null || backendW <= 0f || backendH <= 0f) {
-            LogUtil.e("Map: backend width/height invalid → $backendW x $backendH")
+        // ===== 1) 下载原图文件(不解码 or 仅预览) =====
+        val tempImageFile = BitmapUtil.downloadToFile(context, imageUrl) ?: run {
+            LogUtil.e("Map download failed → $imageUrl")
             return
         }
-        val ratioX = lastActualW / backendW
-        val ratioY = lastActualH / backendH
-        // 若你在别处仍使用 mapRatio,保留各自独立更安全
-        mapRatio = ratioX
 
-        /* --- 4) 计算点位:后端给的是格左上角 → 我们改为“格中心”坐标,再同步到实际位图坐标 --- */
+        // 首屏快速预览(低清),仅用于先显示个底色/缩略
+        BitmapUtil.decodePreview(
+            tempImageFile,
+            backgroundColor = context.getColor(R.color.color_map_base)
+        ).also(onPreview)
+
+        // ===== 2) 读取后端坐标系尺寸 =====
+        val backendW = itMapInfo.width?.toFloat() ?: return
+        val backendH = itMapInfo.height?.toFloat() ?: return
+
+
+        // ===== 3) 计算点位(全部落在“后端坐标系”里)=====
         val offX = (itMapInfo.x ?: "0").toFloat()
         val offY = (itMapInfo.y ?: "0").toFloat()
 
-        // 用局部 list 构建,避免并发修改
         val points = mutableListOf<CustomSwitchStationLayer.IsolationPoint>()
-
-        // 方便异步 icon 回填,避免 “mStationList.last()” 竞态:用 id → point 的索引
         val byId = mutableMapOf<Long, CustomSwitchStationLayer.IsolationPoint>()
 
-        itMapInfo.pointList
+        itMotorMapInfo?.data
             ?.asSequence()
-            ?.filter { it.x != null && it.y != null && it.entityId != null }
+            ?.filter { it.x != null && it.y != null && it.motorId != null }
             ?.forEach { pt ->
-                // 1) 后端格坐标 → 全局像素(取格中心:+0.5f)
+                // 每个点:
                 val gridX = (pt.x!!.toFloat() + 0.5f) * cellPx
                 val gridY = (pt.y!!.toFloat() + 0.5f) * cellPx
 
+                // **修正:用 ‘加’ 把子图局部坐标搬到大图坐标系**
+                var worldX = gridX + offX
+                var worldY = gridY + offY
+
+                // 如果后端以左下为原点,需要翻 Y(基于“世界高度”翻)
+                if (yAxisFlip) {
+                    worldY = backendH - worldY
+                }
+
                 // 2) 去掉子图偏移(后端通常给“子图左上在大图中的偏移”)
                 var localX = gridX - offX
                 var localY = gridY - offY
 
-                // 3) 如果后端以左下为原点,需要翻转 Y(可按需启用)
+                // 3) 若后端以左下为原点,需要翻转 Y(以后端子图高度为参考
                 if (yAxisFlip) {
-                    // 以子图高度为基准翻转;这里使用 backendH 的子区高度,若有“子图高”字段可替换
-                    localY = (backendH - localY)
+                    worldY = backendH - worldY
                 }
 
-                // 4) 缩放到真实位图坐标(注意 X / Y 独立比例
-                val fX = localX * ratioX
-                val fY = localY * ratioY
+                // 4) 直接作为最终坐标(不要乘 ratioX/ratioY
+                val fX = localX
+                val fY = localY
 
-                val switchStatus = if (pt.switchStatus == "1")
-                    CustomSwitchStationLayer.STATUS_ON
-                else
-                    CustomSwitchStationLayer.STATUS_OFF
+                val switchStatus =
+                    if (pt.switchStatus == "1") CustomSwitchStationLayer.STATUS_ON
+                    else CustomSwitchStationLayer.STATUS_OFF
 
                 val p = CustomSwitchStationLayer.IsolationPoint(
-                    pos = PointF(fX, fY),               // 约定:这里保存“中心点”坐标
-                    entityName = pt.entityName.orEmpty(),
-                    icon = null,                        // icon 异步回填
-                    entityId = pt.entityId!!,
+                    pos = android.graphics.PointF(worldX, worldY),
+                    motorCode = pt.motorCode,
+                    pointName = pt.pointName,
+                    motorName = pt.motorName,
+                    motorType = pt.motorType,
+                    pointId = pt.pointId,
+                    icon = null,
+                    entityId = pt.motorId!!,
                     pointSerialNumber = pt.pointSerialNumber,
                     isSelected = false,
                     pointNfc = pt.pointNfc ?: "",
@@ -163,13 +160,13 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
                 points += p
                 byId[p.entityId] = p
 
-                // 5) 异步加载小图标(拿到后刷新该点附近,避免整层重绘开销
+                // 5) 异步加载小图标(有则回填,并做局部刷新
                 val url = pt.pointIcon
                 if (!url.isNullOrBlank()) {
                     BitmapUtil.loadBitmapSmall(
                         ctx = context,
                         url = url,
-                        reqW = 64,   // 可按需调
+                        reqW = 64,
                         reqH = 64
                     ) { bmp ->
                         val target = byId[p.entityId] ?: return@loadBitmapSmall
@@ -180,7 +177,7 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
                 }
             }
 
-        /* --- 5) 把计算好的列表交给点位层 --- */
+        // 6) 提交点位(保持选中可选)
         stationLayer?.submitPoints(points)
 
         /* --- 6) 用 RegionTileLayer 替换旧底图(内部自带 LruCache / 512 tile) --- */

+ 200 - 285
app/src/main/java/com/grkj/iscs_mars/view/widget/CustomSwitchStationLayer.kt

@@ -1,27 +1,22 @@
 package com.grkj.iscs_mars.view.widget
 
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Matrix
-import android.graphics.Paint
-import android.graphics.PointF
+import android.graphics.*
 import android.os.SystemClock
 import android.view.MotionEvent
 import androidx.core.content.ContextCompat
 import com.grkj.iscs_mars.MyApplication
 import com.grkj.iscs_mars.R
 import com.grkj.iscs_mars.modbus.DockBean
-import com.grkj.iscs_mars.modbus.ModBusController
-import com.grkj.iscs_mars.util.log.LogUtil
 import com.onlylemi.mapview.library.MapView
 import com.onlylemi.mapview.library.layer.MapBaseLayer
-import com.sik.sikcore.SIKCore
 import kotlin.math.abs
 import kotlin.math.sin
 
 /**
- * 自定义开关层级(按点位缓存状态版本,变了才刷新)
+ * 自定义开关层(保持对外 API 兼容):
+ * - 点位坐标恒定使用“后端坐标系”(0..backendW, 0..backendH)
+ * - 点位尺寸:以 BASE_SWITCH_SIZE 为基准,受 setRatio() 与 currentZoom 共同影响
+ * - 文本:根据圆直径自适应,左右各保留 ~3px
  */
 class CustomSwitchStationLayer @JvmOverloads constructor(
     mapView: MapView?,
@@ -30,14 +25,18 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
 
     // ===== 数据结构 =====
     data class IsolationPoint(
-        var pos: PointF,                // 可能是中心 or 左上角
-        var entityName: String,
+        var pos: PointF,                // 后端坐标系的“中心点”
+        var motorCode: String?,
+        var pointName: String?,
+        var motorName: String?,
+        var motorType: String?,
+        var pointId: Long?,
         var icon: Bitmap?,
         val entityId: Long,
-        var pointSerialNumber: String?, // 仅用于和 switch.idx 对表
+        var pointSerialNumber: String?,
         var isSelected: Boolean,
         var pointNfc: String?,
-        var status: Int = STATUS_UNKNOWN // 见常量
+        var status: Int = STATUS_UNKNOWN
     )
 
     companion object {
@@ -47,32 +46,20 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         const val STATUS_UNKNOWN = 3
     }
 
-    // ===== 配置开关:你的 pos 是否已经是中心点? =====
-    private val POS_IS_CENTER = true // 若上游 pos 就是中心,设 true;若是左上角,改 false
+    // ===== 配置:你的 pos 是否已经是中心点? =====
+    private val POS_IS_CENTER = true
 
-    // ===== 同步&选择 =====
+    // ===== 同步 & 选择 =====
     private val dataLock = Any()
 
-    @Volatile
-    private var selectedEntityIdForKeep: Long? = null
+    @Volatile private var selectedEntityIdForKeep: Long? = null
+    @Volatile private var selectedNameForKeep: String? = null
 
-    @Volatile
-    private var selectedNameForKeep: String? = null
-
-    // ===== switch 数据快照(仅存原始列表,不在 draw 里查表)=====
-    @Volatile
-    private var hasAlarmFlag: Boolean = false
-
-    // token 防过期回调
-    @Volatile
-    private var selectToken: Long = 0L
-    private inline fun withLiveToken(token: Long, block: () -> Unit) {
-        if (token == selectToken) block()
-    }
+    @Volatile private var selectToken: Long = 0L
+    private inline fun withLiveToken(token: Long, block: () -> Unit) { if (token == selectToken) block() }
 
     // ===== 动画参数 =====
-    @Volatile
-    var inDraw: Boolean = false
+    @Volatile var inDraw: Boolean = false
         private set
 
     private val breathePeriod = 1200f
@@ -94,16 +81,19 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         }
     }
 
+    // 点击/缩放动画
     private val longPressCenterDurationMs = 280L
     private val clickZoomDurationMs = 220L
     private val clickCenterDurationMs = 220L
     private val desiredSelectScale = 2.0f
 
-    // 尺寸
+    // ===== 尺寸(与原版兼容)=====
+    private val BASE_SWITCH_SIZE = 16f
+    private val BASE_TEXT_SIZE = 12f
+
+    // 仍然保留 setRatio 语义
     private var ratio: Float = 1f
-    private val BASE_SWITCH_SIZE = 18f
-    private val BASE_TEXT_SIZE = 14f
-    private var switchSize: Float = BASE_SWITCH_SIZE
+    private var switchSize: Float = BASE_SWITCH_SIZE   // 仅作为屏幕像素基准(dp->px后)
     private var textSize: Float = BASE_TEXT_SIZE
 
     // 绘制状态
@@ -116,30 +106,13 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
     private val appCtx get() = MyApplication.instance!!.applicationContext
     private val colOn by lazy { ContextCompat.getColor(appCtx, R.color.common_switch_enable) }
     private val colOff by lazy { ContextCompat.getColor(appCtx, R.color.common_switch_disable) }
-    private val colRed by lazy {
-        runCatching {
-            ContextCompat.getColor(
-                appCtx,
-                R.color.red_500
-            )
-        }.getOrDefault(Color.parseColor("#EF4444"))
-    }
-    private val colOrange by lazy {
-        runCatching {
-            ContextCompat.getColor(
-                appCtx,
-                R.color.orange_500
-            )
-        }.getOrDefault(Color.parseColor("#F97316"))
-    }
+    private val colRed by lazy { runCatching { ContextCompat.getColor(appCtx, R.color.red_500) }.getOrDefault(Color.parseColor("#EF4444")) }
+    private val colOrange by lazy { runCatching { ContextCompat.getColor(appCtx, R.color.orange_500) }.getOrDefault(Color.parseColor("#F97316")) }
     private val colSelectRing = Color.argb(180, 66, 133, 244)
 
     // 长按探测
-    private val longPressTimeoutMs: Long =
-        android.view.ViewConfiguration.getLongPressTimeout().toLong()
-    private val touchSlop: Float = android.view.ViewConfiguration.get(
-        mapView?.context ?: SIKCore.getApplication()
-    ).scaledTouchSlop * 1.5f
+    private val longPressTimeoutMs: Long = android.view.ViewConfiguration.getLongPressTimeout().toLong()
+    private val touchSlop: Float = android.view.ViewConfiguration.get(mapView?.context ?: appCtx).scaledTouchSlop * 1.5f
     private val touchSlopSq: Float = touchSlop * touchSlop
     private var pendingLongPress = false
     private var longPressTriggered = false
@@ -157,163 +130,93 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
             val c = centerOf(p)
             mapView?.postDelayed({
                 val screen = mapToScreen(c.x, c.y)
-                onLongPressListener?.invoke(p, screen.x, screen.y - switchSize / 2 - 4, c.x, c.y)
+                onLongPressListener?.invoke(p, screen.x, screen.y - getSwitchR() / 2f - 4, c.x, c.y)
             }, longPressCenterDurationMs)
         }
     }
 
-    var onLongPressListener: ((point: IsolationPoint, screenX: Float, screenY: Float, mapX: Float, mapY: Float) -> Unit)? =
-        null
+    // ===== 兼容原版:对外回调 =====
+    var onLongPressListener: ((point: IsolationPoint, screenX: Float, screenY: Float, mapX: Float, mapY: Float) -> Unit)? = null
 
     init {
         paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL_AND_STROKE }
-        setRatio(1f)
+        setRatio(1f) // 初始化
         startAnimation()
     }
 
-    // ================= 外部接口 =================
+    // ================= 外部接口(保持兼容) =================
 
-    /** 点位列表:深拷贝 pos;可选保留当前选中(按 entityId→name) */
-    // ==== 精准 Diff 的 submitPoints ====
+    /** 点位列表:精准 Diff(坐标=后端坐标,不乘比例);可选保留当前选中 */
     fun submitPoints(points: List<IsolationPoint>, keepSelection: Boolean = true) {
-        // 快照避免外部列表后续被改动
         val incoming = if (points === stationList) points.map { deepCopyItem(it) } else points
 
         val keepEid = if (keepSelection) selectedEntityIdForKeep else null
         val keepName = if (keepSelection) selectedNameForKeep else null
 
-        // 记录变化以做局部刷新
         val changedCenters = ArrayList<PointF>(8)
-        val changedRadiusPx = 72 // 以图标 ~64px 给点冗余
+        val changedRadiusPx = 84
 
         synchronized(dataLock) {
-            // 1) 建索引(现有 & 新数据)
             val oldById = HashMap<Long, IsolationPoint>(stationList.size)
             stationList.forEach { oldById[it.entityId] = it }
 
             val seenIds = HashSet<Long>(incoming.size)
             val nextList = ArrayList<IsolationPoint>(incoming.size)
 
-            // 2) 逐项处理:更新/新增
             for (src in incoming) {
                 val id = src.entityId
                 seenIds.add(id)
                 val dst = oldById[id]
 
                 if (dst == null) {
-                    // 新增:深拷贝一次,避免后续被外部改动影响
                     val add = deepCopyItem(src).apply {
                         isSelected = when {
                             !keepSelection -> false
                             keepEid != null -> entityId == keepEid
-                            keepName != null -> entityName == keepName
+                            keepName != null -> motorCode == keepName
                             else -> false
                         }
                     }
                     nextList.add(add)
                     changedCenters.add(add.pos)
                 } else {
-                    // 更新:字段级 copy,尽量复用对象引用,避免动画/缓存抖动
                     var dirty = false
+                    if (!dst.pos.approxEq(src.pos)) { dst.pos = PointF(src.pos.x, src.pos.y); dirty = true }
+                    if (dst.status != src.status) { dst.status = src.status; dirty = true }
+                    if (dst.motorCode != src.motorCode) { dst.motorCode = src.motorCode; dirty = true }
+                    if (dst.pointNfc != src.pointNfc) { dst.pointNfc = src.pointNfc; dirty = true }
+                    if (dst.pointSerialNumber != src.pointSerialNumber) { dst.pointSerialNumber = src.pointSerialNumber; dirty = true }
+                    if (src.icon != null && dst.icon !== src.icon) { dst.icon = src.icon; dirty = true }
 
-                    if (!dst.pos.approxEq(src.pos)) {
-                        dst.pos = PointF(src.pos.x, src.pos.y) // 新对象,避免外部持有同一引用
-                        dirty = true
-                    }
-                    if (dst.status != src.status) {
-                        dst.status = src.status; dirty = true
-                    }
-                    if (dst.entityName != src.entityName) {
-                        dst.entityName = src.entityName; dirty = true
-                    }
-                    if (dst.pointNfc != src.pointNfc) {
-                        dst.pointNfc = src.pointNfc; dirty = true
-                    }
-                    if (dst.pointSerialNumber != src.pointSerialNumber) {
-                        dst.pointSerialNumber = src.pointSerialNumber; dirty = true
-                    }
-                    // icon:如果外部异步回填,这里一般不动;若你也带了新 icon:
-                    if (src.icon != null && dst.icon !== src.icon) {
-                        dst.icon = src.icon; dirty = true
-                    }
-
-                    // 选中保持 or 重置
                     dst.isSelected = when {
                         !keepSelection -> false
                         keepEid != null -> dst.entityId == keepEid
-                        keepName != null -> dst.entityName == keepName
+                        keepName != null -> dst.motorCode == keepName
                         else -> false
                     }
-
                     if (dirty) changedCenters.add(dst.pos)
-                    nextList.add(dst) // 复用原对象引用
+                    nextList.add(dst)
                 }
             }
 
-            // 3) 删除:老数据里有、新数据里没有的
-            if (seenIds.size != oldById.size) {
-                // 直接按 nextList(新顺序)重建 stationList 更简单干净
-                // 被删除的对象引用不再放入 nextList 即可
-            }
-
-            // 4) 最小代价重建列表(保持新顺序,但复用对象)
             stationList.clear()
             stationList.addAll(nextList)
         }
 
-        // 5) 局部刷新(尽可能小范围)
-        if (changedCenters.isNotEmpty()) {
-            refreshRegionsOrThrottle(changedCenters, changedRadiusPx)
-        } else {
-            // 没有变化也触发一次轻微节流刷新,避免偶发不同步
-            throttleInvalidate()
-        }
-    }
-
-// ==== 辅助方法 ====
-
-    // PointF 近似比较,避免浮点误差导致的伪变更
-    private fun PointF.approxEq(other: PointF, eps: Float = 0.1f): Boolean {
-        return kotlin.math.abs(x - other.x) <= eps && kotlin.math.abs(y - other.y) <= eps
+        if (changedCenters.isNotEmpty()) refreshRegionsOrThrottle(changedCenters, changedRadiusPx)
+        else throttleInvalidate()
     }
 
-    /**
-     * 优先做局部刷新;如果你的 MapView 没有暴露 invalidateRect,就回退到节流整层刷新。
-     * 这里假设 mapView/invalidateRect 可用;否则把反射去掉,直接 throttleInvalidate()
-     */
-    private fun refreshRegionsOrThrottle(centers: List<PointF>, radiusPx: Int) {
-        val mv = mapView ?: return throttleInvalidate()
-
-        var didPartial = false
-        runCatching {
-            val m = mv.javaClass.getMethod(
-                "invalidateRect", Int::class.java, Int::class.java, Int::class.java, Int::class.java
-            )
-            for (c in centers) {
-                val l = (c.x - radiusPx).toInt()
-                val t = (c.y - radiusPx).toInt()
-                val r = (c.x + radiusPx).toInt()
-                val b = (c.y + radiusPx).toInt()
-                m.invoke(mv, l, t, r, b)
-            }
-            didPartial = true
-        }.onFailure {
-            // ignore
-        }
-
-        if (!didPartial) throttleInvalidate()
-    }
-
-
-    /** 比例(基准) */
+    /** 比例(基准)。兼容原有 API:影响圆与文字的“基准尺寸”,不影响坐标 */
     fun setRatio(ratio: Float) {
-        this.ratio = ratio
-        switchSize = BASE_SWITCH_SIZE * ratio
-        textSize = BASE_TEXT_SIZE * ratio
+        this.ratio = ratio.coerceAtLeast(0.1f)
+        switchSize = BASE_SWITCH_SIZE * this.ratio     // 屏幕像素基准
+        textSize = BASE_TEXT_SIZE * this.ratio         // (文本的 min/max 可用它当基准去推)
         throttleInvalidate()
     }
 
-    /** 按唯一 name 选中并定位(若 name 唯一) */
+
+    /** 按 entityId 选中并定位(保留原 API) */
     fun selectPoint(
         entityId: Long?,
         animated: Boolean = true,
@@ -326,12 +229,8 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         val token = ++selectToken
 
         val eid = target.entityId
-        val name = target.entityName
-        synchronized(dataLock) {
-            stationList.forEach {
-                it.isSelected = it.entityId == entityId
-            }
-        }
+        val name = target.motorCode
+        synchronized(dataLock) { stationList.forEach { it.isSelected = it.entityId == entityId } }
         selectedEntityIdForKeep = eid
         selectedNameForKeep = name
         throttleInvalidate()
@@ -344,7 +243,7 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         return true
     }
 
-    /** 局部刷新(点在屏幕上时) */
+    /** 局部刷新(点在屏幕上时)——保留原 API */
     fun refreshIfVisible(point: PointF, margin: Float = 0f) {
         if (inDraw) return
         val w = mapView.width
@@ -352,8 +251,7 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         if (w == 0 || h == 0) return
         val pts = floatArrayOf(point.x, point.y)
         mapView.currentMatrix.mapPoints(pts)
-        val x = pts[0];
-        val y = pts[1]
+        val x = pts[0]; val y = pts[1]
         if (x + margin >= 0 && x - margin <= w && y + margin >= 0 && y - margin <= h) {
             throttleInvalidate()
         }
@@ -378,28 +276,20 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
                     }
                 }
             }
-
             MotionEvent.ACTION_MOVE -> {
                 lastScreenX = event.x; lastScreenY = event.y
                 if (pendingLongPress && !longPressTriggered) {
                     val dx = event.x - downScreenX
                     val dy = event.y - downScreenY
-                    if (dx * dx + dy * dy > touchSlopSq) {
+                    if (dx*dx + dy*dy > touchSlopSq) {
                         pendingLongPress = false
                         mapView.removeCallbacks(longPressRunnable)
                     }
                 }
             }
-
             MotionEvent.ACTION_UP -> {
                 mapView.removeCallbacks(longPressRunnable)
-                if (longPressTriggered) {
-                    pendingLongPress = false
-                    longPressTriggered = false
-                    currentPressed = null
-                    return
-                }
-
+                if (longPressTriggered) { pendingLongPress = false; longPressTriggered = false; currentPressed = null; return }
                 val mp = screenToMap(event.x, event.y) ?: return
                 val hit = hitTest(mp.x, mp.y) ?: return
                 val token = ++selectToken
@@ -407,36 +297,23 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
                 val targetScale = desiredSelectScale
                 val targetCenter = centerOf(hit)
                 val eid = hit.entityId
-                val name = hit.entityName
+                val name = hit.motorCode
 
-                // ① 先拉缩放(以当前中心为 pivot)
-                currentMapCenter()?.let { cur ->
-                    mapView?.animateCenterOnPoint(cur.x, cur.y, clickZoomDurationMs, targetScale)
-                }
-                // ② 再居中到目标(保持 targetScale)
+                currentMapCenter()?.let { cur -> mapView?.animateCenterOnPoint(cur.x, cur.y, clickZoomDurationMs, targetScale) }
                 mapView?.postDelayed({
                     withLiveToken(token) {
-                        mapView?.animateCenterOnPoint(
-                            targetCenter.x,
-                            targetCenter.y,
-                            clickCenterDurationMs,
-                            targetScale
-                        )
+                        mapView?.animateCenterOnPoint(targetCenter.x, targetCenter.y, clickCenterDurationMs, targetScale)
                     }
                 }, clickZoomDurationMs)
-                // ③ 完成后只选中目标
                 mapView?.postDelayed({
                     withLiveToken(token) {
-                        synchronized(dataLock) {
-                            stationList.replaceAll { it.copy(isSelected = (it.entityId == eid) || (it.entityName == name)) }
-                        }
+                        synchronized(dataLock) { stationList.replaceAll { it.copy(isSelected = (it.entityId == eid) || (it.motorCode == name)) } }
                         selectedEntityIdForKeep = eid
                         selectedNameForKeep = name
                         throttleInvalidate()
                     }
                 }, clickZoomDurationMs + clickCenterDurationMs)
             }
-
             MotionEvent.ACTION_CANCEL -> {
                 pendingLongPress = false
                 longPressTriggered = false
@@ -447,106 +324,109 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
     }
 
     // ================= 绘制 =================
-    override fun draw(
-        canvas: Canvas,
-        currentMatrix: Matrix,
-        currentZoom: Float,
-        currentRotateDegrees: Float
-    ) {
-        if (!isVisible) return
-        if (inDraw) return
+    override fun draw(canvas: Canvas, currentMatrix: Matrix, currentZoom: Float, currentRotateDegrees: Float) {
+        if (!isVisible || inDraw) return
         inDraw = true
-
         this.currentZoom = currentZoom
-        this.currentDegree = 360 - currentRotateDegrees
 
         try {
             canvas.save()
-            try {
-                canvas.concat(currentMatrix)
-
-                val points = synchronized(dataLock) { stationList.toList() }
-                val switches = runCatching { ModBusController.getSwitchData() }.getOrNull()
-                for (p in points) {
-                    val c = centerOf(p)
-
-                    // 选中外环
-                    if (p.isSelected) {
-                        paint.style = Paint.Style.STROKE
-                        paint.strokeWidth = 4f
-                        paint.color = colSelectRing
-                        canvas.drawCircle(c.x, c.y, switchSize * 1.5f, paint)
-                    }
-
-                    // 主体:直接用 p.status(由 setSwitchData 决定)
-                    paint.style = Paint.Style.FILL_AND_STROKE
-                    var status =
-                        p.pointSerialNumber?.let { parseStatus(switches?.find { it.idx.toString() == p.pointSerialNumber }) }
-                            ?: p.status
-//                    if (p.entityName=="E-45"){
-//                        status = STATUS_ALARM
-//                    }
-//                    val status = Random.nextInt(3)
-                    when (status) {
-                        STATUS_ON -> {
-                            paint.color = colOn; paint.alpha = 255
-                            canvas.drawCircle(c.x, c.y, switchSize, paint)
-                        }
-
-                        STATUS_OFF -> {
-                            paint.color = colOff; paint.alpha = 255
-                            canvas.drawCircle(c.x, c.y, switchSize, paint)
-                        }
-
-                        STATUS_ALARM -> {
-                            val t = pulsePhase
-                            val color = if (t < 0.5f) colRed else colOrange
-                            val a = (160 + 95 * abs(sin(2f * Math.PI.toFloat() * t))).toInt()
-                            paint.color = color; paint.alpha = a
-                            val r = switchSize * (1.0f + 0.10f * sin(2f * Math.PI.toFloat() * t))
-                            canvas.drawCircle(c.x, c.y, r, paint)
-                            paint.alpha = (a * 0.35f).toInt()
-                            canvas.drawCircle(c.x, c.y, r * 1.25f, paint)
-                            paint.alpha = 255
-                        }
+            canvas.concat(currentMatrix)  // 地图照常缩放/旋转
+
+            val points = synchronized(dataLock) { stationList.toList() }
+
+            // 屏幕上希望看到的半径/描边/文字大小(像素):
+            val screenR = switchSize                   // e.g. 30px
+            val screenStroke = 4f
+            val screenTextMin = 8f
+            val screenTextMax = 22f
+
+            // 由于 canvas 已被 currentMatrix 放大了 zoom 倍,
+            // 画到“地图坐标”层面的尺寸需要 /zoom 才能抵消缩放,获得恒定屏幕像素。
+            val mapR = screenR / currentZoom
+            val mapStroke = screenStroke / currentZoom
+
+            for (p in points) {
+                val c = centerOf(p)
+
+                // 选中环
+                if (p.isSelected) {
+                    paint.style = Paint.Style.STROKE
+                    paint.strokeWidth = mapStroke
+                    paint.color = colSelectRing
+                    canvas.drawCircle(c.x, c.y, mapR, paint)
+                }
 
-                        else -> { // STATUS_UNKNOWN
-                            paint.color = Color.DKGRAY; paint.alpha = 200
-                            canvas.drawCircle(c.x, c.y, switchSize, paint)
-                            paint.alpha = 255
-                        }
+                // 主体圆
+                paint.style = Paint.Style.FILL_AND_STROKE
+                when (p.status) {
+                    STATUS_ON -> { paint.color = colOn; paint.alpha = 255; canvas.drawCircle(c.x, c.y, mapR, paint) }
+                    STATUS_OFF -> { paint.color = colOff; paint.alpha = 255; canvas.drawCircle(c.x, c.y, mapR, paint) }
+                    STATUS_ALARM -> {
+                        val t = pulsePhase
+                        val color = if (t < 0.5f) colRed else colOrange
+                        val a = (160 + 95 * kotlin.math.abs(kotlin.math.sin(2f * Math.PI.toFloat() * t))).toInt()
+                        paint.color = color; paint.alpha = a
+                        val r = mapR * (1.0f + 0.10f * kotlin.math.sin(2f * Math.PI.toFloat() * t))
+                        canvas.drawCircle(c.x, c.y, r, paint)
+                        paint.alpha = (a * 0.35f).toInt()
+                        canvas.drawCircle(c.x, c.y, r * 1.25f, paint)
+                        paint.alpha = 255
                     }
+                    else -> { paint.color = Color.DKGRAY; paint.alpha = 200; canvas.drawCircle(c.x, c.y, mapR, paint); paint.alpha = 255 }
+                }
 
-                    // 文本
-                    if (currentZoom >= TEXT_VISIBLE_ZOOM_THRESHOLD) {
-                        paint.style = Paint.Style.FILL
-                        paint.strokeWidth = 1f
-                        paint.color = Color.WHITE
-                        paint.textSize = textSize
-                        val text = p.entityName
-                        val textW = paint.measureText(text)
-                        val fm = paint.fontMetrics
-                        val textH = fm.bottom - fm.top
-                        canvas.drawText(
-                            text,
-                            c.x - textW / 2f,
-                            c.y + (textH / 2 - fm.bottom),
-                            paint
-                        )
-                    }
+                // 文本:先算希望的屏幕宽度,再 /zoom 得到地图层文字 size
+                val text = p.motorCode ?: ""
+                if (text.isNotEmpty()) {
+                    paint.style = Paint.Style.FILL
+                    paint.strokeWidth = mapStroke
+                    paint.color = Color.WHITE
+
+                    val maxTextWidthScreen = (screenR * 2f) - 6f  // 屏幕像素
+                    // 先在“屏幕像素语义”下求字号
+                    val screenTextSize = fitTextSizeScreen(paint, text, maxTextWidthScreen, screenTextMin, screenTextMax)
+                    val mapTextSize = screenTextSize / currentZoom
+
+                    paint.textSize = mapTextSize
+                    val textW = paint.measureText(text)
+                    val fm = paint.fontMetrics
+                    val textH = fm.bottom - fm.top
+                    canvas.drawText(text, c.x - textW / 2f, c.y + (textH / 2 - fm.bottom), paint)
                 }
-            } finally {
-                canvas.restore()
             }
         } finally {
+            canvas.restore()
             inDraw = false
         }
     }
 
+    private fun fitTextSizeScreen(p: Paint, text: String, maxWidthScreenPx: Float, minSp: Float, maxSp: Float): Float {
+        if (text.isEmpty()) return minSp
+        // 在“屏幕像素语义”下先估字号,再回到 draw() 里除以 zoom 赋给 paint.textSize
+        p.textSize = maxSp
+        var w = p.measureText(text)
+        if (w <= maxWidthScreenPx) return maxSp
+        val est = (maxWidthScreenPx / w) * maxSp
+        val clamped = est.coerceIn(minSp, maxSp)
+        p.textSize = clamped
+        w = p.measureText(text)
+        if (w > maxWidthScreenPx) return (clamped * 0.95f).coerceAtLeast(minSp)
+        return clamped
+    }
+
+
+
     // ================= 工具 =================
+    private fun getSwitchR(): Float {
+        // 保持原有 ratio 语义 + 轻微随 zoom 增益
+        val zoomScale = 0.9f + 0.1f * currentZoom.coerceIn(1f, 2.5f)
+        return switchSize * zoomScale // switchSize = BASE_SWITCH_SIZE * ratio
+    }
+
     private fun centerOf(p: IsolationPoint): PointF {
         return if (POS_IS_CENTER) PointF(p.pos.x, p.pos.y)
-        else PointF(p.pos.x + switchSize / 2f, p.pos.y + switchSize / 2f)
+        else PointF(p.pos.x + getSwitchR() / 2f, p.pos.y + getSwitchR() / 2f)
     }
 
     private fun screenToMap(x: Float, y: Float): PointF? {
@@ -573,8 +453,10 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
     }
 
     private fun hitTest(mapX: Float, mapY: Float): IsolationPoint? {
-        val hitR = switchSize * 1.2f
+        val screenR = switchSize * 1.2f
+        val hitR = screenR / currentZoom        // 转回地图坐标
         val hitR2 = hitR * hitR
+
         val snapshot = synchronized(dataLock) { stationList.toList() }
         var hit: IsolationPoint? = null
         var bestD2 = Float.MAX_VALUE
@@ -582,25 +464,34 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
             val c = centerOf(p)
             val dx = mapX - c.x
             val dy = mapY - c.y
-            val d2 = dx * dx + dy * dy
-            if (d2 <= hitR2 && d2 < bestD2) {
-                bestD2 = d2
-                hit = p
-            }
+            val d2 = dx*dx + dy*dy
+            if (d2 <= hitR2 && d2 < bestD2) { bestD2 = d2; hit = p }
         }
         return hit
     }
 
-    private fun parseStatus(bean: DockBean.SwitchBean?): Int {
-        if (bean == null) return STATUS_UNKNOWN
-        if (isAlarm(bean)) return STATUS_ALARM
-        return if (bean.enabled) STATUS_ON else STATUS_OFF
+
+    private fun PointF.approxEq(other: PointF, eps: Float = 0.1f): Boolean {
+        return kotlin.math.abs(x - other.x) <= eps && kotlin.math.abs(y - other.y) <= eps
     }
 
-    /** 告警判断:按你业务改 */
-    private fun isAlarm(bean: DockBean.SwitchBean?): Boolean {
-        // TODO: return bean?.type == 2 || bean?.statusCode == ALARM
-        return false
+    /** 优先做局部刷新;MapView 若无 invalidateRect 则回退整层刷新 */
+    private fun refreshRegionsOrThrottle(centers: List<PointF>, radiusPx: Int) {
+        val mv = mapView ?: return throttleInvalidate()
+        var didPartial = false
+        runCatching {
+            val m = mv.javaClass.getMethod("invalidateRect", Int::class.java, Int::class.java, Int::class.java, Int::class.java)
+            for (c in centers) {
+                val l = (c.x - radiusPx).toInt()
+                val t = (c.y - radiusPx).toInt()
+                val r = (c.x + radiusPx).toInt()
+                val b = (c.y + radiusPx).toInt()
+                m.invoke(mv, l, t, r, b)
+            }
+            didPartial = true
+        }.onFailure { /* ignore */ }
+
+        if (!didPartial) throttleInvalidate()
     }
 
     private fun throttleInvalidate() {
@@ -618,6 +509,30 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
         mapView.removeCallbacks(refreshRunnable)
     }
 
+    // —— 与原版兼容的占位:如果你项目里还调用了它们 ——
+    private fun parseStatus(bean: DockBean.SwitchBean?): Int {
+        if (bean == null) return STATUS_UNKNOWN
+        if (isAlarm(bean)) return STATUS_ALARM
+        return if (bean.enabled) STATUS_ON else STATUS_OFF
+    }
+    private fun isAlarm(bean: DockBean.SwitchBean?): Boolean {
+        // TODO: 按业务判断
+        return false
+    }
+
     private fun deepCopyPoint(p: PointF) = PointF(p.x, p.y)
     private fun deepCopyItem(it: IsolationPoint) = it.copy(pos = deepCopyPoint(it.pos))
+
+    private fun fitTextSize(p: Paint, text: String, maxWidth: Float, minSp: Float = 8f, maxSp: Float = 22f): Float {
+        if (text.isEmpty()) return minSp
+        p.textSize = maxSp
+        var w = p.measureText(text)
+        if (w <= maxWidth) return maxSp
+        val est = (maxWidth / w) * maxSp
+        val clamped = est.coerceIn(minSp, maxSp)
+        p.textSize = clamped
+        w = p.measureText(text)
+        if (w > maxWidth) p.textSize = (clamped * 0.95f).coerceAtLeast(minSp)
+        return p.textSize
+    }
 }

+ 1 - 2
app/src/main/java/com/grkj/iscs_mars/view/widget/RegionTileLayer.kt

@@ -1,7 +1,6 @@
 package com.grkj.iscs_mars.view.widget
 
-import android.graphics.Canvas
-import android.graphics.Matrix
+import android.graphics.*
 import android.view.MotionEvent
 import com.onlylemi.mapview.library.MapView
 import com.onlylemi.mapview.library.layer.MapBaseLayer

+ 19 - 7
app/src/main/java/com/onlylemi/mapview/library/utils/MapUtils.java

@@ -2,8 +2,12 @@ package com.onlylemi.mapview.library.utils;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.DrawFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Picture;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 
@@ -321,14 +325,22 @@ public final class MapUtils {
      */
     public static Picture getPictureFromBitmap(Bitmap bitmap) {
         Picture picture = new Picture();
-        Canvas canvas = picture.beginRecording(bitmap.getWidth(),
-                bitmap.getHeight());
-        canvas.drawBitmap(
-                bitmap,
-                null,
-                new RectF(0f, 0f, (float) bitmap.getWidth(), (float) bitmap
-                        .getHeight()), null);
+        Canvas canvas = picture.beginRecording(bitmap.getWidth(), bitmap.getHeight());
+        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));
+
+        // 抗锯齿 + 位图滤波 + 抖动
+        final Paint paint = new Paint(
+                Paint.ANTI_ALIAS_FLAG
+                        | Paint.DITHER_FLAG
+                        | Paint.FILTER_BITMAP_FLAG
+        );
+
+        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        RectF dst = new RectF(0f, 0f, bitmap.getWidth(), bitmap.getHeight());
+        canvas.drawBitmap(bitmap, src, dst, paint);
+
         picture.endRecording();
         return picture;
     }
+
 }

+ 2 - 2
app/src/main/res/values-zh/strings.xml

@@ -150,7 +150,7 @@
     <string name="select_coloker_outside">选择共锁人(外部)</string>
     <string name="isolation_point">隔离点</string>
     <string name="effect">作用</string>
-    <string name="switch_status">开关状态</string>
+    <string name="switch_status">电机状态</string>
     <string name="lock_status">上锁状态</string>
     <string name="ready_to_colock">待共锁</string>
     <string name="colocked">已共锁</string>
@@ -410,6 +410,6 @@
     <string name="show_point_list">显示</string>
     <string name="hide_point_list">隐藏</string>
     <string name="switch_id">编号:%1$s</string>
-    <string name="switch_status_tv">开关状态:</string>
+    <string name="switch_status_tv">电机状态:</string>
     <string name="device_inputting">硬件录入中……</string>
 </resources>

+ 1 - 1
app/src/main/res/values/colors.xml

@@ -47,7 +47,7 @@
     <color name="common_switch_enable">#22C55E</color>
     <color name="common_switch_disable">#4B5563</color>
     <color name="color_d7d2d2">#d7d2d2</color>
-    <color name="color_map_base">#1d293d</color>
+    <color name="color_map_base">#666666</color>
     <color name="color_map_stroke">#45556c</color>
     <color name="dialogxColorBlue">#2196F3</color>
     <color name="common_transparent_half">#33ffffff</color>

+ 2 - 2
app/src/main/res/values/strings.xml

@@ -150,7 +150,7 @@
     <string name="select_coloker_outside">选择共锁人(外部)</string>
     <string name="isolation_point">隔离点</string>
     <string name="effect">作用</string>
-    <string name="switch_status">开关状态</string>
+    <string name="switch_status">电机状态</string>
     <string name="lock_status">上锁状态</string>
     <string name="ready_to_colock">待共锁</string>
     <string name="colocked">已共锁</string>
@@ -410,6 +410,6 @@
     <string name="show_point_list">显示</string>
     <string name="hide_point_list">隐藏</string>
     <string name="switch_id">编号:%1$s</string>
-    <string name="switch_status_tv">开关状态:</string>
+    <string name="switch_status_tv">电机状态:</string>
     <string name="device_inputting">硬件录入中……</string>
 </resources>