iOS 地图分割,四叉树数据结构C语言版

两个类,第一个OTMQuadTree是数据结构类(4叉树),第二个是树的操作OC类,OTMCoordinateTree。仍有许多地方需要完善,暂时还没有时间,后续抽时间完成。

OTMQuadTree.h文件


#ifndef OTMQuadTree_h
#define OTMQuadTree_h

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>

typedef struct OTMCoordinate {
    double lat;
    double lng;
}OTMCoordinate;
OTMCoordinate OTMCoordinateMake(double lat, double lng);

typedef struct OTMQuadTreeNodeData{
    OTMCoordinate coordinate;
    void* data;
}OTMQuadTreeNodeData;
OTMQuadTreeNodeData OTMQuadTreeNodeDataMake(double lat, double lng, void* data);
OTMQuadTreeNodeData OTMQuadTreeNodeDataMakeWithCoord(OTMCoordinate coord, void* data);

typedef struct OTMBoundingBox {
    struct OTMCoordinate leftTop;
    struct OTMCoordinate rightBottom;
}OTMBoundingBox;
OTMBoundingBox OTMBoundingBoxMakeWithCoordinate(OTMCoordinate leftTop , OTMCoordinate rightBottom);
OTMBoundingBox OTMBoundingBoxMake(double leftX, double leftY, double rightX , double rightY);

typedef struct OTMQuadTreeNode {
    struct OTMQuadTreeNode* leftTop;
    struct OTMQuadTreeNode* rightTop;
    struct OTMQuadTreeNode* leftBottom;
    struct OTMQuadTreeNode* rightBottom;
    OTMBoundingBox boundingBox;
    OTMQuadTreeNodeData* nodeDatas;
    int capacity;
    int count;
}OTMQuadTreeNode;
OTMQuadTreeNode* OTMQuadTreeNodeMake(OTMBoundingBox boundingBox, int capacity);

void freeNode(OTMQuadTreeNode* node);
bool nodeInserData(OTMQuadTreeNode* node, OTMQuadTreeNodeData data);

typedef void (^OTMQuadTreeReturnDataBlock)(OTMQuadTreeNodeData data);
typedef void (^OTMQuadTreeTraverseBlock)(OTMQuadTreeNode *node);

#pragma mark -
#pragma mark - Tree Function
void freeNode(OTMQuadTreeNode* node);
OTMQuadTreeNode* quadTreeBulidWithData(OTMQuadTreeNodeData *data, int count, int capacity, OTMBoundingBox box);
void quadTreeTraverse(OTMQuadTreeNode *node,OTMQuadTreeTraverseBlock block);
void gatherDataInBox(OTMQuadTreeNode *node, OTMBoundingBox box ,OTMQuadTreeReturnDataBlock block);
bool nodeInserData(OTMQuadTreeNode* node, OTMQuadTreeNodeData data);
void nodeSubDivide(OTMQuadTreeNode* node);

#pragma mark -
#pragma mark - Box Function
bool boxIntersectsBox(OTMBoundingBox box1, OTMBoundingBox box2);
bool boxContainData(OTMBoundingBox box, OTMQuadTreeNodeData data);
#endif /* OTMQuadTree_h */

OTMQuadTree.c文件

#include "OTMQuadTree.h"
#include <CoreFoundation/CoreFoundation.h>
OTMCoordinate OTMCoordinateMake(double lat, double lng) {
    OTMCoordinate coord;
    coord.lat = lat;
    coord.lng = lng;
    return coord;
}

OTMQuadTreeNodeData OTMQuadTreeNodeDataMakeWithCoord(OTMCoordinate coord, void* data) {
    OTMQuadTreeNodeData nodeData;
    nodeData.coordinate = coord;
    nodeData.data = data;
    return nodeData;
}

OTMQuadTreeNodeData OTMQuadTreeNodeDataMake(double lat, double lng, void* data) {
    return OTMQuadTreeNodeDataMakeWithCoord(OTMCoordinateMake(lat, lng), data);
};

OTMBoundingBox OTMBoundingBoxMakeWithCoordinate(OTMCoordinate leftTop , OTMCoordinate rightBottom) {
    OTMBoundingBox box;
    box.leftTop = leftTop;
    box.rightBottom = rightBottom;
    return box;
}

OTMBoundingBox OTMBoundingBoxMake(double leftX, double leftY, double rightX , double rightY) {
    OTMCoordinate lt = OTMCoordinateMake(leftX, leftY);
    OTMCoordinate rb = OTMCoordinateMake(rightX, rightY);
    return OTMBoundingBoxMakeWithCoordinate(lt, rb);
}

OTMQuadTreeNode* OTMQuadTreeNodeMake(OTMBoundingBox boundingBox, int capacity) {
    OTMQuadTreeNode *node = malloc(sizeof(OTMQuadTreeNode));
    node->leftTop = NULL;
    node->rightTop = NULL;
    node->leftBottom = NULL;
    node->rightBottom = NULL;
    node->boundingBox = boundingBox;
    node->capacity = capacity;
    node->count = 0;
    node->nodeDatas = malloc(sizeof(OTMQuadTreeNodeData) * capacity);
    return node;
}

#pragma Box Function
bool boxContainData(OTMBoundingBox box, OTMQuadTreeNodeData data) {
    double maxLat = box.leftTop.lat;
    double minLat = box.rightBottom.lat;
    double minLng = box.leftTop.lng;
    double maxLng = box.rightBottom.lng;
    bool latContain = data.coordinate.lat >= minLat && data.coordinate.lat <= maxLat;
    bool lngContain = data.coordinate.lng >= minLng && data.coordinate.lng <= maxLng;
    return latContain && lngContain;
}

bool boxIntersectsBox(OTMBoundingBox box1, OTMBoundingBox box2) {
    bool latInter = box1.leftTop.lat >= box2.rightBottom.lat && box1.rightBottom.lat <= box2.leftTop.lat;
    bool lngInter = box1.leftTop.lng <= box2.rightBottom.lng && box1.rightBottom.lng >= box2.leftTop.lng;
    return latInter && lngInter;
}

#pragma Tree Function

void nodeSubDivide(OTMQuadTreeNode* node) {
    
    double midLat = fabs(node->boundingBox.rightBottom.lat + node->boundingBox.leftTop.lat) / 2;
    double midLng = fabs(node->boundingBox.rightBottom.lng + node->boundingBox.leftTop.lng) / 2;
    OTMCoordinate midCoord = OTMCoordinateMake(midLat, midLng);
    
    OTMBoundingBox ltBox = OTMBoundingBoxMakeWithCoordinate(node->boundingBox.leftTop, midCoord);
    node->leftTop = OTMQuadTreeNodeMake(ltBox, node->capacity);
    
    OTMBoundingBox rtBox = OTMBoundingBoxMake(midLat, node->boundingBox.leftTop.lng, node->boundingBox.rightBottom.lat, midLng);
    node->rightTop = OTMQuadTreeNodeMake(rtBox, node->capacity);
    
    OTMBoundingBox lbBox = OTMBoundingBoxMake(node->boundingBox.leftTop.lat, midLng, midLat, node->boundingBox.rightBottom.lng);
    node->leftBottom = OTMQuadTreeNodeMake(lbBox, node->capacity);
    
    OTMBoundingBox rbBox = OTMBoundingBoxMake(midLat, midLng, node->boundingBox.rightBottom.lat, node->boundingBox.rightBottom.lng);
    node->rightBottom = OTMQuadTreeNodeMake(rbBox, node->capacity);
}

bool nodeInserData(OTMQuadTreeNode* node, OTMQuadTreeNodeData data) {
    
    // box不包涵data的点则失败
    if (!boxContainData(node->boundingBox, data)) {
        return false;
    }
    
    if (node->count < node->capacity) {
        node->nodeDatas[node->count] = data;
        node->count++;
        return true;
    }
    
    //父box满了
    if (node->leftTop == NULL) { //如果没有子box
        //分割
        nodeSubDivide(node);
    }
    
    //递归查找插入位置,直到成功
    if (nodeInserData(node->leftTop, data)) return true;
    if (nodeInserData(node->rightTop, data)) return true;
    if (nodeInserData(node->leftBottom, data)) return true;
    if (nodeInserData(node->rightBottom, data)) return true;
    
    return false;
}

void gatherDataInBox(OTMQuadTreeNode *node, OTMBoundingBox box ,OTMQuadTreeReturnDataBlock block) {
    if (!boxIntersectsBox(node->boundingBox, box)) {
        //两个box不相交不合并
        return;
    }
    //遍历node中所有的数据,判断某个数据是否在目标范围呢,在则执行block
    for (int i = 0 ; i < node->count; i++) {
        if (boxContainData(box, node->nodeDatas[i])) {
            if (block) {
                block(node->nodeDatas[i]);
            }
        }
    }
    //没有子区域了直接返回
    if (node->leftTop == NULL) {
        return;
    }
    
    //递归假如到子box中
    gatherDataInBox(node->leftTop, box, block);
    gatherDataInBox(node->rightTop, box, block);
    gatherDataInBox(node->leftBottom, box, block);
    gatherDataInBox(node->rightBottom, box, block);
}

void quadTreeTraverse(OTMQuadTreeNode *node,OTMQuadTreeTraverseBlock block){
    
    if (block) {
        block(node);
    }
    
    if (node->leftTop == NULL) {
        return;
    }
    
    quadTreeTraverse(node->leftTop,block);
    quadTreeTraverse(node->rightTop,block);
    quadTreeTraverse(node->leftBottom,block);
    quadTreeTraverse(node->rightBottom,block);
}

OTMQuadTreeNode* quadTreeBulidWithData(OTMQuadTreeNodeData *data, int count, int capacity, OTMBoundingBox box) {
    OTMQuadTreeNode *root = OTMQuadTreeNodeMake(box, capacity);
    for (int i = 0; i < count; i++) {
        nodeInserData(root, data[i]);
    }
    return root;
}

void freeNode(OTMQuadTreeNode* node) {
    if (node->leftTop != NULL) freeNode(node->leftTop);
    if (node->rightTop != NULL) freeNode(node->rightTop);
    if (node->leftBottom != NULL) freeNode(node->leftBottom);
    if (node->rightBottom != NULL) freeNode(node->rightBottom);
    
    for (int i = 0 ; i < node -> count; i++) {
        OTMQuadTreeNodeData nodeData = node->nodeDatas[i];
//        free(nodeData.data);
        //如果data是oc对象需要用CFRelease()释放,导入头文件#include <CoreFoundation/CoreFoundation.h>
        CFRelease(nodeData.data);
    }
    free(node->nodeDatas);
    free(node);
}

OTMCoordinateTree.h文件

#import <Foundation/Foundation.h>
#import "OTMQuadTree.h"
#import <MapKit/MapKit.h>
#import "OTMValueDefinitions.h"

@interface OTMCoordinateTree : NSObject
@property (nonatomic, assign) OTMQuadTreeNode *root;
@property (nonatomic, strong) NSArray<OTMPhotoModel *> *modelsArray;
@property (nonatomic, assign) CLLocationCoordinate2D requestLeftTopCoord;
@property (nonatomic, assign) CLLocationCoordinate2D requestRightBottomCoord;

- (NSArray *)clusteredAnnotationsWithinMapRect:(MKMapRect)rect withZoomScale:(double)zoomScale;
- (NSArray *)clusteredMaskAnnotationsOn:(MKMapView *)mapView;

- (void)buildTree;
- (void)rebuildTree;
- (void)clean;
@end

OTMCoordinateTree.m文件

#import "OTMCoordinateTree.h"
#import <CoreLocation/CoreLocation.h>

OTMBoundingBox boxFormMapRect(MKMapRect mapRect){
    CLLocationCoordinate2D topLeft = MKCoordinateForMapPoint(mapRect.origin);
    CLLocationCoordinate2D botRight = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(mapRect), MKMapRectGetMaxY(mapRect)));
//    CLLocationDegrees minLat = botRight.latitude;
//    CLLocationDegrees maxLat = topLeft.latitude;
//
//    CLLocationDegrees minLon = topLeft.longitude;
//    CLLocationDegrees maxLon = botRight.longitude;
    return OTMBoundingBoxMakeWithCoordinate(OTMCoordinateMake(topLeft.latitude, topLeft.longitude), OTMCoordinateMake(botRight.latitude, botRight.longitude));
//    return OTMBoundingBoxMake(maxLat, minLon, minLat, maxLon);
}

MKMapRect mapRectFormBox(OTMBoundingBox box) {
    MKMapPoint topLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake(box.leftTop.lat, box.leftTop.lng));
    MKMapPoint botRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake(box.rightBottom.lat, box.rightBottom.lng));

    return MKMapRectMake(topLeft.x, botRight.y, fabs(botRight.x - topLeft.x), fabs(botRight.y - topLeft.y));
}

NSInteger zoomScaleToZoomLevel(MKZoomScale scale)
{
    double totalTilesAtMaxZoom = MKMapSizeWorld.width / 256.0;
    NSInteger zoomLevelAtMaxZoom = log2(totalTilesAtMaxZoom);
    NSInteger zoomLevel = MAX(0, zoomLevelAtMaxZoom + floor(log2f(scale) + 0.5));
    
    return zoomLevel;
}

@implementation OTMCoordinateTree

- (void)buildTree {
    @autoreleasepool {
        if (self.modelsArray.count == 0) {
            return;
        }
        NSInteger count = self.modelsArray.count;

        OTMQuadTreeNodeData *dataArray = malloc(sizeof(OTMQuadTreeNodeData) * count);
        for (int i = 0 ; i < count; i++) {
            OTMPhotoModel *model = self.modelsArray[i];
            dataArray[i] = OTMQuadTreeNodeDataMake(model.coordinate.latitude, model.coordinate.longitude, (__bridge void *)(model));
        }
        
//        for (int i = 0; i < count; i++) {
//            OTMPhotoModel *model = (__bridge OTMPhotoModel *)(dataArray[i].data);
//            NSLog(@"omt test model index:%d url = %@",i,model.preview_url);
//        }
        
        OTMBoundingBox box = OTMBoundingBoxMake(self.requestLeftTopCoord.latitude,
                                                self.requestLeftTopCoord.longitude,
                                                self.requestRightBottomCoord.latitude,
                                                self.requestRightBottomCoord.longitude);
        
        self.root = quadTreeBulidWithData(dataArray,(int)count, 4, box);
    }
}

- (void)rebuildTree {
    [self clean];
    [self buildTree];
}

- (NSArray *)clusteredMaskAnnotationsOn:(MKMapView *)mapView {
    CGFloat gridWidth = kOTMPhotoAnnotationWidth;
    //计算每个图片的对应的maprect
    __block NSMutableArray *needRemoveModels = [[NSMutableArray alloc] init];
    [self.modelsArray enumerateObjectsUsingBlock:^(OTMPhotoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        OTMPhotoModel *currentModel = obj;
        if (obj.gathered) {
            return ;
        }

        CGPoint centerPt = [mapView convertCoordinate:obj.coordinate toPointToView:mapView];
        CGRect objClusterRect = CGRectMake(centerPt.x -gridWidth, centerPt.y - gridWidth, gridWidth * 2, gridWidth * 2);
        MKCoordinateRegion objClusterRegion = [mapView convertRect:objClusterRect toRegionFromView:mapView];
        MKMapRect mapRect = [self mapRectForCoordinateRegion:objClusterRegion];
        [self.modelsArray enumerateObjectsUsingBlock:^(OTMPhotoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (currentModel == obj || obj.gathered) {
                return ;
            }
            MKMapPoint point = MKMapPointForCoordinate(obj.coordinate);
            if (MKMapRectContainsPoint(mapRect, point)) {
                //有重叠的
                obj.gathered = YES;
                [currentModel.gatheredModels addObject:obj];
                [needRemoveModels addObject:obj];
            }
        }];
    }];
    
    if (needRemoveModels.count > 0) {
        NSMutableArray *arr = [NSMutableArray arrayWithArray:self.modelsArray];
        [arr removeObjectsInArray:needRemoveModels];
        self.modelsArray = arr;
    }
    return self.modelsArray;
}

- (MKMapRect)mapRectForCoordinateRegion:(MKCoordinateRegion)coordinateRegion
{
    CLLocationCoordinate2D topLeftCoordinate =
    CLLocationCoordinate2DMake(coordinateRegion.center.latitude
                               + (coordinateRegion.span.latitudeDelta/2.0),
                               coordinateRegion.center.longitude
                               - (coordinateRegion.span.longitudeDelta/2.0));
    
    MKMapPoint topLeftMapPoint = MKMapPointForCoordinate(topLeftCoordinate);
    
    CLLocationCoordinate2D bottomRightCoordinate =
    CLLocationCoordinate2DMake(coordinateRegion.center.latitude
                               - (coordinateRegion.span.latitudeDelta/2.0),
                               coordinateRegion.center.longitude
                               + (coordinateRegion.span.longitudeDelta/2.0));
    
    MKMapPoint bottomRightMapPoint = MKMapPointForCoordinate(bottomRightCoordinate);
    
    MKMapRect mapRect = MKMapRectMake(topLeftMapPoint.x,
                                      topLeftMapPoint.y,
                                      fabs(bottomRightMapPoint.x-topLeftMapPoint.x),
                                      fabs(bottomRightMapPoint.y-topLeftMapPoint.y));
    
    return mapRect;
}

- (NSArray *)clusteredAnnotationsWithinMapRect:(MKMapRect)rect withZoomScale:(double)zoomScale
{
    double cellSize = 88;
    /// zoomScale = point * (1 / mapPoint)  * (1 / size) ==  point / size / mapPoint
    double scaleFactor = zoomScale / cellSize;
    
    NSInteger minX = floor(MKMapRectGetMinX(rect) * scaleFactor);
    NSInteger maxX = floor(MKMapRectGetMaxX(rect) * scaleFactor);
    NSInteger minY = floor(MKMapRectGetMinY(rect) * scaleFactor);
    NSInteger maxY = floor(MKMapRectGetMaxY(rect) * scaleFactor);
    
    NSMutableArray *clusteredAnnotations = [[NSMutableArray alloc] init];
    for (NSInteger x = minX; x <= maxX; x++) {
        for (NSInteger y = minY; y <= maxY; y++) {
            MKMapRect mapRect = MKMapRectMake(x / scaleFactor, y / scaleFactor, 1.0 / scaleFactor, 1.0 / scaleFactor);
            
            __block double totalX = 0;
            __block double totalY = 0;
            __block int count = 0;
            
            NSMutableArray *models = [[NSMutableArray alloc] init];
            
            gatherDataInBox(self.root, boxFormMapRect(mapRect), ^(OTMQuadTreeNodeData data) {
                totalX += data.coordinate.lat;
                totalY += data.coordinate.lng;
                count ++;
                
                OTMPhotoModel *model = (__bridge OTMPhotoModel *)(data.data);
                [models addObject:model];
            });
            
            if (count == 1) {
                CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(totalX, totalY);
                OTMPhotoModel *model = (OTMPhotoModel *)models[0]; //默认使用第一个图片
                model.coordinate = coordinate;//修改显示的坐标
                [clusteredAnnotations addObject:model];
            }
            
            if (count > 1) {
                CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(totalX / count, totalY / count);
                OTMPhotoModel *model =(OTMPhotoModel *)models[0]; //默认使用第一个图片
                model.coordinate = coordinate; //修改显示的坐标
                model.count = @(count); //从新计算count
                [clusteredAnnotations addObject:model];
            }
        }
    }
    
    return [NSArray arrayWithArray:clusteredAnnotations];
}

- (void)clean {
    if (self.root) {
        freeNode(self.root);
    }
}

- (NSArray<OTMPhotoModel *> *)modelsArray {
    if (!_modelsArray) {
        _modelsArray = [NSArray array];
    }
    return _modelsArray;
}

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容