You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MapLibre Kotlin版日期过滤实现问题求助

问题:OpenHistoricalMap日期过滤迁移到Kotlin后失效

尝试将OpenHistoricalMap的MapLibre JS日期过滤功能迁移到Kotlin平台后,出现两种异常:要么过滤完全不生效,要么部分国家名称无法被移除。以下是问题代码:

@HiltViewModel
class MapViewModel @Inject constructor(
    private val settingsDataStore: SettingsDataStore
) : ViewModel() {

    val dateRange = settingsDataStore.dateRange

    fun filterLayersByDate(map: MapLibreMap, date: Date) {
        val dateRange = DateRange.fromDate(date)
        val style = map.style

        for (layer in style?.layers!!) {
            when (layer) {
                is LineLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                is FillLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                is CircleLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                is SymbolLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                is HeatmapLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                is FillExtrusionLayer -> {
                    layer.setFilter(
                        constrainExpressionFilterByDateRange(layer.filter, dateRange)
                    )
                }
                else -> {
                }
            }
        }
    }

    private fun constrainExpressionFilterByDateRange(
        filter: Expression? = null,
        dateRange: DateRange,
        variablePrefix: String = "maplibre_gl_dates"
    ): Expression {
        val startDecimalYearVariable = "${variablePrefix}__startDecimalYear"
        val startISODateVariable = "${variablePrefix}__startISODate"
        val endDecimalYearVariable = "${variablePrefix}__endDecimalYear"
        val endISODateVariable = "${variablePrefix}__endISODate"

        val dateConstraints = Expression.all(
            Expression.any(
                Expression.all(
                    Expression.has("start_decdate"),
                    Expression.lt(
                        Expression.get("start_decdate"),
                        Expression.`var`(endDecimalYearVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("start_decdate")),
                    Expression.has("start_date"),
                    Expression.lt(
                        Expression.get("start_date"),
                        Expression.`var`(endISODateVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("start_decdate")),
                    Expression.not(Expression.has("start_date"))
                )
            ),
            Expression.any(
                Expression.all(
                    Expression.has("end_decdate"),
                    Expression.gte(
                        Expression.get("end_decdate"),
                        Expression.`var`(startDecimalYearVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("end_decdate")),
                    Expression.has("end_date"),
                    Expression.gte(
                        Expression.get("end_date"),
                        Expression.`var`(startISODateVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("end_decdate")),
                    Expression.not(Expression.has("end_date"))
                )
            )
        )

        val finalExpression = if (filter != null) {
            Expression.all(dateConstraints, filter)
        } else {
            dateConstraints
        }

        return Expression.let(
            Expression.literal(startDecimalYearVariable), Expression.literal(dateRange.startDecimalYear),
            Expression.let(
                Expression.literal(startISODateVariable), Expression.literal(dateRange.startDecimalYear),
                Expression.let(
                    Expression.literal(endDecimalYearVariable), Expression.literal(dateRange.endDecimalYear),
                    Expression.let(
                        Expression.literal(endISODateVariable), Expression.literal(dateRange.endDecimalYear),
                        finalExpression
                    )
                )
            )
        )
    }

    fun updateDateRange(newDateRange: DateRange) {
        viewModelScope.launch {
            settingsDataStore.updateDateRange(newDateRange)
        }
    }
}

原版参考为OpenHistoricalMap官方的maplibre-gl-dates JS实现。


问题分析与修复

1. 核心错误:ISO日期变量赋值错误

在constrainExpressionFilterByDateRange方法中,startISODateVariable和endISODateVariable被错误地赋值为小数年,但实际上应该传入ISO格式的日期字符串(比如"2020-01-01"),这直接导致基于start_date/end_date的过滤逻辑完全失效。

2. 图层遍历的潜在问题

  • style?.layers!!使用非空断言存在空指针风险,建议先判空再处理
  • 遍历所有图层会对非OHM图层(比如底图)应用过滤,可能导致不必要的性能损耗或异常

修复后的代码

@HiltViewModel
class MapViewModel @Inject constructor(
    private val settingsDataStore: SettingsDataStore
) : ViewModel() {

    val dateRange = settingsDataStore.dateRange

    fun filterLayersByDate(map: MapLibreMap, date: Date) {
        val dateRange = DateRange.fromDate(date)
        val style = map.style ?: return // 空安全处理

        // 仅处理OHM矢量图层(可根据实际图层ID前缀进一步过滤,比如"ohm-")
        val targetLayers = style.layers.filter { layer ->
            layer.source?.let { sourceId ->
                style.getSource(sourceId) is VectorSource
            } == true
        }

        targetLayers.forEach { layer ->
            val filteredExpr = constrainExpressionFilterByDateRange(layer.filter, dateRange)
            when (layer) {
                is LineLayer -> layer.setFilter(filteredExpr)
                is FillLayer -> layer.setFilter(filteredExpr)
                is CircleLayer -> layer.setFilter(filteredExpr)
                is SymbolLayer -> layer.setFilter(filteredExpr)
                is HeatmapLayer -> layer.setFilter(filteredExpr)
                is FillExtrusionLayer -> layer.setFilter(filteredExpr)
            }
        }
    }

    private fun constrainExpressionFilterByDateRange(
        filter: Expression? = null,
        dateRange: DateRange,
        variablePrefix: String = "maplibre_gl_dates"
    ): Expression {
        val startDecimalYearVariable = "${variablePrefix}__startDecimalYear"
        val startISODateVariable = "${variablePrefix}__startISODate"
        val endDecimalYearVariable = "${variablePrefix}__endDecimalYear"
        val endISODateVariable = "${variablePrefix}__endISODate"

        // 确保传入ISO格式日期字符串(需根据DateRange实际实现调整属性名)
        val startISODate = dateRange.startISO // 示例值:"1900-01-01"
        val endISODate = dateRange.endISO // 示例值:"2000-12-31"

        val dateConstraints = Expression.all(
            Expression.any(
                Expression.all(
                    Expression.has("start_decdate"),
                    Expression.lt(
                        Expression.get("start_decdate"),
                        Expression.`var`(endDecimalYearVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("start_decdate")),
                    Expression.has("start_date"),
                    Expression.lt(
                        Expression.get("start_date"),
                        Expression.`var`(endISODateVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("start_decdate")),
                    Expression.not(Expression.has("start_date"))
                )
            ),
            Expression.any(
                Expression.all(
                    Expression.has("end_decdate"),
                    Expression.gte(
                        Expression.get("end_decdate"),
                        Expression.`var`(startDecimalYearVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("end_decdate")),
                    Expression.has("end_date"),
                    Expression.gte(
                        Expression.get("end_date"),
                        Expression.`var`(startISODateVariable)
                    )
                ),
                Expression.all(
                    Expression.not(Expression.has("end_decdate")),
                    Expression.not(Expression.has("end_date"))
                )
            )
        )

        val finalExpression = filter?.let { Expression.all(dateConstraints, it) } ?: dateConstraints

        // 修正变量赋值:ISO变量传入对应日期字符串
        return Expression.let(
            Expression.literal(startDecimalYearVariable), Expression.literal(dateRange.startDecimalYear),
            Expression.let(
                Expression.literal(startISODateVariable), Expression.literal(startISODate),
                Expression.let(
                    Expression.literal(endDecimalYearVariable), Expression.literal(dateRange.endDecimalYear),
                    Expression.let(
                        Expression.literal(endISODateVariable), Expression.literal(endISODate),
                        finalExpression
                    )
                )
            )
        )
    }

    fun updateDateRange(newDateRange: DateRange) {
        viewModelScope.launch {
            settingsDataStore.updateDateRange(newDateRange)
        }
    }
}

额外注意事项

  • 确保DateRange类包含正确的startISO和endISO属性,返回符合ISO 8601标准的日期字符串
  • 如果部分国家名称仍无法移除,检查对应图层的数据源是否包含end_decdate/end_date属性,确认过滤逻辑覆盖了所有日期字段情况
  • 可通过MapLibre开发者工具查看图层的过滤表达式是否正确生成,排查具体字段的匹配问题

内容的提问来源于stack exchange,提问作者Marcel Alexandru

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:42:33