幌斛者 发表于 2025-9-18 13:08:44

高德地图热力图的Flutter实现

高德地图SDK是有绘制热力图的功能的,但它的Flutter插件并没有封装这一块功能。我们业务中需要这个,就基于原插件修改了一下。

[*]Android部分从AMapFlutterMapPlugin这个类开始,发现它是通过AMapPlatformViewFactory这个工厂类来创建View的。
进入AMapPlatformViewFactory的create方法,里面有很多形如
if (params.containsKey("xxx")) {
                builder.setInitialMarkers(params.get("yyy"));
}的代码,这是提取从Flutter侧传过来的参数。我们加入一句
if (params.containsKey("heatmapTile")){
    builder.setHeatmapTile(params.get("heatmapTile"));
}heatmapTile是Flutter传过来的参数名,下面Flutter部分会提到。再到AMapOptionsBuilder中添加字段与相应的set方法
private Object heatmapTile;

@Override
public void setHeatmapTile(Object heatmapTile) {
    this.heatmapTile = heatmapTile;
}AMapOptionsBuilder这个类是实现了一个接口,上面这个setHeatmapTile是在接口中定义的,所以这里有个@Override。接口定义的更改这里就不再描述了。
继续深入到AMapOptionsBuilder的build方法中,工厂类是由这个方法创建的视图,在build方法中设置地图的属性,绘制点、线等。绘制热力图当然也在这个方法中,在return之前加入
if (null != heatmapTile){
    aMapPlatformView.getHeatmapController().add(heatmapTile);
}当然在AMapPlatformView中加入
private HeatmapController heatmapController;

public HeatmapController getHeatmapController() {
    return heatmapController;
}绘制热力图的方法就在这个HeatmapController类中,
public class HeatmapController implements MyMethodCallHandler {
    private final AMap amap;
    private static final String CLASS_NAME = "HeatmapController";

    public HeatmapController(AMap amap) {
      this.amap = amap;
    }

    public void add(Object mapTileObj){
      if (mapTileObj != null && amap != null){
            HeatmapOptionsBuilder builder = new HeatmapOptionsBuilder();
            HeatmapUtil.parseHeatmapOptions(mapTileObj,builder);

            if (builder.getPointCount() > 0) {
                HeatmapTileProvider provider = builder.build();
                TileOverlayOptions tileOverlayOptions = new TileOverlayOptions();
                tileOverlayOptions.tileProvider(provider);
                amap.addTileOverlay(tileOverlayOptions);
            }
      }
    }

    @Override
    public String[] getRegisterMethodIdArray() {
      return Const.METHOD_ID_LIST_FOR_HEATMAP;
    }


    @Override
    public void doMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
      LogUtil.i(CLASS_NAME, "doMethodCall===>" + call.method);
      switch (call.method) {
            case Const.METHOD_HEATMAP_UPDATE:
                Object tileData = call.argument("heatmapTile");
                amap.clear();
                add(tileData);
                result.success(null);
                break;
      }
    }
}View Code它的构造函数有个参数,是个地图对象,在前面AMapPlatformView的构造函数中加入下面一句,
heatmapController = new HeatmapController(amap);在HeatmapController中,除了add方法外,其他二个方法是刷新地图时用的。Const.METHOD_ID_LIST_FOR_HEATMAP中现在只有一个值,就是在doMethodCall中的 Const.METHOD_HEATMAP_UPDATE,字面值是“heatmap#update”,下面iOS及Flutter部分也会出现这个值,大家保持一致即可。然后在 AMapPlatformView的 initMyMethodCallHandlerMap中加入
methodIdArray = heatmapController.getRegisterMethodIdArray();
if (null != methodIdArray && methodIdArray.length > 0) {
    for (String methodId : methodIdArray) {
      myMethodCallHandlerMap.put(methodId, heatmapController);
    }
}把方法名称跟执行对象的关系存起来。在AMapPlatformView的onMethodCall方法中,根据key调用相应对象的doMethodCall。
上面的HeatmapController类中还有二个辅助类,是用外部参数来构造HeatmapTileProvider用的,
public class HeatmapOptionsBuilder {
   HeatmapTileProvider.Builder builder;
   private int _pointCount;

    public HeatmapOptionsBuilder() {
      this.builder = new HeatmapTileProvider.Builder();
    }

    public HeatmapTileProvider build(){
      return builder.build();
    }

    public void setData(List<LatLng> data){
      builder = builder.data(data);
      _pointCount = data.size();
    }

    public int getPointCount(){
      return _pointCount;
    }

    public void setGradient(Gradient gradient){
      builder = builder.gradient(gradient);
    }

    public void setRadius(int radius){
      builder = builder.radius(radius);
    }

    public void setTransparency(double transparency){
      builder = builder.transparency(transparency);
    }

    public void setWeightedData(List<WeightedLatLng> data){
      builder = builder.weightedData(data);
    }
}View Codepublic class HeatmapUtil {
    public static void parseHeatmapOptions(Object o,HeatmapOptionsBuilder builder){
      if (null == o) {
            return;
      }
      final Map<?, ?> data = ConvertUtil.toMap(o);

      Object radius = data.get("radius");
      if (radius != null){
            builder.setRadius(ConvertUtil.toInt(radius));
      }

      Object transparency = data.get("transparency");
      if (transparency != null){
            builder.setTransparency(ConvertUtil.toDouble(transparency));
      }

      Object noWeightData = data.get("data");
      if (noWeightData != null){
            builder.setData(ConvertUtil.toPoints(noWeightData));
      }

    }
}View Code注意一下对应关系就好。
 
[*]iOS部分 这部分从下往上叙述。
 首先定义结构,由传入的参数构造MAHeatMapTileOverlay对象
@interface AMapHeatmap : NSObject {
    /// 热力图的坐标点数组,key为@"points"
    CLLocationCoordinate2D *_coords;//坐标的数组指针
    NSUInteger _coordCount;//坐标的个数
}

///透明度
@property (nonatomic, assign) double transparency;

///半径
@property (nonatomic, assign) int radius;

/// 由以上数据生成的heatMapTileOverlay 对象
@property (nonatomic, strong,readonly) MAHeatMapTileOverlay *heatMapTileOverlay;


@endAMapHeatmap.h@interface AMapHeatmap ()

@property (nonatomic, strong, readwrite) MAHeatMapTileOverlay *heatMapTileOverlay;

@end

@implementation AMapHeatmap

- (instancetype)init {
    self = ;
    self.radius = 12;
    self.transparency = 0.6;
    return self;
}

- (void)postHookWith:(NSDictionary *)dict {
    NSArray *points = dict[@"data"];

    //如果经纬度点已经有值,需要手动释放内存
    if (_coords != NULL) {
      free(_coords);
      _coords = NULL;
    }
    _coordCount = points.count;
    if (_coordCount > 0){
      _coords = (CLLocationCoordinate2D*)malloc(_coordCount * sizeof(CLLocationCoordinate2D));
      for (NSUInteger index = 0; index < _coordCount; index ++) {
            NSArray *point = points;
            _coords = ;
      }
    }
}

- (void)dealloc {
    if (_coords != NULL) {
      free(_coords);
      _coords = NULL;
    }
}

- (MAHeatMapTileOverlay *)heatMapTileOverlay {
    if (_coordCount == 0) return nil;
   
    if (_heatMapTileOverlay == nil) {
      _heatMapTileOverlay = [ init];
      NSMutableArray* data = ;
      for (NSUInteger index = 0; index < _coordCount; index++) {
            MAHeatMapNode *node = [ init];
            node.coordinate = _coords;      
            node.intensity = 1;//设置权重
            ;
      }

      _heatMapTileOverlay.data = data;
      _heatMapTileOverlay.radius = self.radius;
      _heatMapTileOverlay.opacity = self.transparency;

      // MAHeatMapGradient *gradient = [ initWithColor:@[,, ] andWithStartPoints:@[@(0.2),@(0.5),@(0.9)]];
      // _heatMapTileOverlay.gradient = gradient;
    }
    return _heatMapTileOverlay;
}

@endAMapHeatmap.m然后实现一个Controller,把热力图的Overlay加入到map中
@class AMapHeatmap;

@interface AMapHeatmapController : NSObject

- (instancetype)init:(FlutterMethodChannel*)methodChannel
            mapView:(MAMapView*)mapView;

- (void)addOverlay:(NSDictionary*)nodesToAdd;

@endAMapHeatmapController.h@interface AMapHeatmapController ()
@property (nonatomic,strong) FlutterMethodChannel *methodChannel;
@property (nonatomic,strong) MAMapView *mapView;

@end

@implementation AMapHeatmapController

- (instancetype)init:(FlutterMethodChannel*)methodChannel
             mapView:(MAMapView*)mapView {
    self = ;
    if (self){
      _methodChannel = methodChannel;
      _mapView = mapView;

      __weak typeof(self) weakSelf = self;
      [_methodChannel addMethodName:@"heatmap#update" withHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult_Nonnull result) {
            id heatmapToAdd = call.arguments[@"heatmapTile"];
            if (]) {
                ;
                ;
            }

            result(nil);
      }];
    }
    return self;
}

- (void)addOverlay:(NSDictionary*)nodesToAdd {   
    AMapHeatmap *heatmapModel = ];
    if (heatmapModel.heatMapTileOverlay == nil) return;
   
    ;
   
}

@endAMapHeatmapController.m在Controller中,除了add,同样有涉及刷新的方法。
最后改造一下AMapViewController,先在@interface中加入
@property (nonatomic,strong) AMapHeatmapController *heatmapController;在initWithFrame方法中,调用addOverlay
_heatmapController = [ init:_channel
                                                            mapView: _mapView];

id heatmapToAdd = args[@"heatmapTile"];
      if (]){
            ;
      }在rendererForOverlay方法加一个分支
else if (]) {
    MATileOverlayRenderer *tileOverlayRenderer = [ initWithTileOverlay:overlay];
    return tileOverlayRenderer;
}  
[*]Flutter部分 先定义widget中用来传递热力图数据的结构,有三个字段,分别表示数据点的数组、半径、透明度。主要有一个把类对象转化为map的方法
class HeatmapTile extends BaseOverlay {
final List<LatLng> data;
final int? radius;
final double? transparency;

HeatmapTile({required this.data, this.radius, this.transparency});

@override
Map<String, dynamic> toMap() {
    final Map<String, dynamic> json = <String, dynamic>{};

    void addIfPresent(String fieldName, dynamic value) {
      if (value != null) {
      json = value;
      }
    }

    json['data'] = _pointsToJson();
    addIfPresent('radius', radius);
    addIfPresent('transparency', transparency);
    return json;
}

dynamic _pointsToJson() {
    final List<dynamic> result = <dynamic>[];
    for (final LatLng point in data) {
      result.add(point.toJson());
    }
    return result;
}
}在AMapWidget这个高德地图的widget中加入
///热力图
final HeatmapTile? heatmapTile;更新一下构造函数。使用时候把热力图数据给widget的这个heatmapTile参数就行。
如果只是这样,当组件刷新的时候,热力图就没了,所以还需要在_MapState的didUpdateWidget方法中添加
_updateHeatmap();_updateHeatmap方法如下
void _updateHeatmap() async {
    final AMapController controller = await _controller.future;
    var tile = widget.heatmapTile;
    if (tile != null){
      controller._updateHeatmap(tile);
    }
}当然还要在AMapController中添加
Future<void> _updateHeatmap(HeatmapTile tile){
    return _methodChannel.updateHeatmap(tile, mapId: mapId);
}通过methodChannel调用原生的方法,在MethodChannelAMapFlutterMap中添加更新的方法
Future<void> updateHeatmap(
      HeatmapTile tile, {
      required int mapId,
      }) {
    return channel(mapId).invokeMethod<void>(
      'heatmap#update',
      { 'heatmapTile': tile.toMap() },
    );

 这样就完成了高德地图Flutter插件的改造,
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 高德地图热力图的Flutter实现