iOS 高德地图SDK多个行政区搜索+自定义地图

2025.01.15 工作变动原因,故将一些工作期间Tapd内部写的Wiki文档转移到个人博客。

新版本开发中,需要搜索多个行政区(市级),标注城市,绘制边界与大头针,记录一下开发相关的 重要代码(涉及高德地图SDK中的API部分),具体例子参考高德地图SDK的代码Demo

在这篇文档中,默认已导入高德地图SDK,如何导入和配置高德地图SDK不是这篇文章的重点,这里就不再赘述了。

一、MAMapView初始化 + 自定义地图

禁止大部份标注和手势,加载自定义地图。
阅读 高德地图SDK - 自定义地图 ,进入 控制台 - 我的地图样式 - 离线地图调用模式 - 发布 - 下载离线文件,把下载好的离线地图放入工程里。

    // 地图
    lazy var mapView: MAMapView = {
        let mapView = MAMapView(frame: self.view.bounds)
        mapView.delegate = self
        mapView.showsUserLocation = false
        mapView.showsCompass = false
        mapView.isShowsLabels = false
        mapView.setZoomLevel(4, animated: true)
        mapView.isRotateEnabled = false
        mapView.isRotateCameraEnabled = false
        mapView.showsScale = false
        
        // 开启自定义地图
        let path = Bundle.main.path(forResource: "myMapStyle.data", ofType: "") ?? ""
        let data: Data
        do {
            let data = try Data(NSData(contentsOfFile: path))
            let options = MAMapCustomStyleOptions()
            options.styleData = data
            mapView.setCustomMapStyleOptions(options)
            mapView.customMapStyleEnabled = true
        } catch {
            
        }
        return mapView
    }()

二、加载MAMapSearch,搜索行政区

创建AMapSearchAPI

    lazy var mapSearch: AMapSearchAPI = {
        let search = AMapSearchAPI()
        search?.delegate = self
        return search!
    }()
    

发起行政区查询,因为要搜索多个城市,故做一遍遍历搜索。

// 清除所有旧数据(大头针 / 边界)
mapView.removeAnnotations(mapView.annotations)
mapView.removeOverlays(mapView.overlays)

for region in runningTrajectoryModel.regions {
    // 行政区搜索
    let dist = AMapDistrictSearchRequest()
    // 搜索关键字
    dist.keywords = region
    // 返回边界
    dist.requireExtension = true
    // 查询的等级,0-返回当前行政区,1-返回所有下级行政区,2-返回所有下下级行政区
    dist.subdistrict = 0
    self.mapSearch.aMapDistrictSearch(dist)
}

三、行政区域查询代理回调处理

处理行政区搜索回调的数据,添加需要在地图上绘制的自定义对象。
注意这里要将 cityCount 替换成你将要搜索的城市的数据源数量!!!

    // 处理搜索位置数据(大头针位置 & 行政区划线位置)
    func onDistrictSearchDone(_ request: AMapDistrictSearchRequest!, response: AMapDistrictSearchResponse!) {
        
        if response.count == 0 {
            return
        }
        
        for aDistrict in response.districts {
            // 查询本行政区
            // 添加中心点大头针的数据对象
            let coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(aDistrict.center.latitude), longitude: CLLocationDegrees(aDistrict.center.longitude))
            let anno = MAPointAnnotation()
            anno.coordinate = coordinate
            anno.title = aDistrict.name
            mapView.addAnnotation(anno)
            
            // 添加绘制面的数据对象
            if aDistrict.polylines != nil {
                var polylines = Array<MAPolygon>()
                for polylineString in aDistrict.polylines {
                    let polyline = polygonForCoordinateString(coordinateString: polylineString)
                    polylines.append(polyline!)
                }
                mapView.addOverlays(polylines)
            }
        }
        
         // 搜到最后一个城市的时候,确定显示范围(本来用mapView.overlays的,但是overlays有时候一个城市不一定只有一个)
         // cityCount = 数据源中需要搜索的城市数量
        if let cityCount = 0, mapView.annotations.count == cityCount {
            mapView.showOverlays(mapView.overlays, animated: true)
        }
    }
    
    // 处理绘制面的边界数据对象
    func polygonForCoordinateString(coordinateString: String) -> MAPolygon? {
        if coordinateString.count == 0 { return nil }
        
        var count: UInt = 0
        let coordinates = coordinatesForString(coordinateString, coordinateCount: &count, token: ";")
        let polyline = MAPolygon(coordinates: coordinates, count: count)
        free(coordinates)
        return polyline
    }
    
    // 处理定位边界的坐标点
    func coordinatesForString(_ string: String, coordinateCount: inout UInt, token: String) -> UnsafeMutablePointer<CLLocationCoordinate2D>? {
        
        var str = ""
        
        if !(token == ",") {
            str = string.replacingOccurrences(of: token, with: ",")
        }
        else {
            str = string
        }
        
        let components = str.components(separatedBy: ",").map { Double($0) }
        let count = components.count / 2
        
        if count > 0 {
            coordinateCount = UInt(count)
        }

        // 使用 UnsafeMutablePointer 分配内存
        let coordinates = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: count)
        
        for i in 0..<count {
            if let longitude = components[2 * i], let latitude = components[2 * i + 1] {
                coordinates[i].longitude = longitude
                coordinates[i].latitude = latitude
            } else {
                // 如果转换失败,应处理错误或释放内存并返回 nil
                coordinates.deallocate()
                return nil
            }
        }
        return coordinates
    }

四、绘制自定义大头针 + 行政区边界

官方SDK中有提供默认的大头针样式,需求中需要 业务样式的大头针+城市名字,绘制自定义UIView中,需要注意规避View重用的问题
在绘制城市标题的时候,引入了 SnapKit约束布局,在代码示例中添加了注释,以下是绘制代码

    // 显示大头针
    func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
        
        if annotation.isKind(of: MAPointAnnotation.self) {
            let pointReuseIndetifier = "pointReuseIndetifier"
            var annotationView: MAPinAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: pointReuseIndetifier) as! MAPinAnnotationView?
            
            if annotationView == nil {
                annotationView = MAPinAnnotationView(annotation: annotation, reuseIdentifier: pointReuseIndetifier)
            }
                        
            annotationView!.isDraggable = false
            // 自定义大头针标注的图片
            annotationView!.image = UIImage(named: "Annotation_myStyle")
            
            // 如果已经有标题了,直接返回(解决重用)
            for subView in annotationView!.subviews {
                if let subLabel = subView as? UILabel {
                    // 城市标题
                    let regionString: String = annotation.title ?? ""
                    subLabel.text = regionString.replacingOccurrences(of: "市", with: "")
                    return annotationView!
                }
            }
            
            // 城市标题
            let regionString: String = annotation.title ?? ""
            let titleLabel = UILabel()
            titleLabel.text = regionString.replacingOccurrences(of: "市", with: "")
            titleLabel.font = .systemFont(ofSize: 12, weight: .semibold)
            titleLabel.textColor = .black
            annotationView!.addSubview(titleLabel)
            // 约束布局
            titleLabel.snp.makeConstraints { make in
                // 与大头针标注对齐
                make.centerX.equalTo(annotationView!)
                // 底部紧贴大头针顶部
                make.bottom.equalTo(annotationView!.snp.top)
            }
            return annotationView!
        }
        
        return nil
    }

绘制 行政区边界与填充颜色,可以直接使用官方的API MAPolygonRenderer 来进行绘制填充面,以下是示例代码:

    // 显示边界
    func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
        if overlay.isKind(of: MAPolygon.self) {
            let renderer: MAPolygonRenderer = MAPolygonRenderer(overlay: overlay)
            renderer.lineWidth = 1.0
            renderer.strokeColor = .cyan
            renderer.fillColor = .cyan.withAlphaComponent(0.5)
            return renderer
        }
        
        return nil
    }

五、最终完成效果

以下是实机演示的部分截图:

tapd_45228716_1732877912_140.png

这是 自定义离线地图(去掉所有道路河流),搜索行政区后最终的展示效果。

六、最后,完结撒花

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

推荐阅读更多精彩内容