@@ -43,13 +43,23 @@ import androidx.compose.ui.Alignment
4343import androidx.compose.ui.Modifier
4444import androidx.compose.ui.res.stringResource
4545import androidx.compose.ui.unit.dp
46+ import android.app.Activity
47+ import androidx.activity.result.contract.ActivityResultContracts
48+ import androidx.activity.compose.rememberLauncherForActivityResult
49+ import androidx.compose.material.icons.filled.Search
4650import com.example.advancedmaps3dsamples.R
4751import com.example.advancedmaps3dsamples.ui.theme.AdvancedMaps3DSamplesTheme
4852import com.example.advancedmaps3dsamples.utils.toValidCamera
53+ import com.google.android.gms.maps.model.LatLng
54+ import com.google.android.libraries.places.api.model.Place
55+ import com.google.android.libraries.places.widget.Autocomplete
56+ import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
4957import com.google.android.gms.maps3d.model.AltitudeMode
5058import com.google.android.gms.maps3d.model.Camera
59+ import com.google.android.gms.maps3d.model.camera
5160import com.google.android.gms.maps3d.model.Map3DMode
5261import com.google.android.gms.maps3d.model.latLngAltitude
62+ import com.google.android.gms.maps3d.model.LatLngAltitude
5363import com.google.android.gms.maps3d.model.polygonOptions
5464import dagger.hilt.android.AndroidEntryPoint
5565import com.google.android.gms.maps3d.Map3DOptions
@@ -77,6 +87,42 @@ class AnimatedPolygonActivity : ComponentActivity() {
7787 var showSettingsDialog by mutableStateOf(false )
7888
7989 setContent {
90+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts .StartActivityForResult ()) { result ->
91+ if (result.resultCode == Activity .RESULT_OK ) {
92+ result.data?.let { intent ->
93+ // Retrieve the Place selected by the user.
94+ val place = Autocomplete .getPlaceFromIntent(intent)
95+ place.latLng?.let { latLng ->
96+ // When a new location is picked, we persist the coordinates to DataStore
97+ // so that the 3D polygon's geometry is recalculated around this new center.
98+ viewModel.savePolygonCenter(latLng.latitude, latLng.longitude)
99+
100+ // Reset the user's zoom/altitude bounds. Since we don't know the exact elevation of the new place,
101+ // expanding the bounds from 0 to 15K feet allows them to easily find the polygon.
102+ viewModel.saveMinAltitude(0f )
103+ viewModel.saveMaxAltitude(15000f )
104+
105+ // Immediately fly the camera to the new location to center it in the view,
106+ // overriding whatever camera position the user previously had.
107+ val newCam = camera {
108+ center = latLngAltitude {
109+ latitude = latLng.latitude
110+ longitude = latLng.longitude
111+ altitude = 10000.0
112+ }
113+ heading = 0.0
114+ tilt = 0.0
115+ }
116+ viewModel.setCamera(newCam)
117+
118+ // Force a save of the new camera right away so that if the user backgrounds
119+ // the app before the map becomes "steady", they don't lose this new location.
120+ viewModel.saveCameraSettings(newCam)
121+ }
122+ }
123+ }
124+ }
125+
80126 AdvancedMaps3DSamplesTheme {
81127 Scaffold (
82128 modifier = Modifier .fillMaxSize(),
@@ -87,9 +133,20 @@ class AnimatedPolygonActivity : ComponentActivity() {
87133 titleContentColor = MaterialTheme .colorScheme.primary,
88134 ),
89135 title = {
90- Text (stringResource(R .string.scenarios_altitude_slider ))
136+ Text (stringResource(R .string.map_sample_animated_polygon ))
91137 },
92138 actions = {
139+ IconButton (onClick = {
140+ val fields = listOf (Place .Field .ID , Place .Field .NAME , Place .Field .LAT_LNG )
141+ val intent = Autocomplete .IntentBuilder (AutocompleteActivityMode .OVERLAY , fields)
142+ .build(this @AnimatedPolygonActivity)
143+ launcher.launch(intent)
144+ }) {
145+ Icon (
146+ imageVector = Icons .Filled .Search ,
147+ contentDescription = " Search Location"
148+ )
149+ }
93150 IconButton (onClick = { showSettingsDialog = true }) {
94151 Icon (
95152 imageVector = Icons .Filled .Settings ,
@@ -137,6 +194,11 @@ fun AnimatedPolygon(
137194 val sweepSpeed by viewModel.sweepSpeedFlow.collectAsStateWithLifecycle(initialValue = 50f )
138195 val savedCamera by viewModel.savedCameraFlow.collectAsStateWithLifecycle(initialValue = null )
139196
197+ val centerLat by viewModel.polygonCenterLatFlow.collectAsStateWithLifecycle(initialValue = 40.0 )
198+ val centerLng by viewModel.polygonCenterLngFlow.collectAsStateWithLifecycle(initialValue = - 105.5 )
199+ val widthMiles by viewModel.polygonWidthMilesFlow.collectAsStateWithLifecycle(initialValue = 26f )
200+ val heightMiles by viewModel.polygonHeightMilesFlow.collectAsStateWithLifecycle(initialValue = 27f )
201+
140202 // Ensure altitude is clamped within the min and max bounds
141203 var altitudeFeet by remember(minAltitude, maxAltitude) {
142204 mutableFloatStateOf((minAltitude + maxAltitude) / 2f )
@@ -145,12 +207,26 @@ fun AnimatedPolygon(
145207 var isSweeping by remember { mutableStateOf(false ) }
146208 var sweepDirection by remember(sweepSpeed) { mutableFloatStateOf(sweepSpeed) }
147209
148- val boulderPolygonPoints = remember {
210+ val boulderPolygonPoints = remember(centerLat, centerLng, widthMiles, heightMiles) {
211+ val center = LatLng (centerLat, centerLng)
212+ val widthMeters = widthMiles * 1609.34
213+ val heightMeters = heightMiles * 1609.34
214+
215+ // 0=North, 90=East, 180=South, 270=West
216+ val north = com.google.maps.android.SphericalUtil .computeOffset(center, heightMeters / 2.0 , 0.0 )
217+ val south = com.google.maps.android.SphericalUtil .computeOffset(center, heightMeters / 2.0 , 180.0 )
218+
219+ // Compute corners by offsetting north/south points east/west
220+ val nw = com.google.maps.android.SphericalUtil .computeOffset(north, widthMeters / 2.0 , 270.0 )
221+ val ne = com.google.maps.android.SphericalUtil .computeOffset(north, widthMeters / 2.0 , 90.0 )
222+ val sw = com.google.maps.android.SphericalUtil .computeOffset(south, widthMeters / 2.0 , 270.0 )
223+ val se = com.google.maps.android.SphericalUtil .computeOffset(south, widthMeters / 2.0 , 90.0 )
224+
149225 listOf (
150- latLngAltitude { latitude = 40.20 ; longitude = - 105.26 ; altitude = 0.0 }, // NW
151- latLngAltitude { latitude = 40.20 ; longitude = - 105.77 ; altitude = 0.0 }, // NE
152- latLngAltitude { latitude = 39.80 ; longitude = - 105.77 ; altitude = 0.0 }, // SE
153- latLngAltitude { latitude = 39.80 ; longitude = - 105.26 ; altitude = 0.0 } // SW
226+ latLngAltitude { latitude = nw.latitude ; longitude = nw.longitude ; altitude = 0.0 }, // NW
227+ latLngAltitude { latitude = ne.latitude ; longitude = ne.longitude ; altitude = 0.0 }, // NE
228+ latLngAltitude { latitude = se.latitude ; longitude = se.longitude ; altitude = 0.0 }, // SE
229+ latLngAltitude { latitude = sw.latitude ; longitude = sw.longitude ; altitude = 0.0 } // SW
154230 )
155231 }
156232
@@ -256,6 +332,8 @@ fun AnimatedPolygon(
256332 var tempMin by remember(minAltitude) { mutableStateOf(minAltitude.toInt().toString()) }
257333 var tempMax by remember(maxAltitude) { mutableStateOf(maxAltitude.toInt().toString()) }
258334 var tempSpeed by remember(sweepSpeed) { mutableStateOf(sweepSpeed.toInt().toString()) }
335+ var tempWidth by remember(widthMiles) { mutableStateOf(widthMiles.toString()) }
336+ var tempHeight by remember(heightMiles) { mutableStateOf(heightMiles.toString()) }
259337
260338 AlertDialog (
261339 onDismissRequest = onDismissSettings,
@@ -281,6 +359,20 @@ fun AnimatedPolygon(
281359 onValueChange = { tempSpeed = it },
282360 label = { Text (" Sweep Speed (ft/frame)" ) },
283361 keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
362+ modifier = Modifier .fillMaxWidth().padding(bottom = 8 .dp)
363+ )
364+ OutlinedTextField (
365+ value = tempWidth,
366+ onValueChange = { tempWidth = it },
367+ label = { Text (" Width (miles)" ) },
368+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
369+ modifier = Modifier .fillMaxWidth().padding(bottom = 8 .dp)
370+ )
371+ OutlinedTextField (
372+ value = tempHeight,
373+ onValueChange = { tempHeight = it },
374+ label = { Text (" Height (miles)" ) },
375+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
284376 modifier = Modifier .fillMaxWidth()
285377 )
286378 }
@@ -294,6 +386,13 @@ fun AnimatedPolygon(
294386 // Match the current direction (positive or negative) with the new speed magnitude
295387 sweepDirection = if (sweepDirection < 0 ) - it else it
296388 }
389+
390+ val w = tempWidth.toFloatOrNull()
391+ val h = tempHeight.toFloatOrNull()
392+ if (w != null && h != null ) {
393+ viewModel.savePolygonDimensions(w, h)
394+ }
395+
297396 onDismissSettings()
298397 }) {
299398 Text (" Save" )
0 commit comments