Working on migrating to OSMdroid from Mapbox

pull/470/head
PWRxPSYCHO 2022-08-23 20:05:19 -04:00
rodzic eccb48d7ed
commit 017095d171
6 zmienionych plików z 144 dodań i 735 usunięć

Wyświetl plik

@ -88,7 +88,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += [ '-opt-in=kotlin.RequiresOptIn' ]
freeCompilerArgs += ['-opt-in=kotlin.RequiresOptIn']
}
lint {
abortOnError false
@ -140,6 +140,9 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
//OSMAND
implementation 'org.osmdroid:osmdroid-android:6.1.14'
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
@ -166,9 +169,6 @@ dependencies {
// implementation 'com.google.android.things:androidthings:1.0'
implementation 'com.github.mik3y:usb-serial-for-android:3.4.6'
// mapbox
implementation 'com.mapbox.maps:android:10.2.0'
// location services
implementation 'com.google.android.gms:play-services-location:19.0.1'
// For Google Sign-In (owner name accesss)

Wyświetl plik

@ -1,680 +1,189 @@
package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.NodeInfo
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
import com.mapbox.bindgen.Value
import com.mapbox.common.*
import com.mapbox.geojson.*
import com.mapbox.maps.*
import com.mapbox.maps.dsl.cameraOptions
import com.mapbox.maps.extension.style.expressions.generated.Expression
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
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.*
import com.mapbox.maps.extension.style.sources.addSource
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
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
import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.OverlayItem
@AndroidEntryPoint
class MapFragment : ScreenFragment("Map"), Logging {
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))
)
}
}
/**
* DEVELOPER OPTION TO ENABLE OFFLINE MAPS
* Set this variable to true to enable offline maps
*/
//___________________________________________________
private val offlineEnabled = false
//___________________________________________________
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
private var userStyleURI: String? = null
private lateinit var geoJsonSource: GeoJsonSource
private lateinit var lineLayer: LineLayer
private var point: Point? = null
private lateinit var map: MapView
private lateinit var mapController: IMapController
private lateinit var nodePositions: List<GeoPoint>
private val model: UIViewModel by activityViewModels()
private val nodeSourceId = "node-positions"
private val nodeLayerId = "node-layer"
private val labelLayerId = "label-layer"
private val markerImageId = "my-marker-image"
private val userPointImageId = "user-image"
private val boundingBoxId = "bounding-box-id"
private val lineLayerId = "line-layer-id"
private var stylePackCancelable: Cancelable? = null
private var tilePackCancelable: Cancelable? = null
private val defaultLat = 38.8976763
private val defaultLong = -77.0365298
private val defaultZoomLevel = 6.0
private val defaultZoomSpeed = 3000L
private val defaultMinZoom = 3.0
private lateinit var squareRegion: Geometry
private val userTouchPositionId = "user-touch-position"
private val userTouchLayerId = "user-touch-layer"
private var nodePositions = GeoJsonSource(GeoJsonSource.Builder(nodeSourceId))
private var tileRegionDownloadSuccess = false
private var stylePackDownloadSuccess = false
private val userTouchPosition = GeoJsonSource(GeoJsonSource.Builder(userTouchPositionId))
private val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId)
.iconImage(markerImageId)
.iconAnchor(IconAnchor.BOTTOM)
.iconAllowOverlap(true)
private val userTouchLayer = SymbolLayer(userTouchLayerId, userTouchPositionId)
.iconImage(userPointImageId)
.iconAnchor(IconAnchor.BOTTOM)
private val labelLayer = SymbolLayer(labelLayerId, nodeSourceId)
.textField(Expression.get("name"))
.textSize(12.0)
.textColor(Color.RED)
.textAnchor(TextAnchor.TOP)
//.textVariableAnchor(TextAnchor.TOP) //TODO investigate need for variable anchor vs normal anchor
.textJustify(TextJustify.AUTO)
.textAllowOverlap(true)
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
val locations = nodesWithPosition.map { node ->
val p = node.position!!
debug("Showing on map: $node")
val f = Feature.fromGeometry(
Point.fromLngLat(
p.longitude,
p.latitude
)
)
node.user?.let {
f.addStringProperty("name", it.longName + " " + formatAgo(p.time))
}
f
}
return FeatureCollection.fromFeatures(locations)
}
nodePositions.featureCollection(getCurrentNodes())
}
private fun zoomToNodes(map: MapboxMap) {
val points: MutableList<Point> = mutableListOf()
val nodesWithPosition =
model.nodeDB.nodes.value?.values?.filter { it.validPosition != null }
if (nodesWithPosition != null && nodesWithPosition.isNotEmpty()) {
val unit = if (nodesWithPosition.size >= 2) {
// Multiple nodes, make them all fit on the map view
nodesWithPosition.forEach {
points.add(
Point.fromLngLat(
it.position!!.longitude,
it.position!!.latitude
)
)
}
map.cameraForCoordinates(points)
} 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])
}
}
map.flyTo(
unit,
MapAnimationOptions.mapAnimationOptions { duration(1000) })
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// 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
}
return inflater.inflate(R.layout.map_view, container, false)
}
var mapView: MapView? = null
private fun onNodesChanged(nodes: Collection<NodeInfo>) {
val nodesWithPosition = nodes.filter { it.validPosition != null }
/**
* Using the latest nodedb, generate GeoPoint
*/
// Find all nodes with valid locations
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)
val locations = nodesWithPosition.map { node ->
val p = node.position!!
debug("Showing on map: $node")
val f = GeoPoint(p.latitude, p.longitude)
// 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.
if (userStyleURI != null) {
offlineManager.removeStylePack(userStyleURI!!)
mapView?.getMapboxMap()?.loadStyleUri(loadMapStyleFromPref())
} else {
offlineManager.removeStylePack(mapView?.getMapboxMap()?.getStyle()?.styleURI.toString())
mapView?.getMapboxMap()?.loadStyleUri(loadMapStyleFromPref())
}
MapboxMap.clearData(resourceOptions) {
it.error?.let { error ->
debug(error)
node.user?.let {
OverlayItem("name", it.longName + " " + formatAgo(p.time), f)
}
f
}
updateStylePackDownloadProgress(0, 0)
updateTileRegionDownloadProgress(0, 0)
nodePositions = locations
}
/**
* 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
get() = mapView?.isVisible == true
override fun onViewCreated(viewIn: View, savedInstanceState: Bundle?) {
super.onViewCreated(viewIn, savedInstanceState)
Configuration.getInstance().userAgentValue =
BuildConfig.APPLICATION_ID // Required to get online tiles
// 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()
map = viewIn.findViewById(R.id.map) as MapView
/**
* Copyright layer required
*/
////////////////////////////////////////////////////////////
val copyrightNotice: String =
map.tileProvider.tileSource.copyrightNotice
val copyrightOverlay = CopyrightOverlay(context)
copyrightOverlay.setCopyrightNotice(copyrightNotice)
map.overlays.add(copyrightOverlay)
///////////////////////////////////////////////////////////
setupMapProperties()
if (view != null) {
val markerIcon =
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_twotone_person_pin_24
)!!.toBitmap()
// Provide initial positions
model.nodeDB.nodes.value?.let { nodes ->
onNodesChanged(nodes.values)
}
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)
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
)!!.toBitmap()
map.loadStyleUri(loadMapStyleFromPref()) {
if (it.isStyleLoaded) {
it.addSource(nodePositions)
it.addImage(markerImageId, markerIcon)
it.addPersistentLayer(nodeLayer)
it.addPersistentLayer(labelLayer)
}
}
v.gestures.rotateEnabled = false
if (offlineEnabled) {
v.gestures.addOnMapLongClickListener(this.longClick)
}
// Provide initial positions
model.nodeDB.nodes.value?.let { nodes ->
onNodesChanged(nodes.values)
}
}
// Any times nodes change update our map
model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes ->
if (isViewVisible)
onNodesChanged(nodes.values)
})
//viewAnnotationManager = v.viewAnnotationManager
zoomToNodes(map)
zoomToNodes(mapController)
// Any times nodes change update our map
model.nodeDB.nodes.observe(viewLifecycleOwner) { nodes ->
onNodesChanged(nodes.values)
}
}
}
private fun setupMapProperties() {
if (this::map.isInitialized) {
map.setTileSource(loadOnlineTileSourceBase())
map.minZoomLevel = defaultMinZoom
map.setMultiTouchControls(true) // Sets gesture controls to true
map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) // Disables default +/- button for zooming
mapController = map.controller
val point = GeoPoint(defaultLat, defaultLong) //White House Coordinates, Washington DC
mapController.animateTo(point, defaultZoomLevel, defaultZoomSpeed)
}
}
private fun downloadOfflineRegion(styleURI: String = "") {
val style = styleURI.ifEmpty {
mapView?.getMapboxMap()
?.getStyle()?.styleURI.toString()
}
if (OfflineSwitch.getInstance().isMapboxStackConnected) {
// 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.
binding.stylePackDownloadProgress.visibility = View.VISIBLE
binding.stylePackText.visibility = View.VISIBLE
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,
progress.requiredResourceCount,
)
},
{ expected ->
if (expected.isValue) {
expected.value?.let { stylePack ->
// Style pack download finishes successfully
debug("StylePack downloaded: $stylePack")
if (binding.stylePackDownloadProgress.progress == binding.stylePackDownloadProgress.max) {
debug("Style pack download complete")
binding.stylePackText.visibility = View.INVISIBLE
binding.stylePackDownloadProgress.visibility = View.INVISIBLE
stylePackDownloadSuccess = true
} else {
debug("Waiting for tile region download to be finished.")
}
}
}
expected.error?.let {
stylePackDownloadSuccess = false
// Handle error occurred during the style pack download.
binding.stylePackText.visibility = View.INVISIBLE
binding.stylePackDownloadProgress.visibility = View.INVISIBLE
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.
binding.tilePackText.visibility = View.VISIBLE
binding.tilePackDownloadProgress.visibility = View.VISIBLE
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,
private fun zoomToNodes(controller: IMapController) {
val points: MutableList<GeoPoint> = mutableListOf()
val nodesWithPosition =
model.nodeDB.nodes.value?.values?.filter { it.validPosition != null }
if ((nodesWithPosition != null) && nodesWithPosition.isNotEmpty()) {
val unit = if (nodesWithPosition.size >= 2) {
// Multiple nodes, make them all fit on the map view
nodesWithPosition.forEach {
points.add(
GeoPoint(
it.position!!.longitude,
it.position!!.latitude
)
)
}
) { expected ->
if (expected.isValue) {
// Tile pack download finishes successfully
expected.value?.let { region ->
debug("TileRegion downloaded: $region")
if (binding.tilePackDownloadProgress.progress == binding.tilePackDownloadProgress.max) {
debug("Finished tilepack download")
binding.tilePackDownloadProgress.visibility = View.INVISIBLE
binding.tilePackText.visibility = View.INVISIBLE
tileRegionDownloadSuccess = true
} else {
debug("Waiting for style pack download to be finished.")
}
}
}
expected.error?.let {
tileRegionDownloadSuccess = false
// Handle error occurred during the tile region download.
binding.tilePackDownloadProgress.visibility = View.INVISIBLE
binding.tilePackText.visibility = View.INVISIBLE
debug("TileRegionError: $it")
}
}
} else {
Toast.makeText(
requireContext(),
R.string.download_region_connection_alert,
Toast.LENGTH_LONG
).show()
}
}
/**
* 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()
point = Point.fromLngLat(it.longitude(), it.latitude())
/*
Calculate region from user specified position.
5 miles NE,NW,SE,SW from user center point.
25 Sq Mile Region
*/
//____________________________________________________________________________________________
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()!!)
//____________________________________________________________________________________________
val pointList = listOf(topRight, topLeft, bottomLeft, bottomRight, topRight)
squareRegion = LineString.fromLngLats(pointList)
geoJsonSource = geoJsonSource(boundingBoxId) {
geometry(squareRegion)
}
lineLayer = lineLayer(lineLayerId, boundingBoxId) {
lineCap(LineCap.ROUND)
lineJoin(LineJoin.MITER)
lineOpacity(0.7)
lineWidth(1.5)
lineColor("#888")
}
if (point != null) {
binding.downloadRegion.visibility = View.VISIBLE
}
mapView?.getMapboxMap()?.getStyle()?.let { style ->
userTouchPosition.geometry(point!!)
if (!style.styleLayerExists(userTouchLayerId)) {
style.addImage(userPointImageId, userDefinedPointImg)
style.addSource(userTouchPosition)
style.addSource(geoJsonSource)
style.addPersistentLayer(lineLayer)
style.addLayer(userTouchLayer)
// TODO: zoom to the middle of all points
controller.animateTo(points[0])
} else {
style.removeStyleLayer(lineLayerId)
style.removeStyleSource(boundingBoxId)
style.addSource(geoJsonSource)
style.addLayer(lineLayer)
}
}
mapView?.getMapboxMap().also { mapboxMap ->
mapboxMap?.flyTo(
CameraOptions.Builder()
.zoom(ZOOM)
.center(point)
.build(), MapAnimationOptions.mapAnimationOptions { duration(1000) })
}
return@OnMapLongClickListener true
}
/**
* Find's coordinates (Lat,Lon) a specified distance from given (lat,lon) using degrees to determine direction
* @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)
* @return Point
*/
private fun calculateCoordinate(degrees: Double, lat: Double, lon: Double): Point {
val deg = Math.toRadians(degrees)
val distancesInMeters =
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 =
lon + (180 / Math.PI) * (distancesInMeters / radiusOfEarthInMeters) * cos(
deg
)
val y =
lat + (180 / Math.PI) * (distancesInMeters / radiusOfEarthInMeters) * sin(deg)
return Point.fromLngLat(x, y)
}
private fun updateStylePackDownloadProgress(
progress: Long,
max: Long,
) {
binding.stylePackDownloadProgress.max = max.toInt()
binding.stylePackDownloadProgress.progress = progress.toInt()
}
private fun updateTileRegionDownloadProgress(
progress: Long,
max: Long,
) {
binding.tilePackDownloadProgress.max = max.toInt()
binding.tilePackDownloadProgress.progress = progress.toInt()
}
companion object {
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"
}
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)
.setMultiChoiceItems(
R.array.MapMenuCheckbox,
null,
) { _, _, isChecked ->
if (isChecked) {
if (!uri.isVisible) {
uri.visibility =
View.VISIBLE
}
} else {
if (uri.isVisible) {
uri.visibility =
View.GONE
}
}
}
.setPositiveButton(
R.string.save_btn, null
)
.setNeutralButton(R.string.view_region_btn) { _, _ ->
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)
}
}
}
} else {
Toast.makeText(
requireContext(),
R.string.no_download_region_alert,
Toast.LENGTH_SHORT
).show()
}
}
.setNegativeButton(
R.string.cancel
) { dialog, _ ->
mapView?.getMapboxMap()?.getStyle { style ->
point = null
userStyleURI = null
style.removeStyleLayer(lineLayerId)
style.removeStyleSource(boundingBoxId)
style.removeStyleLayer(userTouchLayerId)
style.removeStyleSource(userTouchPositionId)
style.removeStyleImage(userPointImageId)
}
binding.downloadRegion.visibility = View.INVISIBLE
removeOfflineRegions() //TODO: Add to offline manager window
dialog.cancel()
}
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
downloadOfflineRegion(userStyleURI!!)
dialog.dismiss()
} else {
Toast.makeText(
requireContext(),
R.string.style_uri_empty_alert,
Toast.LENGTH_SHORT
).show()
}
} else {
downloadOfflineRegion()
dialog.dismiss()
// Only one node, just zoom in on it
val it = nodesWithPosition[0].position!!
points.add(GeoPoint(it.longitude, it.latitude))
controller.animateTo(points[0], defaultZoomLevel, defaultZoomSpeed)
}
}
}
private fun loadMapStyleFromPref():String {
private fun loadOnlineTileSourceBase(): OnlineTileSourceBase {
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
val mapSourceId = prefs?.getInt("map_style_id", 1)
debug("mapStyleId from prefs: $mapSourceId")
val mapSource = when (mapSourceId) {
0 -> TileSourceFactory.MAPNIK
1 -> TileSourceFactory.USGS_TOPO
2 -> TileSourceFactory.USGS_SAT
3 -> TileSourceFactory.OpenTopo
4 -> TileSourceFactory.ROADS_OVERLAY_NL
5 -> TileSourceFactory.CLOUDMADESMALLTILES
6 -> TileSourceFactory.ChartbundleENRH
7 -> TileSourceFactory.ChartbundleWAC
else -> TileSourceFactory.MAPNIK
}
return mapStyle
return mapSource
}
override fun onPause() {
map.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
map.onResume()
}
override fun onDestroy() {
super.onDestroy()
map.onDetach()
}
}

Wyświetl plik

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:autoLink="web"
android:text="@string/map_not_allowed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -5,93 +5,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.maps.MapView
android:id="@+id/mapView"
<org.osmdroid.views.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mapbox_cameraZoom="0" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@+id/mapView">
<TextView
android:id="@+id/style_pack_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Style Pack"
android:textColor="@color/colorPrimaryDark"
android:visibility="invisible" />
<ProgressBar
android:id="@+id/style_pack_download_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="8"
android:clickable="false"
android:indeterminate="false"
android:max="100"
android:progressTint="@color/colorPrimary"
android:visibility="invisible" />
<TextView
android:id="@+id/tile_pack_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Tile Pack"
android:textColor="@color/colorPrimaryDark"
android:visibility="invisible" />
<ProgressBar
android:id="@+id/tile_pack_download_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="8"
android:clickable="false"
android:indeterminate="false"
android:max="100"
android:progressTint="@color/colorPrimary"
android:visibility="invisible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- <com.google.android.material.floatingactionbutton.FloatingActionButton-->
<!-- android:id="@+id/fab_style_toggle"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_margin="8dp"-->
<!-- android:backgroundTint="@color/buttonColor"-->
<!-- android:contentDescription="@string/style_selection"-->
<!-- android:src="@drawadownload_regionble/baseline_layers_white_24dp"-->
<!-- tools:background="@color/buttonColor" />-->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/download_region"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:backgroundTint="@color/buttonColor"
android:contentDescription="@string/download_region"
android:src="@drawable/baseline_download_white_24dp"
android:visibility="invisible"
tools:background="@color/buttonColor" />
</LinearLayout>
tools:layout_editor_absoluteX="110dp"
tools:layout_editor_absoluteY="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -63,13 +63,13 @@
<item>tr</item>
</string-array>
<string-array name="map_styles">
<item>Streets</item>
<item>Outdoors</item>
<item>Light</item>
<item>Dark</item>
<item>Satellite</item>
<item>Satellite Streets</item>
<item>Navigation Day</item>
<item>Navigation Night</item>
<item>OpenStreetMap</item>
<item>USGS TOPO</item>
<item>USGS Satellite</item>
<item>OpenTopo</item>
<item>Roads Overlay NL</item>
<item>Cloud Made Small Tiles</item>
<item>Chartbundle ENRH</item>
<item>Chartbundle WAC</item>
</string-array>
</resources>

Wyświetl plik

@ -140,7 +140,7 @@
<string name="download_failed">Unable to download style pack</string>
<string name="preferences_language">Language (restart needed)</string>
<string name="preferences_system_default">System default</string>
<string name="preferences_map_style">Map style</string>
<string name="preferences_map_style">Map Source</string>
<string name="resend">Resend</string>
<string name="shutdown">Shutdown</string>
<string name="reboot">Reboot</string>