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

682 lines
28 KiB
Kotlin
Raw Normal View History

2020-03-11 14:45:49 -07:00
package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
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
import android.widget.EditText
import android.widget.Toast
2021-02-21 11:34:43 +08:00
import androidx.core.content.ContextCompat
2022-02-05 13:26:08 -05:00
import androidx.core.graphics.drawable.toBitmap
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.databinding.MapNotAllowedBinding
import com.geeksville.mesh.databinding.MapViewBinding
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.util.formatAgo
2022-02-17 10:16:58 -05:00
import com.mapbox.bindgen.Value
import com.mapbox.common.*
2022-02-22 21:46:13 -05:00
import com.mapbox.geojson.*
2022-02-05 20:21:42 -05:00
import com.mapbox.maps.*
import com.mapbox.maps.dsl.cameraOptions
2022-02-04 21:58:00 -05:00
import com.mapbox.maps.extension.style.expressions.generated.Expression
2022-02-05 13:26:08 -05:00
import com.mapbox.maps.extension.style.layers.addLayer
import com.mapbox.maps.extension.style.layers.addPersistentLayer
import com.mapbox.maps.extension.style.layers.generated.LineLayer
2022-02-04 21:58:00 -05:00
import com.mapbox.maps.extension.style.layers.generated.SymbolLayer
import com.mapbox.maps.extension.style.layers.generated.lineLayer
import com.mapbox.maps.extension.style.layers.properties.generated.*
2022-02-05 13:26:08 -05:00
import com.mapbox.maps.extension.style.sources.addSource
2022-02-04 21:58:00 -05:00
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
2022-02-05 20:21:42 -05:00
import com.mapbox.maps.plugin.animation.MapAnimationOptions
import com.mapbox.maps.plugin.animation.flyTo
import com.mapbox.maps.plugin.gestures.OnMapLongClickListener
import com.mapbox.maps.plugin.gestures.gestures
import dagger.hilt.android.AndroidEntryPoint
import kotlin.math.cos
import kotlin.math.sin
2020-03-30 12:47:01 -07:00
2020-03-11 14:45:49 -07:00
@AndroidEntryPoint
2020-04-07 11:27:51 -07:00
class MapFragment : ScreenFragment("Map"), Logging {
2020-04-07 12:13:50 -07:00
2022-02-17 10:16:58 -05:00
private val tileStore: TileStore by lazy {
TileStore.create().also {
// Set default access token for the created tile store instance
it.setOption(
TileStoreOptions.MAPBOX_ACCESS_TOKEN,
TileDataDomain.MAPS,
Value(getString(R.string.mapbox_access_token))
)
}
}
2022-03-12 17:09:20 -05:00
/**
* DEVELOPER OPTION TO ENABLE OFFLINE MAPS
* Set this variable to true to enable offline maps
*/
//___________________________________________________
private val offlineEnabled = false
//___________________________________________________
2022-02-17 16:12:12 -05:00
2022-02-17 10:16:58 -05:00
private val resourceOptions: ResourceOptions by lazy {
ResourceOptions.Builder().applyDefaultParams(requireContext()).tileStore(tileStore).build()
}
private val offlineManager: OfflineManager by lazy {
OfflineManager(resourceOptions)
}
private lateinit var binding: MapViewBinding
private lateinit var mapNotAllowedBinding: MapNotAllowedBinding
2022-03-02 15:29:14 -05:00
private var userStyleURI: String? = null
2022-02-17 10:16:58 -05:00
private lateinit var geoJsonSource: GeoJsonSource
private lateinit var lineLayer: LineLayer
2022-03-02 15:29:14 -05:00
private var point: Point? = null
2022-02-17 10:16:58 -05: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-22 21:46:13 -05:00
private val userPointImageId = "user-image"
2022-03-08 13:58:21 -05:00
private val boundingBoxId = "bounding-box-id"
private val lineLayerId = "line-layer-id"
2020-04-07 12:13:50 -07:00
2022-02-17 10:16:58 -05:00
private var stylePackCancelable: Cancelable? = null
private var tilePackCancelable: Cancelable? = null
2022-02-23 20:22:04 -05:00
private lateinit var squareRegion: Geometry
private val userTouchPositionId = "user-touch-position"
private val userTouchLayerId = "user-touch-layer"
2022-02-05 20:21:42 -05:00
private var nodePositions = GeoJsonSource(GeoJsonSource.Builder(nodeSourceId))
2020-04-07 12:13:50 -07:00
2022-03-10 08:17:00 -05:00
private var tileRegionDownloadSuccess = false
private var stylePackDownloadSuccess = false
private val userTouchPosition = GeoJsonSource(GeoJsonSource.Builder(userTouchPositionId))
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
private val userTouchLayer = SymbolLayer(userTouchLayerId, userTouchPositionId)
.iconImage(userPointImageId)
.iconAnchor(IconAnchor.BOTTOM)
2022-02-04 21:58:00 -05:00
private val labelLayer = SymbolLayer(labelLayerId, nodeSourceId)
.textField(Expression.get("name"))
2022-02-05 13:26:08 -05:00
.textSize(12.0)
2022-02-04 21:58:00 -05:00
.textColor(Color.RED)
2022-02-05 13:26:08 -05:00
.textAnchor(TextAnchor.TOP)
//.textVariableAnchor(TextAnchor.TOP) //TODO investigate need for variable anchor vs normal anchor
2022-02-04 21:58:00 -05:00
.textJustify(TextJustify.AUTO)
.textAllowOverlap(true)
2020-04-07 12:13:50 -07:00
2022-02-22 21:46:13 -05:00
private fun onNodesChanged(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)
}
2022-02-05 20:21:42 -05:00
nodePositions.featureCollection(getCurrentNodes())
2021-02-06 22:08:49 -08:00
}
private fun zoomToNodes(map: MapboxMap) {
val points: MutableList<Point> = mutableListOf()
val nodesWithPosition =
model.nodeDB.nodes.value?.values?.filter { it.validPosition != null }
2021-02-06 22:08:49 -08:00
if (nodesWithPosition != null && nodesWithPosition.isNotEmpty()) {
val unit = if (nodesWithPosition.size >= 2) {
2022-02-05 20:21:42 -05:00
// Multiple nodes, make them all fit on the map view
2022-02-05 22:03:51 -05:00
nodesWithPosition.forEach {
points.add(
Point.fromLngLat(
it.position!!.longitude,
it.position!!.latitude
)
)
}
map.cameraForCoordinates(points)
2022-02-05 20:21:42 -05:00
} else {
// Only one node, just zoom in on it
val it = nodesWithPosition[0].position!!
points.add(Point.fromLngLat(it.longitude, it.latitude))
map.cameraForCoordinates(points)
cameraOptions {
this.zoom(9.0)
this.center(points[0])
}
2022-02-05 20:21:42 -05:00
}
map.flyTo(
unit,
MapAnimationOptions.mapAnimationOptions { duration(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?
): View {
2020-04-11 13:20:30 -07:00
// We can't allow mapbox if user doesn't want analytics
return if ((requireContext().applicationContext as GeeksvilleApplication).isAnalyticsAllowed) {
// Mapbox Access token
binding = MapViewBinding.inflate(inflater, container, false)
binding.root
} else {
mapNotAllowedBinding = MapNotAllowedBinding.inflate(inflater, container, false)
mapNotAllowedBinding.root
}
2020-04-11 13:20:30 -07:00
}
2020-03-11 14:45:49 -07:00
2020-04-11 13:20:30 -07:00
var mapView: MapView? = null
2022-02-17 10:16:58 -05:00
private fun removeOfflineRegions() {
// Remove the tile region with the tile region ID.
// Note this will not remove the downloaded tile packs, instead, it will just mark the tileset
// not a part of a tile region. The tiles still exists as a predictive cache in TileStore.
tileStore.removeTileRegion(TILE_REGION_ID)
// Set the disk quota to zero, so that tile regions are fully evicted
// when removed. The TileStore is also used when `ResourceOptions.isLoadTilePacksFromNetwork`
// is `true`, and also by the Navigation SDK.
// This removes the tiles that do not belong to any tile regions.
tileStore.setOption(TileStoreOptions.DISK_QUOTA, Value(0))
// Remove the style pack with the style url.
// Note this will not remove the downloaded style pack, instead, it will just mark the resources
// not a part of the existing style pack. The resources still exists as disk cache.
2022-03-10 08:17:00 -05:00
if (userStyleURI != null) {
offlineManager.removeStylePack(userStyleURI!!)
2022-03-11 10:08:21 -05:00
mapView?.getMapboxMap()?.loadStyleUri(Style.OUTDOORS)
2022-03-10 08:17:00 -05:00
} else {
offlineManager.removeStylePack(mapView?.getMapboxMap()?.getStyle()?.styleURI.toString())
2022-03-11 10:08:21 -05:00
mapView?.getMapboxMap()?.loadStyleUri(Style.OUTDOORS)
2022-03-10 08:17:00 -05:00
}
2022-02-17 10:16:58 -05:00
MapboxMap.clearData(resourceOptions) {
it.error?.let { error ->
debug(error)
}
}
2022-03-11 10:08:21 -05:00
updateStylePackDownloadProgress(0, 0)
updateTileRegionDownloadProgress(0, 0)
2022-02-17 10:16:58 -05:00
}
/**
* 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) {
// binding.fabStyleToggle.setOnClickListener {
// //TODO: Setup Style menu for satellite view, street view, & outdoor view
// }
binding.downloadRegion.setOnClickListener {
// Display menu for download region
this.downloadRegionDialogFragment()
}
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 =
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_twotone_person_pin_24
2022-02-05 13:26:08 -05:00
)!!.toBitmap()
2022-02-04 21:58:00 -05:00
map.loadStyleUri(loadMapStyleFromPref()) {
if (it.isStyleLoaded) {
it.addSource(nodePositions)
it.addImage(markerImageId, markerIcon)
it.addPersistentLayer(nodeLayer)
it.addPersistentLayer(labelLayer)
}
2022-02-05 13:26:08 -05:00
}
2022-02-04 21:58:00 -05:00
v.gestures.rotateEnabled = false
2022-03-12 17:09:20 -05:00
if (offlineEnabled) {
v.gestures.addOnMapLongClickListener(this.longClick)
}
2022-02-04 21:58:00 -05:00
// Provide initial positions
model.nodeDB.nodes.value?.let { nodes ->
2022-02-22 21:46:13 -05:00
onNodesChanged(nodes.values)
2022-02-04 21:58:00 -05:00
}
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)
2022-02-22 21:46:13 -05:00
onNodesChanged(nodes.values)
2022-02-04 21:58:00 -05:00
})
2022-02-17 10:16:58 -05:00
//viewAnnotationManager = v.viewAnnotationManager
2022-02-04 21:58:00 -05:00
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
}
2022-02-20 22:59:29 -05:00
private fun downloadOfflineRegion(styleURI: String = "") {
2022-03-10 08:17:00 -05:00
2022-02-20 22:59:29 -05:00
val style = styleURI.ifEmpty {
mapView?.getMapboxMap()
?.getStyle()?.styleURI.toString()
}
2022-03-10 08:17:00 -05:00
if (OfflineSwitch.getInstance().isMapboxStackConnected) {
2022-03-11 10:08:21 -05:00
2022-03-10 08:17:00 -05:00
// By default, users may download up to 250MB of data for offline use without incurring
// additional charges. This limit is subject to change during the beta.
// - - - - - - - -
// 1. Create style package with loadStylePack() call.
// A style pack (a Style offline package) contains the loaded style and its resources: loaded
// sources, fonts, sprites. Style packs are identified with their style URI.
// Style packs are stored in the disk cache database, but their resources are not subject to
// the data eviction algorithm and are not considered when calculating the disk cache size.
2022-03-11 10:08:21 -05:00
binding.stylePackDownloadProgress.visibility = View.VISIBLE
binding.stylePackText.visibility = View.VISIBLE
2022-03-10 08:17:00 -05:00
stylePackCancelable = offlineManager.loadStylePack(
style,
// Build Style pack load options
StylePackLoadOptions.Builder()
.glyphsRasterizationMode(GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY)
.metadata(Value(STYLE_PACK_METADATA))
.build(),
{ progress ->
updateStylePackDownloadProgress(
progress.completedResourceCount,
2022-03-11 10:08:21 -05:00
progress.requiredResourceCount,
2022-03-10 08:17:00 -05:00
)
},
{ expected ->
if (expected.isValue) {
expected.value?.let { stylePack ->
// Style pack download finishes successfully
debug("StylePack downloaded: $stylePack")
if (binding.stylePackDownloadProgress.progress == binding.stylePackDownloadProgress.max) {
2022-03-11 10:08:21 -05:00
debug("Style pack download complete")
binding.stylePackText.visibility = View.INVISIBLE
2022-03-10 08:17:00 -05:00
binding.stylePackDownloadProgress.visibility = View.INVISIBLE
stylePackDownloadSuccess = true
} else {
debug("Waiting for tile region download to be finished.")
}
}
}
expected.error?.let {
2022-03-12 17:09:20 -05:00
stylePackDownloadSuccess = false
2022-03-10 08:17:00 -05:00
// Handle error occurred during the style pack download.
2022-03-12 17:09:20 -05:00
binding.stylePackText.visibility = View.INVISIBLE
binding.stylePackDownloadProgress.visibility = View.INVISIBLE
2022-03-10 08:17:00 -05:00
debug("StylePackError: $it")
}
}
)
// - - - - - - - -
// 2. Create a tile region with tiles for the outdoors style
// A Tile Region represents an identifiable geographic tile region with metadata, consisting of
// a set of tiles packs that cover a given area (a polygon). Tile Regions allow caching tiles
// packs in an explicit way: By creating a Tile Region, developers can ensure that all tiles in
// that region will be downloaded and remain cached until explicitly deleted.
// Creating a Tile Region requires supplying a description of the area geometry, the tilesets
// and zoom ranges of the tiles within the region.
// The tileset descriptor encapsulates the tile-specific data, such as which tilesets, zoom ranges,
// pixel ratio etc. the cached tile packs should have. It is passed to the Tile Store along with
// the region area geometry to load a new Tile Region.
// The OfflineManager is responsible for creating tileset descriptors for the given style and zoom range.
val tilesetDescriptor = offlineManager.createTilesetDescriptor(
TilesetDescriptorOptions.Builder()
.styleURI(style)
.minZoom(0)
.maxZoom(10)
.build()
)
// Use the the default TileStore to load this region. You can create custom TileStores are are
// unique for a particular file path, i.e. there is only ever one TileStore per unique path.
// Note that the TileStore path must be the same with the TileStore used when initialise the MapView.
2022-03-11 10:08:21 -05:00
binding.tilePackText.visibility = View.VISIBLE
binding.tilePackDownloadProgress.visibility = View.VISIBLE
2022-03-10 08:17:00 -05:00
tilePackCancelable = tileStore.loadTileRegion(
TILE_REGION_ID, // Make this dynamic
TileRegionLoadOptions.Builder()
.geometry(squareRegion)
.descriptors(listOf(tilesetDescriptor))
.metadata(Value(TILE_REGION_METADATA))
.acceptExpired(true)
.networkRestriction(NetworkRestriction.NONE)
.build(),
{ progress ->
updateTileRegionDownloadProgress(
progress.completedResourceCount,
progress.requiredResourceCount,
)
}
) { expected ->
2022-02-17 10:16:58 -05:00
if (expected.isValue) {
2022-03-10 08:17:00 -05:00
// Tile pack download finishes successfully
expected.value?.let { region ->
debug("TileRegion downloaded: $region")
2022-03-11 10:08:21 -05:00
if (binding.tilePackDownloadProgress.progress == binding.tilePackDownloadProgress.max) {
2022-03-10 08:17:00 -05:00
debug("Finished tilepack download")
2022-03-11 10:08:21 -05:00
binding.tilePackDownloadProgress.visibility = View.INVISIBLE
binding.tilePackText.visibility = View.INVISIBLE
2022-03-10 08:17:00 -05:00
tileRegionDownloadSuccess = true
2022-03-11 10:08:21 -05:00
2022-02-17 15:22:22 -05:00
} else {
2022-03-10 08:17:00 -05:00
debug("Waiting for style pack download to be finished.")
2022-02-17 15:22:22 -05:00
}
2022-02-17 10:16:58 -05:00
}
}
expected.error?.let {
2022-03-12 17:09:20 -05:00
tileRegionDownloadSuccess = false
2022-03-10 08:17:00 -05:00
// Handle error occurred during the tile region download.
2022-03-12 17:09:20 -05:00
binding.tilePackDownloadProgress.visibility = View.INVISIBLE
binding.tilePackText.visibility = View.INVISIBLE
2022-03-10 08:17:00 -05:00
debug("TileRegionError: $it")
2022-02-17 10:16:58 -05:00
}
}
2022-03-10 08:17:00 -05:00
} else {
Toast.makeText(
requireContext(),
R.string.download_region_connection_alert,
2022-03-10 08:17:00 -05:00
Toast.LENGTH_LONG
).show()
2022-02-17 10:16:58 -05:00
}
}
/**
* OnLongClick of the map set a position marker.
* If a user long-clicks again, the position of the first marker will be updated
*/
private val longClick = OnMapLongClickListener {
val userDefinedPointImg =
ContextCompat.getDrawable(
requireActivity(),
R.drawable.baseline_location_on_white_24dp
)!!
.toBitmap()
2022-02-17 10:16:58 -05:00
point = Point.fromLngLat(it.longitude(), it.latitude())
2022-02-23 20:22:04 -05:00
/*
Calculate region from user specified position.
5 miles NE,NW,SE,SW from user center point.
25 Sq Mile Region
2022-02-23 20:22:04 -05:00
*/
2022-03-08 13:49:22 -05:00
//____________________________________________________________________________________________
2022-03-02 15:29:14 -05:00
val topRight = calculateCoordinate(45.0, point?.latitude()!!, point?.longitude()!!)
val topLeft = calculateCoordinate(135.0, point?.latitude()!!, point?.longitude()!!)
val bottomLeft = calculateCoordinate(225.0, point?.latitude()!!, point?.longitude()!!)
val bottomRight = calculateCoordinate(315.0, point?.latitude()!!, point?.longitude()!!)
2022-03-08 13:49:22 -05:00
//____________________________________________________________________________________________
2022-02-23 20:22:04 -05:00
val pointList = listOf(topRight, topLeft, bottomLeft, bottomRight, topRight)
2022-02-25 22:39:39 -05:00
squareRegion = LineString.fromLngLats(pointList)
2022-02-23 20:22:04 -05:00
geoJsonSource = geoJsonSource(boundingBoxId) {
2022-02-23 20:22:04 -05:00
geometry(squareRegion)
}
lineLayer = lineLayer(lineLayerId, boundingBoxId) {
lineCap(LineCap.ROUND)
lineJoin(LineJoin.MITER)
lineOpacity(0.7)
lineWidth(1.5)
lineColor("#888")
}
2022-03-06 19:52:49 -05:00
if (point != null) {
binding.downloadRegion.visibility = View.VISIBLE
}
mapView?.getMapboxMap()?.getStyle()?.let { style ->
2022-03-02 15:29:14 -05:00
userTouchPosition.geometry(point!!)
if (!style.styleLayerExists(userTouchLayerId)) {
style.addImage(userPointImageId, userDefinedPointImg)
style.addSource(userTouchPosition)
style.addSource(geoJsonSource)
style.addPersistentLayer(lineLayer)
style.addLayer(userTouchLayer)
} else {
style.removeStyleLayer(lineLayerId)
style.removeStyleSource(boundingBoxId)
style.addSource(geoJsonSource)
style.addLayer(lineLayer)
}
}
2022-03-06 19:52:49 -05:00
mapView?.getMapboxMap().also { mapboxMap ->
mapboxMap?.flyTo(
CameraOptions.Builder()
.zoom(ZOOM)
.center(point)
.build(), MapAnimationOptions.mapAnimationOptions { duration(1000) })
}
return@OnMapLongClickListener true
}
2022-02-24 17:53:40 -05:00
/**
* Find's coordinates (Lat,Lon) a specified distance from given (lat,lon) using degrees to determine direction
2022-03-08 13:49:22 -05:00
* @param degrees degree of desired position from current position. (center point is 0,0 and desired point, top right corner, is 45 degrees from 0,0)
* @param lat latitude position (current position lat)
* @param lon longitude position (current position lon)
2022-02-24 17:53:40 -05:00
* @return Point
*/
2022-03-08 13:49:22 -05:00
private fun calculateCoordinate(degrees: Double, lat: Double, lon: Double): Point {
val deg = Math.toRadians(degrees)
2022-02-24 08:51:27 -05:00
val distancesInMeters =
2022-03-11 10:08:21 -05:00
1609.344 * 2.5 // 1609.344 is 1 mile in meters -> multiplier will be user specified up to a max of 10
val radiusOfEarthInMeters = 6378137
val x =
2022-03-08 13:49:22 -05:00
lon + (180 / Math.PI) * (distancesInMeters / radiusOfEarthInMeters) * cos(
deg
)
val y =
2022-02-23 20:22:04 -05:00
lat + (180 / Math.PI) * (distancesInMeters / radiusOfEarthInMeters) * sin(deg)
return Point.fromLngLat(x, y)
}
2022-02-17 15:22:22 -05:00
private fun updateStylePackDownloadProgress(
progress: Long,
max: Long,
) {
binding.stylePackDownloadProgress.max = max.toInt()
binding.stylePackDownloadProgress.progress = progress.toInt()
}
private fun updateTileRegionDownloadProgress(
progress: Long,
max: Long,
) {
2022-03-11 10:08:21 -05:00
binding.tilePackDownloadProgress.max = max.toInt()
binding.tilePackDownloadProgress.progress = progress.toInt()
2022-02-17 15:22:22 -05:00
}
2022-02-17 10:16:58 -05:00
companion object {
2022-03-11 10:08:21 -05:00
private const val ZOOM = 12.5
private const val TILE_REGION_ID = "tile-region"
private const val STYLE_PACK_METADATA = "outdoor-style-pack"
private const val TILE_REGION_METADATA = "outdoor-tile-region"
2022-02-17 10:16:58 -05:00
}
2022-02-20 21:06:59 -05:00
private fun downloadRegionDialogFragment() {
val mapDownloadView = layoutInflater.inflate(R.layout.dialog_map_download, null)
val uri = mapDownloadView.findViewById<EditText>(R.id.uri)
val downloadRegionDialogFragment = AlertDialog.Builder(context)
downloadRegionDialogFragment.setView(mapDownloadView)
.setTitle(R.string.download_region_dialog_title)
2022-02-20 21:06:59 -05:00
.setMultiChoiceItems(
R.array.MapMenuCheckbox,
null,
) { _, _, isChecked ->
if (isChecked) {
if (!uri.isVisible) {
uri.visibility =
View.VISIBLE
}
2022-02-20 21:06:59 -05:00
} else {
if (uri.isVisible) {
uri.visibility =
View.GONE
}
2022-02-20 21:06:59 -05:00
}
}
.setPositiveButton(
R.string.save_btn, null
2022-03-02 15:29:14 -05:00
)
.setNeutralButton(R.string.view_region_btn) { _, _ ->
2022-03-11 10:08:21 -05:00
if (tileRegionDownloadSuccess && stylePackDownloadSuccess) {
mapView?.getMapboxMap().also {
it?.flyTo(
CameraOptions.Builder()
.zoom(ZOOM)
.center(point)
.build(),
MapAnimationOptions.mapAnimationOptions { duration(1000) })
if (userStyleURI != null) {
it?.loadStyleUri(userStyleURI.toString())
} else {
it?.getStyle().also { style ->
style?.removeStyleImage(userPointImageId)
}
2022-03-07 09:44:29 -05:00
}
2022-03-02 18:46:50 -05:00
}
2022-03-11 10:08:21 -05:00
} else {
Toast.makeText(
requireContext(),
R.string.no_download_region_alert,
Toast.LENGTH_SHORT
).show()
2022-03-02 18:46:50 -05:00
}
2022-02-22 21:46:13 -05:00
}
2022-02-20 21:06:59 -05:00
.setNegativeButton(
R.string.cancel
) { dialog, _ ->
2022-02-24 08:51:27 -05:00
mapView?.getMapboxMap()?.getStyle { style ->
2022-03-02 15:29:14 -05:00
point = null
userStyleURI = null
2022-02-24 08:51:27 -05:00
style.removeStyleLayer(lineLayerId)
style.removeStyleSource(boundingBoxId)
style.removeStyleLayer(userTouchLayerId)
style.removeStyleSource(userTouchPositionId)
style.removeStyleImage(userPointImageId)
}
2022-03-06 19:52:49 -05:00
binding.downloadRegion.visibility = View.INVISIBLE
2022-02-22 21:46:13 -05:00
removeOfflineRegions() //TODO: Add to offline manager window
2022-02-20 21:06:59 -05:00
dialog.cancel()
}
2022-03-02 15:29:14 -05:00
val dialog = downloadRegionDialogFragment.create()
dialog.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (uri.isVisible) {
if (uri.text.isNotEmpty()) {
// Save URI
userStyleURI = uri.text.toString()
uri.setText("") // clear text
2022-03-07 08:10:06 -05:00
downloadOfflineRegion(userStyleURI!!)
dialog.dismiss()
} else {
Toast.makeText(
requireContext(),
R.string.style_uri_empty_alert,
2022-03-07 08:10:06 -05:00
Toast.LENGTH_SHORT
).show()
2022-03-02 15:29:14 -05:00
}
2022-03-06 19:52:49 -05:00
} else {
2022-03-07 08:10:06 -05:00
downloadOfflineRegion()
dialog.dismiss()
2022-03-02 15:29:14 -05:00
}
}
}
private fun loadMapStyleFromPref():String {
val prefs = context?.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
val mapStyleId = prefs?.getInt("map_style_id", 1)
debug("mapStyleId from prefs: $mapStyleId")
val mapStyle = when (mapStyleId) {
0 -> Style.MAPBOX_STREETS
1 -> Style.OUTDOORS
2 -> Style.LIGHT
3 -> Style.DARK
4 -> Style.SATELLITE
5 -> Style.SATELLITE_STREETS
6 -> Style.TRAFFIC_DAY
7 -> Style.TRAFFIC_NIGHT
else -> Style.OUTDOORS
}
return mapStyle
}
2020-03-11 14:45:49 -07:00
}
2020-04-07 11:27:51 -07:00