I have created an application that, given two input points, calls a function that draws a path, on a map, between the two points. I would now like to be able to implement the ability to click on the path and save it locally, in a list, so that I can retrieve it when necessary.
I'm a beginner with programming in kotlin and android, could you advise me how to do it?
I share below the function that I use to create the path in case it is useful.
fun routePath(p1Latit: Double, p1Laong: Double, p2Latit: Double, p2Laong: Double){
val roadManager: RoadManager = OSRMRoadManager(context, "lolloMaps")
OSRMRoadManager.MEAN_BY_FOOT
val waypoints = arrayListOf<GeoPoint>()
val startPoint: GeoPoint = GeoPoint(p1Latit, p1Laong)
waypoints.add(startPoint)
val endPoint: GeoPoint = GeoPoint(p2Latit, p2Laong)
waypoints.add(endPoint)
mapView.overlays.forEach{
if (it is Polyline && it.id == "Path"){
mapView.overlays.remove(it)
}
}
road = roadManager.getRoad(waypoints)
if (road.mStatus != Road.STATUS_OK){
Toast.makeText(context, "Error - status = " + road.mStatus, Toast.LENGTH_SHORT).show()
}
val roadOverlay: Polyline = RoadManager.buildRoadOverlay(road)
roadOverlay.id = "Path"
mapView.overlays.add(roadOverlay)
mapView.invalidate()
val nodeIcon: Drawable? = ResourcesCompat.getDrawable(mapView.resources, R.drawable.marker_node, null)
mapView.overlays.forEach{
if (it is Marker && it.id == "Node"){
mapView.overlays.remove(it)
}
}
for (i: Int in road.mNodes.indices){
val node: RoadNode = road.mNodes[i]
val nodeMarker: Marker = Marker(mapView)
nodeMarker.position = node.mLocation
nodeMarker.icon = nodeIcon
nodeMarker.title = "Passo $i"
nodeMarker.id = "Node"
mapView.overlays.add(nodeMarker)
nodeMarker.snippet = node.mInstructions
nodeMarker.subDescription = Road.getLengthDurationText(context,node.mLength, node.mDuration)
// var icon: Drawable = resources.getDrawable(R.drawable.ic_continue)
// nodeMarker.image = icon
}
}
I have a nice map with icon markers and a recyclerview setup using Mapbox. It is similar to this:
https://docs.mapbox.com/android/maps/examples/recyclerview-interaction
My problem is that I can not figure out how to set the icons according to my local json property. I have in total around 40 different icons I need to use so I thought the best place to set that would be in my geojson properties. I have the png files for my icons in my drawable-xxhdpi folder. Here is what my local geojson file looks like:
{
"type": "Feature",
"properties": {
"name": "Broadway",
"icon": "route_icon_1",
"hours": "V1",
"phone": "One Star Rating",
"description": "Located at Lower Point Boulders"
},
"geometry": {
"type": "Point",
"coordinates": [
-86.311166,
33.919872
]
}
},
{
"type": "Feature",
"properties": {
"name": "Return of the Jedi",
"icon": "route_icon_2",
"hours": "V7",
"phone": "One Star Rating",
"description": "Located at Lower Point Boulders"
},
"geometry": {
"type": "Point",
"coordinates": [
-86.311174,
33.919883
]
}
},
I'm accessing the geojson file just fine and everything is running perfectly, but I can not seem to set my icon according to the "icon" property in the geojson file. Is this even possible? I apologize for the length of my code. This is my first time asking a question on here and I wasn't sure how much of the code I should post.
Here is my code in my map activity:
class MapActivity : AppCompatActivity(), OnMapReadyCallback, LocationRecyclerViewAdapter.ClickListener,
MapboxMap.OnMapClickListener {
private var featureCollection: FeatureCollection? = null
private var mapboxMap: MapboxMap? = null
private var mapView: MapView? = null
private var locationsRecyclerView: RecyclerView? = null
private var listOfIndividualLocations: ArrayList<IndividualLocation>? = null
private var styleRvAdapter: LocationRecyclerViewAdapter? = null
private var markerSelected = false
private var markerAnimator: ValueAnimator? = null
private var selectedMarkerIcon: Bitmap? = null
private var unselectedMarkerIcon: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Configure the Mapbox access token. Configuration can either be called in your application
// class or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.access_token))
// Hide the status bar for the map to fill the entire screen
#Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
// Inflate the layout with the the MapView. Always inflate this after the Mapbox access token is configured.
setContentView(R.layout.activity_map)
// Create a GeoJSON feature collection from the GeoJSON file in the assets folder.
try {
featureCollectionFromJson
} catch (exception: Exception) {
Log.e("MapActivity", "onCreate: $exception")
Toast.makeText(this, R.string.failure_to_load_file, Toast.LENGTH_LONG).show()
}
// Initialize a list of IndividualLocation objects for future use with recyclerview
listOfIndividualLocations = ArrayList()
// Set up the Mapbox map
mapView = findViewById(R.id.mapView)
mapView?.onCreate(savedInstanceState)
mapView?.getMapAsync(this)
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
// Initialize the custom class that handles marker icon creation and map styling based on the selected theme
selectedMarkerIcon = BitmapFactory.decodeResource(
resources,
R.drawable.basic_red_marker
)
unselectedMarkerIcon = BitmapFactory.decodeResource(
resources,
R.drawable.basic_red_marker
)
mapboxMap.setStyle(
Style.MAPBOX_STREETS
)
{ style ->
// Setting the returned mapboxMap object (directly above) equal to the "globally declared" one
this#MapActivity.mapboxMap = mapboxMap
mapboxMap.addOnMapClickListener(this)
val quad = LatLngQuad(
LatLng(33.92307338188975, -86.31129446659145),
LatLng(33.92307338188975, -86.30688718139171),
LatLng(33.91948714730282, -86.30688718139171),
LatLng(33.91948714730282, -86.31129446659145)
)
val quad2 = LatLngQuad(
LatLng(33.921356, -86.307309),
LatLng(33.921356, -86.307156),
LatLng(33.921200, -86.307156),
LatLng(33.921200, -86.307309)
)
// Add an ImageSource to the map
style.addSource(ImageSource(ID_IMAGE_SOURCE, quad, R.drawable.asset14))
style.addSource(ImageSource(ID_IMAGE_SOURCE_VANDALA, quad2, R.drawable.vandala_overlay_sm))
// Create a raster layer and use the imageSource's ID as the layer's data. Then add a RasterLayer to the map.
style.addLayerBelow(
RasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE),
LAYER_ID
)
style.addLayerAbove(
RasterLayer(ID_IMAGE_LAYER_VANDALA, ID_IMAGE_SOURCE_VANDALA),
ID_IMAGE_LAYER
)
// Set bounds for the map camera so that the user can't pan the map outside of the NYC area
mapboxMap.setLatLngBoundsForCameraTarget(LOCKED_MAP_CAMERA_BOUNDS)
// Set up the SymbolLayer which will show the icons for each route location
initRouteLocationIconSymbolLayer()
// Set up the SymbolLayer which will show the selected route icon
initSelectedRouteSymbolLayer()
// Create a list of features from the feature collection
if (featureCollection != null) {
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
// Retrieve and update the source designated for showing the route location icons
mapboxMap.style?.getSourceAs<GeoJsonSource?>(SOURCE_ID)!!
.setGeoJson(FeatureCollection.fromFeatures(featureList))
for (x in featureList.indices) {
val singleLocation: Feature = featureList[x]
// Get the single location's String properties to place in its map marker
val singleLocationName: String =
singleLocation.getStringProperty("name")
val singleLocationRouteIcon: String =
singleLocation.getStringProperty("icon")
Log.e("tag", singleLocationRouteIcon)
val singleLocationHours: String =
singleLocation.getStringProperty("hours")
val singleLocationDescription: String =
singleLocation.getStringProperty("description")
val singleLocationPhoneNum: String =
singleLocation.getStringProperty("phone")
// Add a boolean property to use for adjusting the icon of the selected route location
singleLocation.addBooleanProperty(PROPERTY_SELECTED, false)
// Get the single location's LatLng coordinates
val singleLocationPosition: Point = singleLocation.geometry() as Point
// Create a new LatLng object with the Position object created above
val singleLocationLatLng = LatLng(
singleLocationPosition.latitude(),
singleLocationPosition.longitude()
)
// Add the location to the Arraylist of locations for later use in the recyclerview
listOfIndividualLocations!!.add(
IndividualLocation(
singleLocationName,
singleLocationRouteIcon,
singleLocationDescription,
singleLocationHours,
singleLocationPhoneNum,
singleLocationLatLng
)
)
}
}
// the Maps SDK's LocationComponent can be used to easily display and customize
// the device location's puck
setUpRecyclerViewOfLocationCards()
Toast.makeText(this#MapActivity, "Click on a card", Toast.LENGTH_SHORT)
.show()
}
}
override fun onMapClick(point: LatLng): Boolean {
Log.e("tag", "onMapClick")
val style = mapboxMap!!.style
if (style != null) {
val selectedMarkerSymbolLayer = style.getLayer("selected-route-location-layer-id") as SymbolLayer?
val pixel = mapboxMap!!.projection.toScreenLocation(point)
val features = mapboxMap!!.queryRenderedFeatures(pixel, "route-location-layer-id")
val selectedFeature = mapboxMap!!.queryRenderedFeatures(
pixel, "selected-route-location-layer-id"
)
if (selectedFeature.size > 0 && markerSelected) {
Log.e(
"tag",
"1 onMapClick: selectedFeature.size > 0 && markerSelected ${selectedFeature.size} $markerSelected"
)
return false
}
if (features.isEmpty()) {
Log.e("tag", "2 onMapClick: features.isEmpty")
if (markerSelected) {
Log.e("tag", "selectMarker = true")
deselectMarker(selectedMarkerSymbolLayer!!)
}
return false
}
val source = style.getSourceAs<GeoJsonSource>(ID_SELECTED_ROUTE_ICON)
source?.setGeoJson(
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
features[0].geometry()
)
)
)
)
if (markerSelected) {
Log.e("tag", "3 onMapClick: selectMarker")
deselectMarker(selectedMarkerSymbolLayer!!)
}
if (features.size > 0) {
Log.e("tag", "4 onMapClick: features.size > 0")
Log.e("tag", "4 onMapClick: features.size = ${features.size}")
selectMarker(selectedMarkerSymbolLayer!!)
}
}
handleClickIcon(mapboxMap?.projection!!.toScreenLocation(point))
return true
}
private fun handleClickIcon(screenPoint: PointF): Boolean {
Log.e("tag", "handleClickIcon")
val features: List<Feature> = mapboxMap?.queryRenderedFeatures(
screenPoint,
LAYER_ID,
"route-location-layer-id"
) as List<Feature>
return if (features.isNotEmpty()) {
Log.e("tag", "1 handleClickIcon: features.isNotEmpty")
val name: String = features[0].getStringProperty("name")
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
for (i in featureList.indices) {
if (featureList[i].getStringProperty("name") == name) {
Log.e("tag", "(${featureList[i].getStringProperty("name")} == $name)")
val selectedFeaturePoint: Point = featureList[i].geometry() as Point
Log.e("tag", "handleClickIcon: featureSelectStatus = ${featureSelectStatus(i)}")
if (featureSelectStatus(i)) {
Log.e("tag", "${featureSelectStatus(i)}")
Log.e(
"tag", "2 handleClickIcon: featureSelectStatus = ${
featureSelectStatus(
i
)
}"
)
setFeatureSelectState(featureList[i], true)
Log.e(
"tag",
"2 handleClickIcon: setFeatureSelectState(featureList $i: selectedState set to false)"
)
} else {
Log.e(
"tag", "3 handleClickIcon: featureSelectStatus = ${
featureSelectStatus(
i
)
}"
)
setSelected(i)
}
if (selectedFeaturePoint.latitude() != MOCK_DEVICE_LOCATION_LAT_LNG.latitude) {
Log.e(
"tag",
"selectedFeaturePoint.latitude != MOCK_DEVICE_LOCATION_LAT_LNG.latitude"
)
for (x in 0 until featureCollection!!.features()!!.size) {
if (listOfIndividualLocations!![x].getLocation()?.latitude == selectedFeaturePoint.latitude()) {
// Scroll the recyclerview to the selected marker's card. It's "x-1" below because
// the mock device location marker is part of the marker list but doesn't have its own card
// in the actual recyclerview.
locationsRecyclerView!!.smoothScrollToPosition(x)
Log.e("tag", "smoothScrollToPosition")
}
}
}
}
else
{
setFeatureSelectState(featureList[i], false)
Log.e("tag", "4 handleClickIcon: (${featureList[i]}")
}
}
true
}
else
{
false
}
}
private fun selectMarker(iconLayer: SymbolLayer) {
Log.e("tag", "selectMarker: markerSelect set to true")
markerAnimator = ValueAnimator()
markerAnimator!!.setObjectValues(1f, 2f)
markerAnimator!!.duration = 300
markerAnimator!!.addUpdateListener { animator ->
iconLayer.setProperties(
iconSize(animator.animatedValue as Float)
)
}
markerAnimator!!.start()
markerSelected = true
}
private fun deselectMarker(iconLayer: SymbolLayer) {
Log.e("tag", "deselectMarker: markerSelect set to false")
markerAnimator!!.setObjectValues(1.5f, 1f)
markerAnimator!!.duration = 300
markerAnimator!!.addUpdateListener { animator ->
iconLayer.setProperties(
iconSize(animator.animatedValue as Float)
)
}
markerAnimator!!.start()
markerSelected = false
}
/**
* The LocationRecyclerViewAdapter's interface which listens to clicks on each location's card
*
* #param position the clicked card's position/index in the overall list of cards
*/
override fun onItemClick(position: Int) {
Log.e("tag", "itemClick")
// Get the selected individual location via its card's position in the recyclerview of cards
val selectedLocation: IndividualLocation = listOfIndividualLocations!![position]
// Evaluate each Feature's "select state" to appropriately style the location's icon
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
val selectedLocationPoint: Point =
featureCollection!!.features()?.get(position)?.geometry() as Point
for (i in featureList.indices) {
if (featureList[i].getStringProperty("name") == selectedLocation.name) {
Log.e(
"tag",
"(${featureList[i].getStringProperty("name")} == ${selectedLocation.name})"
)
if (featureSelectStatus(i)) {
Log.e("tag", "1 featureSelectStatus = ${featureSelectStatus(i)}")
// setFeatureSelectState(featureList[i], false)
Toast.makeText(
applicationContext,
"Go to ${selectedLocation.name} Details Activity",
Toast.LENGTH_LONG
).show()
Log.e("tag", "Go to ${selectedLocation.name} Details Activity")
} else {
Log.e("tag", "2 itemClick: setSelected $i")
setSelected(i)
}
} else {
setFeatureSelectState(featureList[i], false)
Log.e("tag", "3 itemClick: featureList $i: selectedState false")
}
}
selectedLocation.getLocation()?.let { onCardClick(it) }
// Reposition the map camera target to the selected marker
repositionMapCamera(selectedLocationPoint)
}
private fun onCardClick(point: LatLng): Boolean {
Log.e("tag", "onCardClick")
val style = mapboxMap!!.style
if (style != null) {
val selectedMarkerSymbolLayer = style.getLayer("selected-route-location-layer-id") as SymbolLayer?
val pixel = mapboxMap!!.projection.toScreenLocation(point)
val features = mapboxMap!!.queryRenderedFeatures(pixel, "route-location-layer-id")
val selectedFeature = mapboxMap!!.queryRenderedFeatures(
pixel, "selected-route-location-layer-id"
)
if (selectedFeature.size > 0 && markerSelected) {
Log.e(
"tag",
"1 onCardClick: selectedFeature.size > 0 && markerSelected ${selectedFeature.size} $markerSelected"
)
return false
}
if (features.isEmpty()) {
Log.e("tag", "2 onCardClick: features.isEmpty")
if (markerSelected) {
Log.e("tag", "Do Nothing")
return false
}
else {
Log.e("tag", "markerSelected = $markerSelected")
selectMarker(selectedMarkerSymbolLayer!!)
}
return false
}
val source = style.getSourceAs<GeoJsonSource>(ID_SELECTED_ROUTE_ICON)
source?.setGeoJson(
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
features[0].geometry()
)
)
)
)
if (markerSelected) {
Log.e("tag", "3 onCardClick: markerSelected")
deselectMarker(selectedMarkerSymbolLayer!!)
}
if (features.size > 0) {
Log.e("tag", "4 onCardClick: features.size > 0")
Log.e("tag", "4 onCardClick: features.size = ${features.size}")
selectMarker(selectedMarkerSymbolLayer!!)
}
}
return true
}
/**
* Adds a SymbolLayer which will show all of the location's icons
*/
private fun initRouteLocationIconSymbolLayer() {
val style: Style? = mapboxMap?.style
if (style != null) {
// Add the icon image to the map
style.addImage(
ID_ROUTE_ICON, unselectedMarkerIcon as Bitmap
)
style.transition = TransitionOptions(0, 0, false)
// Create and add the GeoJsonSource to the map
val routeLocationGeoJsonSource = GeoJsonSource(SOURCE_ID)
style.addSource(routeLocationGeoJsonSource)
// Create and add the route location icon SymbolLayer to the map
val routeLocationSymbolLayer = SymbolLayer(
"route-location-layer-id",
SOURCE_ID
)
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON),
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
style.addLayer(routeLocationSymbolLayer)
} else {
Log.d("RouteFinderActivity", "initRouteLocationIconSymbolLayer: Style isn't ready yet.")
throw IllegalStateException("Style isn't ready yet.")
}
}
/**
* Adds a SymbolLayer which will show the selected location's icon
*/
private fun initSelectedRouteSymbolLayer() {
val style: Style? = mapboxMap?.style
if (style != null) {
// Add the icon image to the map
style.addImage(
ID_SELECTED_ROUTE_ICON, selectedMarkerIcon as Bitmap
)
// Create and add the route location icon SymbolLayer to the map
val selectedRouteLocationSymbolLayer = SymbolLayer(
"selected-route-location-layer-id",
SOURCE_ID
)
selectedRouteLocationSymbolLayer.withProperties(
iconImage(ID_SELECTED_ROUTE_ICON),
iconOffset(arrayOf(0f, -9f)),
iconAllowOverlap(true)
)
selectedRouteLocationSymbolLayer.withFilter(eq(get(PROPERTY_SELECTED), literal(true)))
style.addLayer(selectedRouteLocationSymbolLayer)
} else {
Log.d("RouteFinderActivity", "initSelectedRouteSymbolLayer: Style isn't ready yet.")
throw IllegalStateException("Style isn't ready yet.")
}
}
/**
* Checks whether a Feature's boolean "selected" property is true or false
*
* #param index the specific Feature's index position in the FeatureCollection's list of Features.
* #return true if "selected" is true. False if the boolean property is false.
*/
private fun featureSelectStatus(index: Int): Boolean {
return if (featureCollection == null) {
false
} else featureCollection!!.features()?.get(index)?.getBooleanProperty(PROPERTY_SELECTED)!!
}
/**
* Set a feature selected state.
*
* #param index the index of selected feature
*/
private fun setSelected(index: Int) {
Log.e("tag", "setSelected")
val feature: Feature = featureCollection?.features()!![index]
setFeatureSelectState(feature, true)
Log.e(
"tag",
"setSelected: setFeatureSelectedState(feature = ${featureCollection?.features()!![index]}, selectedState set to true"
)
refreshSource()
}
/**
* Selects the state of a feature
*
* #param feature the feature to be selected.
*/
private fun setFeatureSelectState(feature: Feature, selectedState: Boolean) {
feature.properties()?.addProperty(PROPERTY_SELECTED, selectedState)
refreshSource()
}
/**
* Updates the display of data on the map after the FeatureCollection has been modified
*/
private fun refreshSource() {
val source: GeoJsonSource? = mapboxMap?.style?.getSourceAs(SOURCE_ID)
if (source != null && featureCollection != null) {
source.setGeoJson(featureCollection)
}
}
private fun repositionMapCamera(newTarget: Point) {
val newCameraPosition: CameraPosition = CameraPosition.Builder()
.target(LatLng(newTarget.latitude(), newTarget.longitude()))
.zoom(22.0)
.build()
mapboxMap?.animateCamera(
CameraUpdateFactory.newCameraPosition(newCameraPosition),
CAMERA_MOVEMENT_SPEED_IN_MILSECS
)
}
// Use fromJson() method to convert the GeoJSON file into a usable FeatureCollection object
#get:Throws(IOException::class)
private val featureCollectionFromJson: Unit
get() {
try {
// Use fromJson() method to convert the GeoJSON file into a usable FeatureCollection object
featureCollection = "list_of_locations.geojson".loadGeoJsonFromAsset()?.let {
FeatureCollection.fromJson(
it
)
}
} catch (exception: Exception) {
Log.e("MapActivity", "getFeatureCollectionFromJson: $exception")
}
}
private fun String.loadGeoJsonFromAsset(): String? {
Log.e("tag", "loadGeoJsonFromAsset")
return try {
// Load the GeoJSON file from the local asset folder
val `is`: InputStream = assets.open(this)
val size: Int = `is`.available()
val buffer = ByteArray(size)
`is`.read(buffer)
`is`.close()
String(buffer, charset("UTF-8"))
} catch (exception: Exception) {
Log.e("MapActivity", "Exception Loading GeoJSON: $exception")
exception.printStackTrace()
null
}
}
private fun setUpRecyclerViewOfLocationCards() {
Log.e("tag", "seUpRecyclerViewOfLocationCards")
// Initialize the recyclerview of location cards and a custom class for automatic card scrolling
locationsRecyclerView = findViewById(R.id.map_layout_rv)
locationsRecyclerView?.setHasFixedSize(true)
locationsRecyclerView?.layoutManager = LinearLayoutManagerWithSmoothScroller(this)
styleRvAdapter = listOfIndividualLocations?.let {
LocationRecyclerViewAdapter(
it,
applicationContext, this
)
}
locationsRecyclerView?.adapter = styleRvAdapter
val snapHelper: SnapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(locationsRecyclerView)
}
// Add the mapView's lifecycle to the activity's lifecycle methods
public override fun onResume() {
super.onResume()
mapView?.onResume()
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
public override fun onPause() {
super.onPause()
mapView?.onPause()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView?.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView?.onSaveInstanceState(outState)
}
companion object {
private val LOCKED_MAP_CAMERA_BOUNDS = LatLngBounds.Builder()
.include(LatLng(33.92307338188975, -86.30688718139171))
.include(
LatLng(
33.91948714730282,
-86.31129446659145
)
).build()
private val MOCK_DEVICE_LOCATION_LAT_LNG: LatLng =
LatLng(33.921466408978034, -86.30824978533974)
private const val CAMERA_MOVEMENT_SPEED_IN_MILSECS = 1200
private const val PROPERTY_SELECTED = "selected"
private const val ID_ROUTE_ICON = "route-icon-id"
private const val ID_SELECTED_ROUTE_ICON = "selected-route-icon-id"
private const val LAYER_ID = "LAYER_ID"
private const val ID_IMAGE_SOURCE = "image_source-id"
private const val ID_IMAGE_LAYER = "image_layer-id"
private const val ID_IMAGE_SOURCE_VANDALA = "image_source-id_vandala"
private const val ID_IMAGE_LAYER_VANDALA = "image_layer-id_vandala"
private const val SOURCE_ID = "route-location-source-id"
}
}
Thank you in advance.
You are almost there. You need a bit of code more.
On your function initRouteLocationIconSymbolLayer(), you have the following code:
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON), // <---- Here is where you have to change
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
You have only set your layers to have one icon. The MapBoxSDK allows you to play with the properties of the geojson file.
So, here we need to change how the icon image is generated for each type of icon
routeLocationSymbolLayer
.withProperties(
/* iconImage constructor, with some query-like code */
iconImage(
match(
get("icon"), // JSON Key to evaluate
literal("route_icon_1"), // default
stop("route_icon_1", "route_icon_1"), // stop(input,output)
stop("route_icon_2", "route_icon_2")
)
),
/*the remaining properties you declared*/
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f)
)
Source: MapBox Android SDK Example
I would like to know if there is a way of using the accelerometer of an Android device in order to set the direction of the displayed Google Maps ?
Here is how I get a reference to the Accelerometer :
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensorAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
if (sensorAccelerometer != null) sensorManager.registerListener(this, sensorAccelerometer, SensorManager.SENSOR_DELAY_NORMAL)
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER){
Log.d(TAG, "onSensorChanger ACCELEROMETER")
val accelX = event.values[0]
val accelY = event.values[1]
val accelZ = event.values[2]
textViewAdView.text = "aX=${accelX.toString()}\r\naY=${accelY.toString()}\r\naZ=${accelZ.toString()}"
}
}
Thanks !
You can just show the user's current location and direction of movement using googleMap.setMyLocationEnabled(true);
But if you want to turn the whole map, you can use updateCameraBearing():
https://stackoverflow.com/a/37486292/7434090
I already use this feature with the following Kotlin code :
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap.uiSettings.isCompassEnabled = true
mMap.uiSettings.isMapToolbarEnabled = true
mMap.uiSettings.isMyLocationButtonEnabled = true
try {
mMap.isMyLocationEnabled = true
}catch (e: SecurityException) {
}
}
I'll check the bearing stuff, thanks
I will try this when outside :
When getting a new location from a fusedLocationClient :
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
...
updateCamera(location.bearing)
....
private fun updateCamera(bearing : Float) {
val oldPos = mMap.getCameraPosition()
val pos = CameraPosition.builder(oldPos).bearing(bearing).build()
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(pos))
textViewAdView.text = "Bearing=$bearing"
}
I used MapGestureListener to compare each time the coords of the clicked area and the coords of the marker and if they're at the same coords then I'm good to go but it just won't work because of the relative altitude change that doesn't assure the accuracy of getting the clicked position.
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
val clickedArea=mpView.geoCoordinatesFromPoint(Math.round(e!!.getX()), Math.round(e.getY()))
for (marker : MapMarker in markerList )
{
val dist=clickedArea!!.distanceTo(marker.position)
if (dist< 2)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
return false
}
})
I've managed to do it , i just had to call the "requestObjectsAtPoint" inside the MapGesture listener and do some workaround ,here's the code :
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
mpView.requestObjectsAtPoint(e!!.getX(),e.getY(), RequestObjectCallback { objects, x, y ->
for (marker : ViewObject in objects )
{
if (marker.objectType==1)
{
if ((marker as MapObject).mapObjectType==1)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
mSelectedMarker = marker as MapMarker
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
}
})
return true
}
})
I've got the following Unit function:
private fun loadFilterArea() {
val query = ALIAS.AREA
val type = preferences.getInt(query, TYPEVALUES.MY_LOCATION)
val longitude: Double
val latitude: Double
when (type) {
TYPEVALUES.MY_LOCATION -> {
...
longitude = point.longitude
latitude = point.latitude
...
}
TYPEVALUES.CUSTOM_RADIUS -> {
...
longitude = point.longitude
latitude = point.latitude
...
}
TYPEVALUES.INPUT -> {
...
longitude = preferences.getFloat(ALIAS.AREA_LONGITUDE, 0f).toDouble()
latitude = preferences.getFloat(ALIAS.AREA_LATITUDE, 0f) .toDouble()
...
} else -> {
...
}
}
//... the question is here ...
//... can't be compiled cause val's are not initialized
if (longitude != null && latitude != null) {
storeKey(ALIAS.AREA_LONGITUDE, longitude)
storeKey(ALIAS.AREA_LATITUDE, latitude)
}
}
So, how can check if values longitude and latitude are initialized ?
I don't want to move them from function to global scope and use them as lateinit with further ::latitude.isInitialized, and it seems like my version of Kotlin doesn't support local lateinit variables
If it makes sense to have null values for latitude and longitude for your use case, then you can define those variables as nullable Double, e.g., var latitude: Double? = null (the same for longitude). After that, it makes sense to check if they're not null, but the variable becomes no more immutable.
In order to keep immutability, you can do something like that:
val longitude: Double?
val latitude: Double?
when (type) {
TYPEVALUES.MY_LOCATION -> {
longitude = ...
latitude = ...
...
}
TYPEVALUES.CUSTOM_RADIUS -> {
longitude = ...
latitude = ...
...
}
TYPEVALUES.INPUT -> {
...
} else -> {
latitude = null
longitude = null
}
}
Otherwise, if you want them to be non-nullable, then you need to provide a meaningful, non-null value in all branches of the when statement.
You could use the power of when to store the value directly, so that declaration and assignments are done at the same time:
val (latitude, longitude) = when (type) {
TYPEVALUES.MY_LOCATION -> Pair(0.0, 0.0)
TYPEVALUES.CUSTOM_RADIUS -> Pair(1.0, 1.0)
else -> Pair(2d, 2d)
}
//Your latitude is a double
//Your longitude is a double