Thanks will look into those.
About kotlin, I’m using maplibre-compose-playground for now until an issue in ramani-maps is fixed.
and the filterLayersByDate
method it’s a Style
extension I’ve written based on the GL JS example.
import org.maplibre.android.style.expressions.Expression
import org.maplibre.android.style.layers.CircleLayer
import org.maplibre.android.style.layers.FillExtrusionLayer
import org.maplibre.android.style.layers.FillLayer
import org.maplibre.android.style.layers.HeatmapLayer
import org.maplibre.android.style.layers.Layer
import org.maplibre.android.style.layers.LineLayer
import org.maplibre.android.style.layers.SymbolLayer
import java.time.LocalDate
import java.time.temporal.ChronoUnit
import kotlin.math.floor
data class DateRange(
val startDecimalYear: Double,
val endDecimalYear: Double
) {
val startISODate: String
get() = decimalYearToISODate(startDecimalYear)
val endISODate: String
get() = decimalYearToISODate(endDecimalYear)
companion object {
fun fromDate(date: LocalDate): DateRange {
val year = date.year
val startOfYear = LocalDate.of(year, 1, 1)
val startOfNextYear = LocalDate.of(year + 1, 1, 1)
val daysInYear = ChronoUnit.DAYS.between(startOfYear, startOfNextYear)
val dayOfYear = ChronoUnit.DAYS.between(startOfYear, date)
val decimalYear = year + (dayOfYear.toDouble() / daysInYear)
return DateRange(decimalYear, decimalYear)
}
private fun decimalYearToISODate(decimalYear: Double): String {
return decimalYearToLocalDate(decimalYear).toString()
}
fun decimalYearToLocalDate(decimalYear: Double): LocalDate {
val year = floor(decimalYear).toInt()
val fractionOfYear = decimalYear - year
val startOfYear = LocalDate.of(year, 1, 1)
val startOfNextYear = LocalDate.of(year + 1, 1, 1)
val daysInYear = ChronoUnit.DAYS.between(startOfYear, startOfNextYear)
val dayOfYear = (fractionOfYear * daysInYear).toLong()
return startOfYear.plusDays(dayOfYear)
}
}
}
fun Style.filterLayersByDate(date: LocalDate) {
val dateRange = DateRange.fromDate(date)
for (layer in this.layers) {
when (layer) {
is LineLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
is FillLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
is CircleLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
is SymbolLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
is HeatmapLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
is FillExtrusionLayer -> {
if (!originalFilters.containsKey(layer.id)) {
originalFilters[layer.id] = layer.filter
}
layer.resetFilter()
layer.setFilter(constrainExpressionFilterByDateRange(originalFilters[layer.id], dateRange))
}
else -> null
}
}
}
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`(startISODateVariable)
)
),
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.startISODate),
Expression.let(
Expression.literal(endDecimalYearVariable), Expression.literal(dateRange.endDecimalYear),
Expression.let(
Expression.literal(endISODateVariable), Expression.literal(dateRange.endISODate),
finalExpression
)
)
)
)
}
fun Layer.resetFilter() {
originalFilters[this.id]?.let { originalFilter ->
when (this) {
is LineLayer -> setFilter(originalFilter)
is FillLayer -> setFilter(originalFilter)
is CircleLayer -> setFilter(originalFilter)
is SymbolLayer -> setFilter(originalFilter)
is HeatmapLayer -> setFilter(originalFilter)
is FillExtrusionLayer -> setFilter(originalFilter)
else -> {}
}
}
}