diff --git a/app/build.gradle b/app/build.gradle index b10034946..a26fcdf1a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 220554801..1042bc7dc 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -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 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) { - 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 = 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) { + 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(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 = 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(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() + } } diff --git a/app/src/main/res/layout/map_not_allowed.xml b/app/src/main/res/layout/map_not_allowed.xml deleted file mode 100644 index b90ab1204..000000000 --- a/app/src/main/res/layout/map_not_allowed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml index 7b1c190c3..a1a6e1265 100644 --- a/app/src/main/res/layout/map_view.xml +++ b/app/src/main/res/layout/map_view.xml @@ -5,93 +5,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + tools:layout_editor_absoluteX="110dp" + tools:layout_editor_absoluteY="16dp" /> \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 38e18b013..357dc973a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -63,13 +63,13 @@ tr - Streets - Outdoors - Light - Dark - Satellite - Satellite Streets - Navigation Day - Navigation Night + OpenStreetMap + USGS TOPO + USGS Satellite + OpenTopo + Roads Overlay NL + Cloud Made Small Tiles + Chartbundle ENRH + Chartbundle WAC \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58a385dee..74edf5905 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,7 +140,7 @@ Unable to download style pack Language (restart needed) System default - Map style + Map Source Resend Shutdown Reboot