Skip to content

Commit a84144f

Browse files
Render tiles inside viewport
1 parent 1699c1f commit a84144f

File tree

2 files changed

+133
-197
lines changed

2 files changed

+133
-197
lines changed

app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import f.cking.software.dpToPx
8888
import f.cking.software.frameRate
8989
import f.cking.software.mapParallel
9090
import f.cking.software.pxToDp
91+
import f.cking.software.splitToBatchesEqual
9192
import f.cking.software.toLocation
9293
import f.cking.software.topLeft
9394
import f.cking.software.ui.AsyncBatchProcessor
@@ -132,7 +133,6 @@ import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions
132133
import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme
133134
import timber.log.Timber
134135
import java.time.format.FormatStyle
135-
import java.util.concurrent.ConcurrentHashMap
136136

137137
@OptIn(ExperimentalMaterial3Api::class)
138138
object DeviceDetailsScreen {
@@ -905,12 +905,10 @@ object DeviceDetailsScreen {
905905

906906
fun updateViewport() {
907907
pendingViewport = getViewport()
908-
Timber.tag(TAG).d("Update viewport")
909908
}
910909

911910
fun commitViewPort() {
912911
committedViewport = pendingViewport
913-
Timber.tag(TAG).d("Commit viewport")
914912
}
915913

916914
MapView(
@@ -1013,7 +1011,7 @@ object DeviceDetailsScreen {
10131011
@Composable
10141012
private fun rememberTilesState() = remember { TilesState() }
10151013
private class TilesState {
1016-
var tiles = ConcurrentHashMap<Tile, TilesData>()
1014+
var tiles = HashMap<Tile, TilesData>()
10171015
var lastLocationsState: List<LocationModel> = emptyList()
10181016
}
10191017

@@ -1029,12 +1027,26 @@ object DeviceDetailsScreen {
10291027
withContext(Dispatchers.Default) {
10301028
val locations = mapUpdate.points.map { it.toLocation() }
10311029
Timber.tag(TAG).d("Heatmap points: ${locations.size}")
1030+
val pointsChanged = tilesState.lastLocationsState.size != locations.size
10321031
val tiles = HeatMapBitmapFactory.buildTilesWithRenderPaddingStable(locations, TILE_SIZE_METERS, PADDING_METERS)
1033-
.filter { viewport.intersects(it) }
1034-
Timber.tag(TAG).d("Heatmap tiles: ${tiles.size}")
1032+
.asSequence()
1033+
.filter {
1034+
val inViewport = viewport.intersects(it)
1035+
1036+
if (!pointsChanged) {
1037+
val existedTile = tilesState.tiles[it]
1038+
val isAdded = mapUpdate.map.overlays.contains(existedTile?.overlay)
1039+
inViewport && (!tilesState.tiles.containsKey(it) || !isAdded)
1040+
} else {
1041+
inViewport
1042+
}
1043+
}
1044+
.sortedBy { tilesState.tiles.containsKey(it) }
1045+
.toList()
10351046

1036-
if (tilesState.lastLocationsState != mapUpdate.points) {
1047+
Timber.tag(TAG).d("Heatmap tiles: ${tiles.size}")
10371048

1049+
if (pointsChanged) {
10381050
val removedTiles = tilesState.tiles.values.filter { !tiles.contains(it.tile) }
10391051
removedTiles.forEach { tileData ->
10401052
Timber.tag(TAG).d("Tile exists but should be removed")
@@ -1045,62 +1057,69 @@ object DeviceDetailsScreen {
10451057

10461058
tilesState.lastLocationsState = mapUpdate.points
10471059

1048-
val loadingJob = launch {
1060+
val loadingJob = launch(Dispatchers.Main) {
10491061
delay(30)
10501062
yield()
10511063
viewModel.loadingHeatmap = true
10521064
}
1053-
tiles.mapParallel { tile ->
1054-
val positionsForTile = locations
1055-
.mapNotNull {
1056-
it.takeIf { tile.contains(it, PADDING_METERS) }
1057-
?.let { HeatMapBitmapFactory.Position(it, PADDING_METERS.toFloat()) }
1058-
}
1059-
val existedTile = tilesState.tiles[tile]
1060-
1061-
if (existedTile != null && positionsForTile == existedTile.locations) {
1062-
// tile is already added and didn't change
1063-
Timber.tag(TAG).d("Tile already rendered")
1064-
withContext(Dispatchers.IO) {
1065-
if (!mapUpdate.map.overlays.contains(existedTile.overlay)) {
1066-
mapUpdate.map.overlays.add(0, existedTile.overlay)
1065+
1066+
tiles.splitToBatchesEqual(10).mapParallel { batch ->
1067+
batch.map { tile ->
1068+
withContext(Dispatchers.Default) {
1069+
1070+
val positionsForTile = locations
1071+
.mapNotNull {
1072+
it.takeIf { tile.contains(it, PADDING_METERS) }
1073+
?.let { HeatMapBitmapFactory.Position(it, PADDING_METERS.toFloat()) }
1074+
}
1075+
val existedTile = tilesState.tiles[tile]
1076+
1077+
if (existedTile != null && positionsForTile == existedTile.locations) {
1078+
// tile is already added and didn't change
1079+
Timber.tag(TAG).d("Tile already rendered")
1080+
withContext(Dispatchers.IO) {
1081+
if (!mapUpdate.map.overlays.contains(existedTile.overlay)) {
1082+
mapUpdate.map.overlays.add(0, existedTile.overlay)
1083+
mapUpdate.map.invalidate()
1084+
}
1085+
}
1086+
return@withContext
1087+
} else if (existedTile != null) {
1088+
// tile is rendered but changed (need to re-render)
1089+
Timber.tag(TAG).d("Tile exists but changed")
1090+
mapUpdate.map.overlays.remove(existedTile.overlay)
1091+
tilesState.tiles.remove(tile)
1092+
}
1093+
Timber.tag(TAG).d("Rendering tile with ${positionsForTile.size} points")
1094+
val bitmap = HeatMapBitmapFactory.generateTileGradientBitmapFastSeamless(
1095+
positionsAll = positionsForTile,
1096+
coreTile = tile,
1097+
widthPxCore = 300,
1098+
renderPaddingMeters = PADDING_METERS,
1099+
debugBorderPx = 0,
1100+
)
1101+
1102+
yield()
1103+
val heatmapOverlay = GroundOverlay()
1104+
heatmapOverlay.setImage(bitmap)
1105+
heatmapOverlay.transparency = 0.3f
1106+
heatmapOverlay.setPosition(tile.topLeft.toGeoPoint(), tile.bottomRight.toGeoPoint())
1107+
withContext(Dispatchers.Main) {
1108+
mapUpdate.map.overlays.add(0, heatmapOverlay)
10671109
mapUpdate.map.invalidate()
10681110
}
1111+
tilesState.tiles[tile] = TilesData(tile, heatmapOverlay, positionsForTile)
10691112
}
1070-
return@mapParallel
1071-
} else if (existedTile != null) {
1072-
// tile is rendered but changed (need to re-render)
1073-
Timber.tag(TAG).d("Tile exists but changed")
1074-
mapUpdate.map.overlays.remove(existedTile.overlay)
1075-
tilesState.tiles.remove(tile)
1076-
}
1077-
Timber.tag(TAG).d("Rendering tile with ${positionsForTile.size} points")
1078-
val bitmap = HeatMapBitmapFactory.generateTileGradientBitmapFastSeamless(
1079-
positionsAll = positionsForTile,
1080-
coreTile = tile,
1081-
widthPxCore = 300,
1082-
renderPaddingMeters = PADDING_METERS,
1083-
debugBorderPx = 0,
1084-
)
1085-
1086-
yield()
1087-
val heatmapOverlay = GroundOverlay()
1088-
heatmapOverlay.setImage(bitmap)
1089-
heatmapOverlay.setPosition(tile.topLeft.toGeoPoint(), tile.bottomRight.toGeoPoint())
1090-
withContext(Dispatchers.Main) {
1091-
mapUpdate.map.overlays.add(0, heatmapOverlay)
1092-
mapUpdate.map.invalidate()
10931113
}
1094-
tilesState.tiles[tile] = TilesData(tile, heatmapOverlay, positionsForTile)
10951114
}
10961115

10971116
Timber.tag(TAG).d("All tiles rendered")
10981117
loadingJob.cancel()
1099-
viewModel.loadingHeatmap = false
11001118
}
11011119
} else {
11021120
mapUpdate.map.overlays.removeAll { it is GroundOverlay }
11031121
}
1122+
viewModel.loadingHeatmap = false
11041123
mapUpdate.map.invalidate()
11051124
}
11061125

0 commit comments

Comments
 (0)