From 8878254a4a3af9d15262fd2553c7b2db0a7bd8bc Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Tue, 11 Jan 2022 04:12:45 -0600 Subject: Implemented polygon geofences --- androidApp/build.gradle.kts | 1 + .../android/details/reports/UnitReportsFragment.kt | 4 +- .../TrackerMap/android/map/MapFragment.kt | 92 ++++++++++++++++++---- .../TrackerMap/android/map/UnitMapFragment.kt | 4 + .../TrackerMap/android/units/UnitsViewModel.kt | 14 ++++ 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 5736c84..22d894a 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -38,5 +38,6 @@ dependencies { implementation("io.ktor:ktor-client-serialization:1.6.6") implementation("com.github.zerobranch:SwipeLayout:1.3.1") implementation("com.github.addisonElliott:SegmentedButton:3.1.9") + implementation("mil.nga.sf:sf-wkt:1.0.1") implementation(group = "", name = "WhirlyGlobeMaply", ext = "aar") } \ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt index b701990..43f879d 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/details/reports/UnitReportsFragment.kt @@ -17,7 +17,6 @@ import mx.trackermap.TrackerMap.android.databinding.UnitDetailsReportsBinding import mx.trackermap.TrackerMap.android.details.UnitDetailsAdapter import mx.trackermap.TrackerMap.android.map.MapFragment import mx.trackermap.TrackerMap.android.map.MarkerTransformations -import mx.trackermap.TrackerMap.client.models.Event import mx.trackermap.TrackerMap.client.models.EventInformation import mx.trackermap.TrackerMap.client.models.Position import mx.trackermap.TrackerMap.client.models.Stop @@ -119,8 +118,9 @@ class UnitReportsFragment : Fragment() { ) } - unitReportsViewModel.geofences.observe(this) { geofences -> + unitReportsViewModel.geofences.observe(viewLifecycleOwner) { geofences -> Log.d("UnitReportsFragment", "Success $geofences") + reportsMapFragment.displayGeofences(geofences.values.toTypedArray()) } } diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt index 41cfd50..fa0a56e 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt @@ -10,8 +10,12 @@ import android.view.ViewGroup import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import com.mousebird.maply.* +import mil.nga.sf.Polygon +import mil.nga.sf.util.SFException +import mil.nga.sf.wkt.GeometryReader import java.io.File import mx.trackermap.TrackerMap.android.R +import mx.trackermap.TrackerMap.client.models.Geofence import mx.trackermap.TrackerMap.utils.MarkerType typealias MarkerCallback = (Int?) -> Unit @@ -28,6 +32,7 @@ class MapFragment : GlobeMapFragment() { var markerCallback: MarkerCallback? = null private val objects = mutableListOf() + private val geofenceObjects = mutableListOf() override fun chooseDisplayType(): MapDisplayType { return MapDisplayType.Map @@ -95,12 +100,20 @@ class MapFragment : GlobeMapFragment() { markerCallback?.let { it(null) } } - private fun clear() { - mapControl.removeObjects( - objects, - ThreadMode.ThreadAny - ) - objects.clear() + private fun clear(geofences: Boolean = false) { + if (geofences) { + mapControl.removeObjects( + geofenceObjects, + ThreadMode.ThreadAny + ) + geofenceObjects.clear() + } else { + mapControl.removeObjects( + objects, + ThreadMode.ThreadAny + ) + objects.clear() + } } fun display(markers: Array, isReport: Boolean) { @@ -108,6 +121,15 @@ class MapFragment : GlobeMapFragment() { clear() + val points = markers.map { marker -> + Point2d.FromDegrees(marker.longitude, marker.latitude) + }.toTypedArray() + + val vectorInfo = VectorInfo() + vectorInfo.setColor(Color.GREEN) + vectorInfo.setLineWidth(20.0f) + + /* Draw markers for positions */ val screenMarkers = markers.mapIndexed { i, marker -> val screenMarker = ScreenMarker() val markerSize = Point2d(144.0, 144.0) @@ -133,19 +155,11 @@ class MapFragment : GlobeMapFragment() { ThreadMode.ThreadAny )) - val points = markers.map { marker -> - Point2d.FromDegrees(marker.longitude, marker.latitude) - }.toTypedArray() - - // Draw polyline for report + /* Draw polyline for report */ if (isReport && markers.isNotEmpty()) { val vector = VectorObject() vector.addAreal(points) - val vectorInfo = VectorInfo() - vectorInfo.setColor(Color.GREEN) - vectorInfo.setLineWidth(25.0f) - objects.add(mapControl.addVector( vector, vectorInfo, @@ -168,6 +182,54 @@ class MapFragment : GlobeMapFragment() { } } + fun displayGeofences(geofences: Array) { + Log.d("MapFragment", "Displaying geofences") + + clear(true) + + val vectorInfoGeofence = VectorInfo() + vectorInfoGeofence.setColor(Color.BLUE) + vectorInfoGeofence.setLineWidth(10.0f) + + val shapes = mutableListOf() + val vectors = mutableListOf() + + geofences.forEach { geofence -> + geofence.area?.let { area -> + Log.d("MainFragment", "Geofence ${geofence.name} = ${geofence.area}") + try { + val geometry = GeometryReader.readGeometry(area) + if (!geometry.isEmpty) { + when (geometry) { + is Polygon -> { + val vector = VectorObject() + vector.addAreal(geometry.exteriorRing.points.map { + Point2d.FromDegrees(it.y, it.x) + }.toTypedArray()) + + vectors.add(vector) + } + } + } + } catch (e: SFException) { + e.printStackTrace() + } + } + } + + geofenceObjects.add(mapControl.addShapes( + shapes, + ShapeInfo(), + ThreadMode.ThreadAny + )) + + geofenceObjects.add(mapControl.addVectors( + vectors, + vectorInfoGeofence, + ThreadMode.ThreadAny + )) + } + fun focusOn(latitude: Double, longitude: Double, zoom: Double = 0.0000144, animated: Boolean = true) { val lat = latitude * Math.PI / 180 val lon = longitude * Math.PI / 180 diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/UnitMapFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/UnitMapFragment.kt index 3c34abb..4f98a96 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/UnitMapFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/UnitMapFragment.kt @@ -124,6 +124,10 @@ class UnitMapFragment : Fragment() { unitsMapFragment.focusOn(unit.position!!.latitude!!, unit.position!!.longitude!!) } } + + unitsViewModel.geofences.observe(viewLifecycleOwner) { geofences -> + unitsMapFragment.displayGeofences(geofences.values.toTypedArray()) + } } private fun itemAction(unit: UnitInformation, action: Action) { diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt index ca05736..c0dc7c6 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.* import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import mx.trackermap.TrackerMap.client.models.Geofence import mx.trackermap.TrackerMap.client.models.UnitInformation +import mx.trackermap.TrackerMap.controllers.GeofencesController import mx.trackermap.TrackerMap.controllers.UnitsController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -20,22 +22,28 @@ class UnitsViewModel( } private val unitsController: UnitsController by inject() + private val geofenceController: GeofencesController by inject() private var _searchQuery = savedStateHandle.getLiveData("searchQuery", "") private var _unitsDisplayMode = MutableLiveData(UnitsDisplayMode.LIST) private var _units = MutableLiveData>() private var _selectedUnit = MutableLiveData() + private var _geofences = MutableLiveData>() val searchQuery: LiveData get() = _searchQuery val unitsDisplayMode: LiveData get() = _unitsDisplayMode val units: LiveData> get() = _units val selectedUnit: LiveData get() = _selectedUnit + val geofences: LiveData> get() = _geofences init { Log.d("UnitsViewModel", "Initializing Units View Model") viewModelScope.launch { setupObservers() } + viewModelScope.launch { + setupGeofenceObserver() + } } private suspend fun setupObservers() { @@ -45,6 +53,12 @@ class UnitsViewModel( } } + private suspend fun setupGeofenceObserver() { + geofenceController.geofencesFlow.collect { + this._geofences.postValue(it) + } + } + fun selectUnit(unit: UnitInformation) { Log.d("UnitsViewModel", "Selecting unit ${unit.device.name}") _selectedUnit.postValue(unit) -- cgit v1.2.3