Skip to content
Merged
2 changes: 1 addition & 1 deletion android-for-arcgis
Submodule android-for-arcgis updated 27 files
+52 −2 api/android-for-arcgis.api
+232 −0 src/androidTest/java/com/mapconductor/arcgis/ArcGISGraphicsZIndexTest.kt
+121 −0 src/androidTest/java/com/mapconductor/arcgis/ArcGISGroundImageLayerThreadTest.kt
+144 −0 src/androidTest/java/com/mapconductor/arcgis/ArcGISOverlaySyncCancellationTest.kt
+106 −0 src/main/java/com/mapconductor/arcgis/ArcGISGraphicsZIndex.kt
+23 −9 src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt
+23 −9 src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt
+48 −8 src/main/java/com/mapconductor/arcgis/ArcGISMapView2DController.kt
+62 −22 src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt
+4 −7 src/main/java/com/mapconductor/arcgis/ArcGISMapViewStateImpl.kt
+82 −0 src/main/java/com/mapconductor/arcgis/ArcGISOverlayStacking.kt
+19 −0 src/main/java/com/mapconductor/arcgis/ArcGISOverlaySync.kt
+46 −0 src/main/java/com/mapconductor/arcgis/WebMercatorTileLevels.kt
+6 −1 src/main/java/com/mapconductor/arcgis/circle/ArcGISCircleOverlayController.kt
+8 −0 src/main/java/com/mapconductor/arcgis/circle/ArcGISCircleOverlayRenderer.kt
+6 −1 src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageController.kt
+34 −27 src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageOverlayRenderer.kt
+5 −1 src/main/java/com/mapconductor/arcgis/marker/ArcGISMarkerController.kt
+6 −0 src/main/java/com/mapconductor/arcgis/marker/ArcGISMarkerRenderer.kt
+6 −1 src/main/java/com/mapconductor/arcgis/polygon/ArcGISPolygonOverlayController.kt
+13 −18 src/main/java/com/mapconductor/arcgis/polygon/ArcGISPolygonOverlayRenderer.kt
+6 −1 src/main/java/com/mapconductor/arcgis/polyline/ArcGISPolylineOverlayController.kt
+15 −3 src/main/java/com/mapconductor/arcgis/polyline/ArcGISPolylineOverlayRenderer.kt
+5 −0 src/main/java/com/mapconductor/arcgis/raster/ArcGISRasterLayerController.kt
+24 −20 src/main/java/com/mapconductor/arcgis/raster/ArcGISRasterLayerOverlayRenderer.kt
+52 −0 src/test/java/com/mapconductor/arcgis/IdentityWeakTableTest.kt
+71 −0 src/test/java/com/mapconductor/arcgis/WebMercatorTileLevelsTest.kt
1 change: 1 addition & 0 deletions example-app/src/main/assets/boston/boston-buildings.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ import com.mapconductor.example.pages.marker.postofficecluster.MarkerClusterMapP
import com.mapconductor.example.pages.marker.streettree.StreetTreePage
import com.mapconductor.example.pages.offlinemap.OfflineMapPage
import com.mapconductor.example.pages.polygon.basic.PolygonMapPage
import com.mapconductor.example.pages.polygon.boston.BostonBuildingsPage
import com.mapconductor.example.pages.polygon.click.PolygonClickPage
import com.mapconductor.example.pages.polygon.collection.PolygonsMapPage
import com.mapconductor.example.pages.polygon.geodesic.PolygonGeodesicPage
import com.mapconductor.example.pages.polygon.hole.HolePolygonMapPage
import com.mapconductor.example.pages.polyline.PolylineClickMapPage
Expand Down Expand Up @@ -151,6 +153,8 @@ fun DemoAppScreen(initPage: String = "map") {
SidebarItem(id = "polygon-click", title = "Polygon Click"),
SidebarItem(id = "polygon-geodesic", title = "Geodesic Polygons"),
SidebarItem(id = "polygon-hole", title = "Hole Polygon"),
SidebarItem(id = "polygons", title = "Polygons"),
SidebarItem(id = "boston-buildings", title = "Boston Buildings"),
),
),
SidebarSection(
Expand Down Expand Up @@ -326,6 +330,16 @@ fun DemoAppScreen(initPage: String = "map") {
onToggleSidebar = navigationViewModel::toggleSidebar,
)
}
"polygons" -> {
PolygonsMapPage(
onToggleSidebar = navigationViewModel::toggleSidebar,
)
}
"boston-buildings" -> {
BostonBuildingsPage(
onToggleSidebar = navigationViewModel::toggleSidebar,
)
}
"groundImage" -> {
GroundImageMapPage(
groundImageResources = groundImageResources,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package com.mapconductor.example.pages.polygon.boston

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Button
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mapconductor.compose.info.InfoBubble
import com.mapconductor.compose.polygon.Polygons
import com.mapconductor.example.MapViewContainer
import com.mapconductor.example.ui.DefaultMapViewItems
import com.mapconductor.example.ui.DemoMapPageScaffold
import com.mapconductor.example.ui.MessageCard
import android.app.Activity

@Composable
fun BostonBuildingsPage(
modifier: Modifier = Modifier,
onToggleSidebar: () -> Unit = {},
) {
val context = LocalContext.current
// `adb shell am start ... --es camera "42.35545,-71.0605,17" --es area 2` opens
// a fixed view, so a measurement sees the same thing on every provider.
val viewModel =
remember {
val intent = (context as? Activity)?.intent
BostonBuildingsViewModel(
context = context.applicationContext,
initCamera = intent?.getStringExtra("camera"),
initArea = intent?.getStringExtra("area")?.toIntOrNull(),
initDataset =
when (intent?.getStringExtra("data")) {
"buildings" -> Dataset.CITY_BUILDINGS
"parcels" -> Dataset.CITY_PARCELS
else -> null
},
)
}

DemoMapPageScaffold(
menuItems = DefaultMapViewItems(viewModel.initCameraPosition),
onToggleSidebar = onToggleSidebar,
onMapViewStateChanged = viewModel::onMapViewChanged,
) { paddingValues ->
val mapViewState = viewModel.mapViewState.collectAsState()
val states = viewModel.polygonStates.collectAsState()
val area = viewModel.area.collectAsState()
val dataset = viewModel.dataset.collectAsState()
val loadError = viewModel.loadError.collectAsState()
val stats = viewModel.stats.collectAsState()
val attribution = viewModel.attribution.collectAsState()
val tap = viewModel.tap.collectAsState()
val progress = viewModel.progress.collectAsState()

// Two frames after the list reaches Polygons(): the polygons are registered with the map by then.
LaunchedEffect(states.value) {
val shown = states.value
withFrameNanos {}
withFrameNanos {}
viewModel.onDrawn(shown)
}

mapViewState.value?.let {
MapViewContainer(
state = it,
modifier = modifier.padding(paddingValues),
onMapClick = viewModel::onMapClick,
) {
Polygons(states.value)
val shownTap = tap.value
val building = shownTap?.building
val point = shownTap?.point
if (building != null && point != null) {
InfoBubble(position = point, borderWidth = 1.dp, borderColor = Color(0x33000000)) {
PolygonInfo(building)
}
}
}
}

progress.value?.let { LoadingOverlay(it, modifier = Modifier.padding(paddingValues)) }

Column(
modifier =
Modifier
.align(Alignment.TopStart)
.padding(top = paddingValues.calculateTopPadding() + 16.dp, start = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Dataset.entries.forEach { entry ->
if (entry == dataset.value) {
Button(onClick = {}) { Text(entry.label) }
} else {
FilledTonalButton(onClick = { viewModel.setDataset(entry) }) { Text(entry.label) }
}
}
}
if (dataset.value == Dataset.DOWNTOWN) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
AREAS.forEachIndexed { level, label ->
if (level == area.value) {
Button(onClick = {}) { Text(label) }
} else {
FilledTonalButton(onClick = { viewModel.setArea(level) }) { Text(label) }
}
}
}
}
}

MessageCard(
title =
stats.value?.let { "Boston ${dataset.value.label}: ${it.polygons} polygons, ${it.vertices} vertices" }
?: "Boston ${dataset.value.label}",
isCardOpen = true,
maxHeight = 260.dp,
modifier =
Modifier
.align(Alignment.BottomStart)
// On a tablet the card would otherwise run under the whole map.
.widthIn(max = PANEL_MAX_WIDTH)
.padding(
bottom = paddingValues.calculateBottomPadding() + 16.dp,
start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp,
end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp,
),
) {
loadError.value?.let { Text(it) }
if (dataset.value == Dataset.CITY_PARCELS) LandUseLegend(modifier = Modifier.padding(vertical = 4.dp))
stats.value?.let {
val frame = it.frameMillis?.let { ms -> ", next frame $ms ms" } ?: ""
Text("Load ${it.loadMillis} ms, states ${it.buildMillis} ms$frame")
}
tap.value?.let { tap ->
val building = tap.building
val use = building?.use
Text(
when {
building == null -> "No building here"
use != null -> "${building.name} · ${use.label}"
else -> {
val height = building.height?.let { "$it m" } ?: "height unknown"
"${building.name ?: "Building"} · $height · ${building.id.take(8)}"
}
},
)
}
Text(attribution.value, fontSize = 11.sp)
}
}
}

/** Wide enough for the title and two legend columns without wrapping. */
private val PANEL_MAX_WIDTH = 480.dp
Loading