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
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容