目录
前言
当Mapbox升级到V10(我直接到当前的最新V10.3)版本后,就可以就此实现自己想要实现的功能。
官方文档 (docs.mapbox.com/android/map…)上的一些case就不在重复了
UISettings:
PreV10 通过MapView 拿到UISettings, 然后控制相关属性,V10 UISettings已经被移除了,不再统一管理,比较分散。
参见相关属性的控制:
mMapView.compass.visibility = false mMapView.logo.enabled = false mMapView.attribution.enabled = false mMapView.gestures.updateSettings { null }//控制无法触摸
PreV10 与 V10 的Camera 相关的animation (涉及到用到的Point,PreV10 之前有LatLn, Point 两个类,当时还觉得为啥弄两个,比较冗余,V10里面拿掉了LatLn保留Point,注意的是Point构造时 longitude为第一个prarams. )
普通的 camera
fun moveCamera( mapView: MapView, originalList: List<Point>, paddingStart: Double, paddingTop: Double, paddingEnd: Double, paddingBottom: Double ) { if (originalList.isEmpty()) { return } val mapboxMap = mapView.getMapboxMap() val camera = mapboxMap.cameraForCoordinates(originalList, EdgeInsets(paddingTop, paddingStart, paddingBottom, paddingEnd)) mapView.camera.flyTo(camera) // mapboxMap.setCamera(camera) }
camera动画之后,animationEnd 后的回调 需求时,传入animationOptions给 easeTo(),如下实现:
fun easeCamera(mapView:MapView, originalList: List<Point>, margin: Double, duration: Long, actionAfter: (() -> Unit)? = null){ if (originalList.isEmpty()) { return } val animationOptions = MapAnimationOptions.mapAnimationOptions { duration(duration) // owner(MapAnimationOwnerRegistry.GESTURES) animatorListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { actionAfter?.invoke() } }) } val mapboxMap = mapView.getMapboxMap() val camera = mapboxMap.cameraForCoordinates(originalList, EdgeInsets(margin, margin, margin, margin)) mapView.camera.easeTo(camera, animationOptions) }
LatlngBounds:
Pre10 之前的类,并且可以通过 List 创建一个 LatlngBounds, 然后 getCenter(), V10中直接拿掉了 LatLngBounds, 也就没有获取List 对应的 getCenter()了,没有仔细地去找API是否有相对应的替代,直接自己用扩展实现了一下:
@JvmStatic fun getCenter(originalList: List<Point>): Point? { if (originalList.isEmpty()) { return null } if (originalList.size < 2) { return originalList[0] } val multiPoint = MultiPoint.fromLngLats(originalList) val boundingBox = multiPoint.createBoundingBoxFromPoints() return boundingBox?.getCenter() }
涉及到两个类 BoundingBox 及 MultiPoint, 在两个类上添加扩展方法:
/** ** 通过 southwest、northeast 两个点构建 BoundingBox 对象 **/ fun MultiPoint.createBoundingBoxFromPoints(): BoundingBox?{ val coordinates = coordinates() if (coordinates.size > 1){ var minLat: Double = MAX_LATITUDE var minLon: Double = MAX_LONGITUDE var maxLat: Double = MIN_LATITUDE var maxLon: Double = MIN_LONGITUDE for (gp in coordinates) { val latitude: Double = gp.latitude() val longitude: Double = gp.longitude() minLat = Math.min(minLat, latitude) minLon = Math.min(minLon, longitude) maxLat = Math.max(maxLat, latitude) maxLon = Math.max(maxLon, longitude) } val southwest = Point.fromLngLat(minLon, minLat) val northeast = Point.fromLngLat(maxLon, maxLat) return BoundingBox.fromPoints(southwest, northeast) } return null } /** ** 扩展BoundingBox getCenter()方法。 **/ fun BoundingBox.getCenter(): Point { val centerLon = (southwest().longitude() + northeast().longitude())/2.0 val centerLat = (southwest().latitude() + northeast().latitude())/2.0 return Point.fromLngLat(centerLon, centerLat) }
Style设定Layer
V10 添加了DSL build 添加 source、layer,相对而言source、layer都比较集中在builder{}的 block里,实际应用中通常source、layer 的添加都是分离的,动态的,通过sourceID, layerId 找到对应的 source、layer然后修改里面的内容, 发现 addLayerBelow(layer, layId) 该方法不好使,Crash了,暂且不用它了。 SymbolLayer 相比之前的api接口,少了一个 style.addImages(imagesMap), 不再支持一次性添加多个Image,添加一个简单的扩展函数即可。
fun Style.addImages(imageMap: Map<String, Bitmap>) { imageMap.forEach { (t, u) -> addImage(t, u) } }
创建SymbolLayer 及 Source (Feature)的case
private fun createSymbolLayer( layerId: String, sourceId: String, isChangeStyle: Boolean, offsetY: Float ): SymbolLayer { return symbolLayer(layerId, sourceId){ iconImage(PROPERTY_ICON_NAME_PATTERN) iconAllowOverlap(true) iconSize(if (isChangeStyle) 1.0 else 0.0) iconIgnorePlacement(true) iconOffset(listOf(0.0, offsetY.toDouble())) } } // 控制iconSize 大小是方便做动画。 private fun createSymbolBitmap(latLng: Point, markerStr: String, markerParams: MarkerParams?) { val feature = Feature.fromGeometry(Point.fromLngLat(latLng.longitude(), latLng.latitude())) val bitmap = createMarkerBitmap(mContext, markerParams!!) feature.addStringProperty(PROPERTY_ICON_NAME, markerStr) imagesMap[markerStr] = bitmap markerCoordinates.add(feature) }
添加对应的 source, Layer
style.addSource( geoJsonSource(END_SOURCE_ID){ featureCollection(FeatureCollection.fromFeatures(markerCoordinates)) } ) style.addLayer(endSymbolLayer)
绘制轨迹LineLayer
同样添加Layer前需要添加 source, List 构建 FeatureCollection, 如下:
mMapView.getMapboxMap().getStyle()?.addSource( geoJsonSource(sourceId){ featureCollection( FeatureCollection.fromFeatures( arrayOf( Feature.fromGeometry( LineString.fromLngLats(points) ) ) ) ) lineMetrics(true) // 注意这里,绘制LineGradient 需要添加这行代码。 } )
添加单色的 LineLayer
mMapView.getMapboxMap().getStyle()?.addLayer( lineLayer(layerId, sourceId){ lineDasharray(listOf(0.01, 2.0)) lineCap(LineCap.ROUND) lineJoin(LineJoin.ROUND) lineWidth(TRACE_WIDTH.toDouble()) lineColor(pathColor) } )
绘制LineGradient, 先聊 Pre10的方案
/** * Defines a gradient with which to color a line feature. Can only be used with GeoJSON sources that specify `"lineMetrics": true`. * * @param expression an expression statement * @return property wrapper around an expression statement */ public static PropertyValue<Expression> lineGradient(Expression expression) { return new PaintPropertyValue<>("line-gradient", expression); } /** Produces continuous, smooth results by interpolating between pairs of input and output values ("stops"). The `input` may be any numeric expression (e.g., `["get", "population"]`). Stop inputs must be numeric literals in strictly ascending order. The output type must be `number`, `array<number>`, or `color`. Example usage: FillLayer fillLayer = new FillLayer("layer-id", "source-id"); fillLayer.setProperties( fillColor( interpolate( exponential(0.5f), zoom(), stop(1.0f, color(Color.RED)), stop(5.0f, color(Color.BLUE)), stop(10.0f, color(Color.GREEN)) ) ) ); Params: interpolation – type of interpolation number – the input expression stops – pair of input and output values Returns: expression See Also: Style specification */ public static Expression interpolate(@NonNull Interpolator interpolation, @NonNull Expression number, Stop... stops) { return interpolate(interpolation, number, Stop.toExpressionArray(stops)); }
以上只需创建 Expression.Stop[] stops, 根据List 中每个Point 的配速对应的色值,传入即可,绘制LineGradient。
V10 中不再有 Expression 下 的Stop类,所以无从谈起创建Stop[] 了,从官方的demo里看 , 最后跟了不定的 stop{}, 可以看见是一个可变参数,所以打算构建一个 stop{} 的数据。
private fun createHeatmapLayer(): HeatmapLayer { return heatmapLayer( HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID ) { maxZoom(9.0) sourceLayer(HEATMAP_LAYER_SOURCE) // Begin color ramp at 0-stop with a 0-transparancy color // to create a blur-like effect. heatmapColor( interpolate { linear() heatmapDensity() stop { literal(0) rgba(33.0, 102.0, 172.0, 0.0) } stop { literal(0.2) rgb(103.0, 169.0, 207.0) } stop { literal(0.4) rgb(209.0, 229.0, 240.0) } stop { literal(0.6) rgb(253.0, 219.0, 240.0) } stop { literal(0.8) rgb(239.0, 138.0, 98.0) } stop { literal(1) rgb(178.0, 24.0, 43.0) } } ) ... ... } }
其实 stop{} 的源码如下, 所以需要提供一个高阶函数的数组
fun stop(block: ExpressionBuilder.() -> Unit) { this@ExpressionBuilder.apply(block) } //给Expression.InterpolatorBuilder 添加一个 stops()的扩展方法即可 fun Expression.InterpolatorBuilder.stops(stopList:Array<(Expression.ExpressionBuilder.() -> Unit)?>){ stopList.forEach { stop -> stop?.let { apply(it) } } } //将以上的扩展方法作为参数传入 构建 Expression的最后一个参数, var colorExpression = Expression.interpolate{ linear() lineProgress() stops(colorStops) } //最后将 colorExpression 应用到构建lineLayer的 lineGradient(colorExpression) 作为参数即可,大功告成 mMapView.getMapboxMap().getStyle()?.addLayer( lineLayer(layerId, sourceId){ lineCap(LineCap.ROUND) lineJoin(LineJoin.ROUND) lineWidth(5.0) lineGradient(colorExpression) } )