11package com.honz.itsvisualizer
22
3+ import android.Manifest
4+ import android.app.AlertDialog
35import android.content.BroadcastReceiver
46import android.content.Context
57import android.content.Intent
68import android.content.IntentFilter
9+ import android.content.pm.PackageManager
10+ import android.location.Location
711import android.os.Bundle
812import android.util.Log
913import androidx.fragment.app.Fragment
1014import android.view.LayoutInflater
1115import android.view.View
1216import android.view.ViewGroup
17+ import androidx.appcompat.content.res.AppCompatResources
18+ import androidx.core.app.ActivityCompat
1319import androidx.localbroadcastmanager.content.LocalBroadcastManager
1420import com.google.android.material.floatingactionbutton.FloatingActionButton
21+ import com.mapbox.android.gestures.MoveGestureDetector
22+ import com.mapbox.geojson.Point
23+ import com.mapbox.maps.CameraOptions
24+ import com.mapbox.maps.EdgeInsets
25+ import com.mapbox.maps.MapView
26+ import com.mapbox.maps.Style
27+ import com.mapbox.maps.plugin.LocationPuck2D
28+ import com.mapbox.maps.plugin.animation.MapAnimationOptions
29+ import com.mapbox.maps.plugin.animation.camera
30+ import com.mapbox.maps.plugin.compass.compass
31+ import com.mapbox.maps.plugin.gestures.OnMoveListener
32+ import com.mapbox.maps.plugin.gestures.addOnMoveListener
33+ import com.mapbox.maps.plugin.locationcomponent.location
34+ import com.mapbox.navigation.base.options.NavigationOptions
35+ import com.mapbox.navigation.core.MapboxNavigation
36+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
37+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
38+ import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
39+ import com.mapbox.navigation.core.trip.session.LocationMatcherResult
40+ import com.mapbox.navigation.core.trip.session.LocationObserver
41+ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
1542
1643class MapFragment : Fragment () {
1744
45+ private lateinit var mapView: MapView
1846 private lateinit var connectionToggleFab: FloatingActionButton
47+ private lateinit var cameraCenteringToggleFab: FloatingActionButton
48+
49+ private var isTripSessionStarted = false
50+ private var centerCamera = true
51+ private var lastLocation: Location ? = null
52+
53+ private val navigationLocationProvider = NavigationLocationProvider ()
54+
55+ /* *
56+ * locationObserver passes new location data to update camera position
57+ */
58+ private val locationObserver = object : LocationObserver {
59+ override fun onNewLocationMatcherResult (locationMatcherResult : LocationMatcherResult ) {
60+ val enhancedLocation = locationMatcherResult.enhancedLocation
61+ navigationLocationProvider.changePosition(
62+ enhancedLocation,
63+ locationMatcherResult.keyPoints
64+ )
65+
66+ lastLocation = enhancedLocation
67+ updateCameraPosition(enhancedLocation)
68+ }
69+
70+ // Not implemented
71+ override fun onNewRawLocation (rawLocation : Location ) {}
72+ }
73+
74+ /* *
75+ * OnMoveListener that disables camera centering after moves the map
76+ */
77+ private val onMoveListener = object : OnMoveListener {
78+ override fun onMoveBegin (detector : MoveGestureDetector ) {
79+ if (centerCamera) setCameraCentering(false )
80+ }
81+
82+ override fun onMove (detector : MoveGestureDetector ): Boolean {
83+ return false
84+ }
85+
86+ override fun onMoveEnd (detector : MoveGestureDetector ) {}
87+ }
1988
2089 override fun onCreate (savedInstanceState : Bundle ? ) {
2190 super .onCreate(savedInstanceState)
22- Log .i(" [MAP FRAGMENT]" , " onCreate()" )
2391
2492 // Signals from SocketService
2593 val stateFilter = IntentFilter (" itsVisualizer.SERVICE_STATE" )
2694 LocalBroadcastManager .getInstance(requireContext()).registerReceiver(stateReceiver, stateFilter)
2795
28- // TODO: Init map
2996 }
3097
3198 override fun onCreateView (
@@ -34,16 +101,84 @@ class MapFragment : Fragment() {
34101 ): View ? {
35102 val view = inflater.inflate(R .layout.fragment_map, container, false )
36103
104+
105+ // Connection toggle FAB
37106 connectionToggleFab = view.findViewById(R .id.connectionToggleFab)
38107 connectionToggleFab.setOnClickListener { toggleConnection() }
39108
40- // Update FAB icon based on current state
109+ // Update icon based on current state
41110 val intent = Intent (" itsVisualizer.SOCKET_SERVICE_STATE_REQUEST" )
42111 LocalBroadcastManager .getInstance(requireContext()).sendBroadcast(intent)
43112
113+ // Camera centering FAB
114+ cameraCenteringToggleFab = view.findViewById(R .id.cameraCenteringToggleFab)
115+ cameraCenteringToggleFab.setOnClickListener {
116+ setCameraCentering(null )
117+ }
118+
119+ // Init mapbox
120+ mapView = view.findViewById(R .id.mapView)
121+ mapView.getMapboxMap().loadStyleUri(Style .TRAFFIC_DAY )
122+ mapView.getMapboxMap().addOnMoveListener(onMoveListener)
123+ // mapView.scalebar.enabled = false
124+ mapView.compass.updateSettings {
125+ marginTop = 100.0f
126+ }
127+
128+ // Location init
129+ initNavigation()
130+
44131 return view
45132 }
46133
134+ /* *
135+ * Mapbox navigation element that matches current position to nearest road
136+ */
137+ private val mapboxNavigation by requireMapboxNavigation(
138+ onResumedObserver = object : MapboxNavigationObserver {
139+ override fun onAttached (mapboxNavigation : MapboxNavigation ) {
140+ mapboxNavigation.registerLocationObserver(locationObserver)
141+
142+ // Check if user granted location permissions
143+ if (ActivityCompat .checkSelfPermission(
144+ requireContext(),
145+ Manifest .permission.ACCESS_FINE_LOCATION
146+ ) != PackageManager .PERMISSION_GRANTED && ActivityCompat .checkSelfPermission(
147+ requireContext(),
148+ Manifest .permission.ACCESS_COARSE_LOCATION
149+ ) != PackageManager .PERMISSION_GRANTED
150+ ) {
151+ // Permissions denied, show a message to the user
152+ AlertDialog .Builder (activity)
153+ .setTitle(R .string.perm_disabled_title)
154+ .setMessage(R .string.perm_disabled_text)
155+ .setNeutralButton(R .string.cancel) { dialog, _ ->
156+ dialog.dismiss()
157+ }
158+ .setCancelable(false )
159+ .show()
160+
161+ setCameraCentering(false )
162+ cameraCenteringToggleFab.isEnabled = false
163+ return
164+ }
165+
166+ if (! isTripSessionStarted) {
167+ mapboxNavigation.startTripSession()
168+ isTripSessionStarted = true
169+ }
170+ else {
171+ lastLocation?.let { updateCameraPosition(it) }
172+ }
173+
174+ }
175+
176+ override fun onDetached (mapboxNavigation : MapboxNavigation ) {
177+ mapboxNavigation.unregisterLocationObserver(locationObserver)
178+ }
179+ },
180+ )
181+
47182 /* *
48183 * Gets boolean representing current state of socket to update FAB image
49184 */
@@ -58,6 +193,74 @@ class MapFragment : Fragment() {
58193 }
59194 }
60195
196+ /* *
197+ * Sets up the location provider and location puck image
198+ */
199+ private fun initNavigation () {
200+ MapboxNavigationApp .setup(
201+ NavigationOptions .Builder (requireActivity().applicationContext)
202+ .accessToken(getString(R .string.mapbox_access_token))
203+ .build()
204+ )
205+
206+ mapView.location.apply {
207+ setLocationProvider(navigationLocationProvider)
208+ locationPuck = LocationPuck2D (
209+ bearingImage = AppCompatResources .getDrawable(
210+ requireActivity().applicationContext,
211+ com.mapbox.navigation.ui.maps.R .drawable.mapbox_navigation_puck_icon2
212+ ),
213+ shadowImage = AppCompatResources .getDrawable(
214+ requireActivity().applicationContext,
215+ com.mapbox.navigation.ui.maps.R .drawable.mapbox_navigation_puck_icon2_shadow
216+ ),
217+ )
218+
219+ enabled = true
220+ }
221+ }
222+
223+ /* *
224+ * Eases the camera to position provided in 'location' parameter
225+ */
226+ private fun updateCameraPosition (location : Location ) {
227+ if (! centerCamera) return
228+
229+ val mapAnimationOptions =
230+ MapAnimationOptions .Builder ()
231+ .duration(1500L )
232+ .build()
233+
234+ mapView.camera.easeTo(
235+ CameraOptions .Builder ()
236+ .center(Point .fromLngLat(location.longitude, location.latitude))
237+ .bearing(location.bearing.toDouble())
238+ .zoom(18.0 )
239+ .pitch(45.0 )
240+ .padding(EdgeInsets (500.0 , 0.0 , 0.0 , 0.0 ))
241+ .build(),
242+ mapAnimationOptions
243+ )
244+ }
245+
246+ /* *
247+ * Controls if camera follows current location.
248+ * Passing 'null' as a parameter toggles current state.
249+ */
250+ private fun setCameraCentering (enabled : Boolean? ) {
251+ centerCamera = enabled ? : ! centerCamera
252+
253+ if (centerCamera) {
254+ cameraCenteringToggleFab.setImageResource(R .drawable.location)
255+ }
256+ else {
257+ cameraCenteringToggleFab.setImageResource(R .drawable.location_off)
258+ }
259+ }
260+
261+ /* *
262+ * Toggles the current state of socket connection
263+ */
61264 private fun toggleConnection () {
62265 val intent = Intent (" itsVisualizer.TOGGLE_SOCKET_SERVICE" )
63266 LocalBroadcastManager .getInstance(requireContext()).sendBroadcast(intent)
0 commit comments