如何使用 Mapbox Android SKD 从本地 geojson 属性设置图标类型
How do I set icon type from local geojson properties using Mapbox Android SKD
我有一个带有图标标记的漂亮地图和一个使用 Mapbox 的 recyclerview 设置。类似这样:
https://docs.mapbox.com/android/maps/examples/recyclerview-interaction
我的问题是我不知道如何根据我的本地设置图标 json 属性。我总共需要使用大约 40 个不同的图标,所以我认为最好的设置位置是在我的 geojson 属性中。我的 drawable-xxhdpi 文件夹中有图标的 png 文件。这是我的本地 geojson 文件的样子:
{
"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
]
}
},
我正在访问 geojson 文件,一切正常 运行,但我似乎无法根据 属性 中的“图标”设置我的图标geojson 文件。这可能吗?我为我的代码的长度道歉。这是我第一次在这里提问,我不确定我应该写多少代码 post。
这是我地图中的代码 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"
}
}
提前致谢。
你快到了。您还需要一些代码。
在您的函数 initRouteLocationIconSymbolLayer()
上,您有以下代码:
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON), // <---- Here is where you have to change
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
您只设置了图层有一个图标。 MapBoxSDK 允许您使用 geojson 文件的属性。
因此,这里我们需要更改为每种类型的图标生成图标图像的方式
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)
)
我有一个带有图标标记的漂亮地图和一个使用 Mapbox 的 recyclerview 设置。类似这样:
https://docs.mapbox.com/android/maps/examples/recyclerview-interaction
我的问题是我不知道如何根据我的本地设置图标 json 属性。我总共需要使用大约 40 个不同的图标,所以我认为最好的设置位置是在我的 geojson 属性中。我的 drawable-xxhdpi 文件夹中有图标的 png 文件。这是我的本地 geojson 文件的样子:
{
"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
]
}
},
我正在访问 geojson 文件,一切正常 运行,但我似乎无法根据 属性 中的“图标”设置我的图标geojson 文件。这可能吗?我为我的代码的长度道歉。这是我第一次在这里提问,我不确定我应该写多少代码 post。
这是我地图中的代码 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"
}
}
提前致谢。
你快到了。您还需要一些代码。
在您的函数 initRouteLocationIconSymbolLayer()
上,您有以下代码:
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON), // <---- Here is where you have to change
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
您只设置了图层有一个图标。 MapBoxSDK 允许您使用 geojson 文件的属性。
因此,这里我们需要更改为每种类型的图标生成图标图像的方式
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)
)