Meshtastic-Android/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt

198 lines
7.2 KiB
Kotlin
Raw Normal View History

2020-03-11 14:45:49 -07:00
package com.geeksville.mesh.ui
2020-03-30 15:00:18 -07:00
import android.graphics.Color
2020-03-30 11:56:59 -07:00
import android.os.Bundle
2020-04-07 11:27:51 -07:00
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
2021-02-21 11:34:43 +08:00
import androidx.core.content.ContextCompat
2022-02-04 21:58:00 -05:00
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
2020-04-11 13:20:30 -07:00
import com.geeksville.android.GeeksvilleApplication
2020-03-30 10:26:16 -07:00
import com.geeksville.android.Logging
import com.geeksville.mesh.NodeInfo
2020-03-11 18:13:44 -07:00
import com.geeksville.mesh.R
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.util.formatAgo
2020-03-30 12:47:01 -07:00
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
2022-02-04 21:58:00 -05:00
import com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAllowOverlap
import com.mapbox.maps.MapView
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.expressions.generated.Expression
import com.mapbox.maps.extension.style.layers.generated.SymbolLayer
import com.mapbox.maps.extension.style.layers.properties.generated.IconAnchor
import com.mapbox.maps.extension.style.layers.properties.generated.TextAnchor
import com.mapbox.maps.extension.style.layers.properties.generated.TextJustify
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
2020-03-30 12:47:01 -07:00
2020-03-11 14:45:49 -07:00
2020-04-07 11:27:51 -07:00
class MapFragment : ScreenFragment("Map"), Logging {
2020-04-07 12:13:50 -07:00
private val model: UIViewModel by activityViewModels()
2020-04-07 12:13:50 -07:00
private val nodeSourceId = "node-positions"
private val nodeLayerId = "node-layer"
private val labelLayerId = "label-layer"
private val markerImageId = "my-marker-image"
2022-02-04 21:58:00 -05:00
private val nodePositions = GeoJsonSource(GeoJsonSource.Builder(nodeSourceId))
2020-04-07 12:13:50 -07:00
2022-02-04 21:58:00 -05:00
private val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId)
.iconImage(markerImageId)
.iconAnchor(IconAnchor.BOTTOM)
.iconAllowOverlap(true)
2020-04-07 12:13:50 -07:00
2022-02-04 21:58:00 -05:00
private val labelLayer = SymbolLayer(labelLayerId, nodeSourceId)
.textField(Expression.get("name"))
.textSize() // TODO Set text size
2022-02-04 21:58:00 -05:00
.textColor(Color.RED)
.textVariableAnchor(arrayListOf(TextAnchor.TOP.toString()))
.textJustify(TextJustify.AUTO)
.textAllowOverlap(true)
2020-04-07 12:13:50 -07:00
private fun onNodesChanged(map: MapboxMap, nodes: Collection<NodeInfo>) {
val nodesWithPosition = nodes.filter { it.validPosition != null }
/**
* Using the latest nodedb, generate geojson features
*/
fun getCurrentNodes(): FeatureCollection {
// Find all nodes with valid locations
2020-04-07 12:13:50 -07:00
val locations = nodesWithPosition.map { node ->
val p = node.position!!
debug("Showing on map: $node")
2020-04-07 12:13:50 -07:00
val f = Feature.fromGeometry(
Point.fromLngLat(
p.longitude,
p.latitude
)
2020-04-07 12:13:50 -07:00
)
node.user?.let {
f.addStringProperty("name", it.longName + " " + formatAgo(p.time))
}
f
2020-04-07 12:13:50 -07:00
}
return FeatureCollection.fromFeatures(locations)
}
2020-04-07 12:13:50 -07:00
2021-02-06 22:08:49 -08:00
//TODO Update node positions
2022-02-04 21:58:00 -05:00
// nodePositions.setGeoJson(getCurrentNodes()) // Update node positions
2021-02-06 22:08:49 -08:00
}
//TODO Update camera movements
2021-02-06 22:08:49 -08:00
fun zoomToNodes(map: MapboxMap) {
val nodesWithPosition =
model.nodeDB.nodes.value?.values?.filter { it.validPosition != null }
2021-02-06 22:08:49 -08:00
if (nodesWithPosition != null && nodesWithPosition.isNotEmpty()) {
2022-02-04 21:58:00 -05:00
// val update = if (nodesWithPosition.size >= 2) {
// // Multiple nodes, make them all fit on the map view
// val bounds = LatLngBounds.Builder()
//
// // Add all positions
// bounds.includes(nodesWithPosition.map { it.position!! }
// .map { LatLng(it.latitude, it.longitude) })
//
// CameraUpdateFactory.newLatLngBounds(bounds.build(), 150)
// } else {
// // Only one node, just zoom in on it
// val it = nodesWithPosition[0].position!!
//
// val cameraPos = CameraPosition.Builder().target(
// LatLng(it.latitude, it.longitude)
// ).zoom(9.0).build()
// CameraUpdateFactory.newCameraPosition(cameraPos)
// }
// map.animateCamera(update, 1000)
// }
// }
2021-02-06 22:08:49 -08:00
}
2020-04-07 12:13:50 -07:00
}
2020-04-07 11:27:51 -07:00
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
2020-04-11 13:20:30 -07:00
): View? {
// We can't allow mapbox if user doesn't want analytics
val id =
if ((requireContext().applicationContext as GeeksvilleApplication).isAnalyticsAllowed) {
// Mapbox Access token
R.layout.map_view
} else {
R.layout.map_not_allowed
}
return inflater.inflate(id, container, false)
}
2020-03-11 14:45:49 -07:00
2020-04-11 13:20:30 -07:00
var mapView: MapView? = null
/**
* Mapbox native code can crash painfully if you ever call a mapbox view function while the view is not actively being show
*/
private val isViewVisible: Boolean
2022-02-04 21:58:00 -05:00
get() = mapView?.isVisible == true
override fun onViewCreated(viewIn: View, savedInstanceState: Bundle?) {
2020-06-10 17:19:17 -07:00
super.onViewCreated(viewIn, savedInstanceState)
2020-03-30 11:56:59 -07:00
2020-04-11 13:20:30 -07:00
// We might not have a real mapview if running with analytics
if ((requireContext().applicationContext as GeeksvilleApplication).isAnalyticsAllowed) {
val vIn = viewIn.findViewById<MapView>(R.id.mapView)
mapView = vIn
mapView?.let { v ->
// Each time the pane is shown start fetching new map info (we do this here instead of
// onCreate because getMapAsync can die in native code if the view goes away)
2022-02-04 21:58:00 -05:00
val map = v.getMapboxMap()
if (view != null) { // it might have gone away by now
// val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24)
val markerIcon =
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_twotone_person_pin_24
)!!
map.loadStyleUri(Style.OUTDOORS)
//TODO add layers to current view of map
2022-02-04 21:58:00 -05:00
// style.addSource(nodePositions)
// style.addImage(markerImageId, markerIcon)
// style.addLayer(nodeLayer)
// style.addLayer(labelLayer)
//TODO setup gesture controls
2022-02-04 21:58:00 -05:00
// map.uiSettings.isRotateGesturesEnabled = false
2022-02-04 21:58:00 -05:00
// Provide initial positions
model.nodeDB.nodes.value?.let { nodes ->
onNodesChanged(map, nodes.values)
}
2020-04-11 13:20:30 -07:00
}
2022-02-04 21:58:00 -05:00
// Any times nodes change update our map
model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes ->
if (isViewVisible)
onNodesChanged(map, nodes.values)
})
zoomToNodes(map)
2020-04-11 13:20:30 -07:00
}
2020-03-30 10:26:16 -07:00
}
2020-03-11 14:45:49 -07:00
}
}
2020-04-07 11:27:51 -07:00