2

所以我正在编写一个基于 MapKit 的应用程序,它在地图上绘制一个叠加层。然而,很多覆盖图是动态的,因此被绘制的图块经常变化,所以我实现了一个自定义的 MKTileOverlay 和一个自定义的 MKTileOverlayRenderer。第一个处理存储平铺图像的 url 方案,第二个处理自定义 drawMapRect 实现。

我遇到的问题是我似乎在多个位置绘制相同的平铺图像。这是一个屏幕截图,可以帮助您直观地理解我的意思:(我知道瓷砖是颠倒的和向后的,我可以解决这个问题) iOS Simulator Screenshot

我已经更改了某些平铺图像,使它们具有不同的颜色并包含其平铺路径。您会注意到许多平铺图像在不同区域重复出现。

我一直试图弄清楚为什么会发生这种情况,所以按照我的代码路径,覆盖起点是非常标准的——ViewController 设置 addOverlay() 调用,它调用代表的 mapView(rendererForOverlay:) 返回我的自定义 MKTileOverlayRenderer 类,然后尝试调用我的 drawMapRect(mapRect:, zoomScale:, context)。然后它获取给定的 map_rect 并计算 map_rect 属于哪个图块,调用自定义 MKTileOverlay 类的 loadTileAtPath(),然后绘制生成的图块图像数据。这正是我的代码看起来也正在做的事情,所以我不确定我哪里出错了。也就是说,如果我不尝试实现自定义绘图并使用默认的 MKTileOverlayRenderer,它工作得非常好。不幸的是,那'

作为参考,这是我的自定义类中的相关代码:

我的自定义 MKTileOverlay 类

class ExploredTileOverlay: MKTileOverlay {

var base_path: String
//var tile_path: String?
let cache: NSCache = NSCache()
var point_buffer: ExploredSegment
var last_tile_path: MKTileOverlayPath?
var tile_buffer: ExploredTiles

init(URLTemplate: String?, startingLocation location: CLLocation, city: City) {
    let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
    let documentsDirectory: AnyObject = paths[0]
    self.base_path = documentsDirectory.stringByAppendingPathComponent("/" + city.name + "_tiles")
    if (!NSFileManager.defaultManager().fileExistsAtPath(base_path)) {
        try! NSFileManager.defaultManager().createDirectoryAtPath(base_path, withIntermediateDirectories: false, attributes: nil)
    }

    let new_point = MKMapPointForCoordinate(location.coordinate)
    self.point_buffer = ExploredSegment(fromPoint: new_point, inCity: city)
    self.tile_buffer = ExploredTiles(startingPoint: ExploredPoint(mapPoint: new_point, r: 50))
    self.last_tile_path = Array(tile_buffer.edited_tiles.values).last!.path
    super.init(URLTemplate: URLTemplate)
}

override func URLForTilePath(path: MKTileOverlayPath) -> NSURL {
    let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
    let tile_path = base_path + "/" + filled_template
    //print("fetching tile " + filled_template)
    if !NSFileManager.defaultManager().fileExistsAtPath(tile_path) {
        return NSURL(fileURLWithPath: "")
    }

    return NSURL(fileURLWithPath: tile_path)
}

override func loadTileAtPath(path: MKTileOverlayPath, result: (NSData?, NSError?) -> Void) {
    let url = URLForTilePath(path)
    let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
    let tile_path = base_path + "/" + filled_template
    if (url != NSURL(fileURLWithPath: tile_path)) {
        print("creating tile at " + String(path))
        let img_data: NSData = UIImagePNGRepresentation(UIImage(named: "small")!)!
        let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
        let tile_path = base_path + "/" + filled_template
        img_data.writeToFile(tile_path, atomically: true)
        cache.setObject(img_data, forKey: url)
        result(img_data, nil)
        return
    } else if let cachedData = cache.objectForKey(url) as? NSData {
        print("using cache for " + String(path))
        result(cachedData, nil)
        return
    } else {
        print("loading " + String(path) + " from directory")
        let img_data: NSData = UIImagePNGRepresentation(UIImage(contentsOfFile: tile_path)!)!
        cache.setObject(img_data, forKey: url)
        result(img_data, nil)
        return
    }
}

我的自定义 MKTileOverlayRenderer 类:

class ExploredTileRenderer: MKTileOverlayRenderer {

let tile_overlay: ExploredTileOverlay
var zoom_scale: MKZoomScale?
let cache: NSCache = NSCache()

override init(overlay: MKOverlay) {
    self.tile_overlay = overlay as! ExploredTileOverlay
    super.init(overlay: overlay)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(saveEditedTiles), name: "com.Coder.Wander.reachedMaxPoints", object: nil)
}

// There's some weird cache-ing thing that requires me to recall it 
// whenever I re-draw over the tile, I don't really get it but it works
override func canDrawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {
    self.setNeedsDisplayInMapRect(mapRect, zoomScale: zoomScale)
    return true
}

override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {
    zoom_scale = zoomScale
    let tile_path = self.tilePathForMapRect(mapRect, andZoomScale: zoomScale)
    let tile_path_string = stringForTilePath(tile_path)
    //print("redrawing tile: " + tile_path_string)
    self.tile_overlay.loadTileAtPath(tile_path, result: {
        data, error in
        if error == nil && data != nil {
            if let image = UIImage(data: data!) {
                let draw_rect = self.rectForMapRect(mapRect)
                CGContextDrawImage(context, draw_rect, image.CGImage)
                var path: [(CGMutablePath, CGFloat)]? = nil
                self.tile_overlay.point_buffer.readPointsWithBlockAndWait({ points in
                    let total = self.getPathForPoints(points, zoomScale: zoomScale, offset: MKMapPointMake(0.0, 0.0))
                    path = total.0
                    //print("number of points: " + String(path!.count))
                })
                if ((path != nil) && (path!.count > 0)) {
                    //print("drawing path")
                    for segment in path! {
                        CGContextAddPath(context, segment.0)
                        CGContextSetBlendMode(context, .Clear)
                        CGContextSetLineJoin(context, CGLineJoin.Round)
                        CGContextSetLineCap(context, CGLineCap.Round)
                        CGContextSetLineWidth(context, segment.1)
                        CGContextStrokePath(context)
                    }
                }
            }
        }
    })
}

我的辅助函数处理 zoomScale、zoomLevel、tile path 和 tile 坐标之间的转换:

func tilePathForMapRect(mapRect: MKMapRect, andZoomScale zoom: MKZoomScale) -> MKTileOverlayPath {
    let zoom_level = self.zoomLevelForZoomScale(zoom)
    let mercatorPoint = self.mercatorTileOriginForMapRect(mapRect)
    //print("mercPt: " + String(mercatorPoint))

    let tilex = Int(floor(Double(mercatorPoint.x) * self.worldTileWidthForZoomLevel(zoom_level)))
    let tiley = Int(floor(Double(mercatorPoint.y) * self.worldTileWidthForZoomLevel(zoom_level)))

    return MKTileOverlayPath(x: tilex, y: tiley, z: zoom_level, contentScaleFactor: UIScreen.mainScreen().scale)
}

func stringForTilePath(path: MKTileOverlayPath) -> String {
    return String(format: "%d_%d_%d", path.z, path.x, path.y)
}

func zoomLevelForZoomScale(zoomScale: MKZoomScale) -> Int {
    let real_scale = zoomScale / UIScreen.mainScreen().scale
    var z = Int((log2(Double(real_scale))+20.0))
    z += (Int(UIScreen.mainScreen().scale) - 1)
    return z
}

func worldTileWidthForZoomLevel(zoomLevel: Int) -> Double {
    return pow(2, Double(zoomLevel))
}

func mercatorTileOriginForMapRect(mapRect: MKMapRect) -> CGPoint {
    let map_region: MKCoordinateRegion = MKCoordinateRegionForMapRect(mapRect)

    var x : Double = map_region.center.longitude * (M_PI/180.0)
    var y : Double = map_region.center.latitude * (M_PI/180.0)
    y = log10(tan(y) + 1.0/cos(y))

    x = (1.0 + (x/M_PI)) / 2.0
    y = (1.0 - (y/M_PI)) / 2.0

    return CGPointMake(CGFloat(x), CGFloat(y))
}

我认为这是一个非常模糊的错误,所以没有太多运气找到其他面临类似问题的人。任何事情都会有帮助!

4

0 回答 0