2022-01-17 14:03:48 +13:00
|
|
|
//
|
|
|
|
|
// LocalMBTileOverlay.swift
|
2022-06-09 22:11:54 -07:00
|
|
|
// MeshtasticApple
|
2022-01-17 14:03:48 +13:00
|
|
|
//
|
2023-01-13 22:30:10 -08:00
|
|
|
// Copyright(c) Joshua Pirihi 16/01/22.
|
2022-01-17 14:03:48 +13:00
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
import MapKit
|
|
|
|
|
import SQLite
|
|
|
|
|
|
2022-01-18 17:09:23 +13:00
|
|
|
extension MKMapRect {
|
|
|
|
|
init(coordinates: [CLLocationCoordinate2D]) {
|
|
|
|
|
self = MKMapRect()
|
|
|
|
|
var coordinates = coordinates
|
|
|
|
|
if !coordinates.isEmpty {
|
|
|
|
|
let first = coordinates.removeFirst()
|
|
|
|
|
var top = first.latitude
|
|
|
|
|
var bottom = first.latitude
|
|
|
|
|
var left = first.longitude
|
|
|
|
|
var right = first.longitude
|
|
|
|
|
coordinates.forEach { coordinate in
|
|
|
|
|
top = max(top, coordinate.latitude)
|
|
|
|
|
bottom = min(bottom, coordinate.latitude)
|
|
|
|
|
left = min(left, coordinate.longitude)
|
|
|
|
|
right = max(right, coordinate.longitude)
|
|
|
|
|
}
|
2023-03-06 10:33:18 -08:00
|
|
|
let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude: top, longitude: left))
|
|
|
|
|
let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude: bottom, longitude: right))
|
|
|
|
|
self = MKMapRect(x: topLeft.x, y: topLeft.y,
|
|
|
|
|
width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y)
|
2022-01-18 17:09:23 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-29 14:25:08 +13:00
|
|
|
enum MapTileError: Error {
|
|
|
|
|
case invalidFormat
|
|
|
|
|
case other
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
class LocalMBTileOverlay: MKTileOverlay {
|
|
|
|
|
|
|
|
|
|
var path: String!
|
|
|
|
|
var mb: Connection!
|
2022-01-18 17:09:23 +13:00
|
|
|
private var _boundingMapRect: MKMapRect!
|
|
|
|
|
override var boundingMapRect: MKMapRect {
|
2023-03-06 13:26:04 -08:00
|
|
|
return _boundingMapRect
|
2022-01-18 17:09:23 +13:00
|
|
|
}
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-29 14:25:08 +13:00
|
|
|
init?(mbTilePath path: String) {
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
super.init(urlTemplate: nil)
|
|
|
|
|
self.path = path
|
|
|
|
|
do {
|
|
|
|
|
self.mb = try Connection(self.path, readonly: true)
|
|
|
|
|
let metadata = Table("metadata")
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
let name = Expression<String>("name")
|
|
|
|
|
let value = Expression<String>("value")
|
2023-03-06 10:33:18 -08:00
|
|
|
|
|
|
|
|
// make sure it's raster
|
2022-01-29 14:25:08 +13:00
|
|
|
let formatQuery = try mb.pluck(metadata.select(value).filter(name == "format"))
|
2023-12-08 12:36:42 -08:00
|
|
|
if formatQuery?[value] == nil || (formatQuery![value] != "jpeg" && formatQuery![value] != "jpg" && formatQuery![value] != "png") {
|
2022-01-29 14:25:08 +13:00
|
|
|
throw MapTileError.invalidFormat
|
|
|
|
|
}
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
let minZQuery = try mb.pluck(metadata.select(value).filter(name == "minzoom"))
|
|
|
|
|
self.minimumZ = Int(minZQuery![value])!
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
let maxZQuery = try mb.pluck(metadata.select(value).filter(name == "maxzoom"))
|
|
|
|
|
self.maximumZ = Int(maxZQuery![value])!
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
self.isGeometryFlipped = true
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-18 17:09:23 +13:00
|
|
|
let boundingBoxString = try mb.pluck(metadata.select(value).filter(name == "bounds"))
|
|
|
|
|
let boundCoords = boundingBoxString![value].split(separator: ",")
|
|
|
|
|
let coords = [
|
|
|
|
|
CLLocationCoordinate2D(latitude: Double(boundCoords[1]) ?? 0,
|
|
|
|
|
longitude: Double(boundCoords[0]) ?? 0),
|
|
|
|
|
CLLocationCoordinate2D(latitude: Double(boundCoords[3]) ?? 0,
|
|
|
|
|
longitude: Double(boundCoords[2]) ?? 0)
|
|
|
|
|
]
|
|
|
|
|
self._boundingMapRect = MKMapRect(coordinates: coords)
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
} catch {
|
2024-05-31 16:42:34 -05:00
|
|
|
logger.error("Map tile error: \(error)")
|
2022-01-29 14:25:08 +13:00
|
|
|
return nil
|
2022-01-17 14:03:48 +13:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-18 17:09:23 +13:00
|
|
|
override func loadTile(at path: MKTileOverlayPath, result: @escaping (Data?, Error?) -> Void) {
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-17 14:03:48 +13:00
|
|
|
let tileX = Int64(path.x)
|
|
|
|
|
let tileY = Int64(path.y)
|
2023-01-13 22:30:10 -08:00
|
|
|
let tileZ = Int64(path.z)
|
2022-01-17 14:03:48 +13:00
|
|
|
let tileData = Expression<SQLite.Blob>("tile_data")
|
|
|
|
|
let zoomLevel = Expression<Int64>("zoom_level")
|
|
|
|
|
let tileColumn = Expression<Int64>("tile_column")
|
|
|
|
|
let tileRow = Expression<Int64>("tile_row")
|
2023-03-06 10:33:18 -08:00
|
|
|
|
2022-01-18 17:09:23 +13:00
|
|
|
if let dataQuery = try? self.mb.pluck(Table("tiles").select(tileData).filter(zoomLevel == tileZ).filter(tileColumn == tileX).filter(tileRow == tileY)) {
|
2023-03-06 10:33:18 -08:00
|
|
|
let data = Data(bytes: dataQuery[tileData].bytes, count: dataQuery[tileData].bytes.count)// dataQuery![tileData].bytes
|
2022-01-18 17:09:23 +13:00
|
|
|
result(data, nil)
|
2022-01-17 14:03:48 +13:00
|
|
|
} else {
|
2024-05-31 16:42:34 -05:00
|
|
|
logger.error("No tile here: x:\(tileX) y:\(tileY) z:\(tileZ)")
|
2022-01-18 17:09:23 +13:00
|
|
|
let error = NSError(domain: "LocalMBTileOverlay", code: 1, userInfo: ["reason": "no_tile"])
|
|
|
|
|
result(nil, error)
|
2022-01-17 14:03:48 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-25 17:56:57 -07:00
|
|
|
|
2023-08-26 23:17:30 -07:00
|
|
|
// public class CustomMapOverlaySource: MKTileOverlay {
|
2023-04-25 17:56:57 -07:00
|
|
|
//
|
|
|
|
|
// // requires folder: tiles/{mapName}/z/y/y,{tileType}
|
|
|
|
|
// private var parent: MapViewSwiftUI
|
|
|
|
|
// private let mapName: String
|
|
|
|
|
// private let tileType: String
|
|
|
|
|
// private let defaultTile: DefaultTile?
|
|
|
|
|
//
|
|
|
|
|
// public init(
|
|
|
|
|
// parent: MapViewSwiftUI,
|
|
|
|
|
// mapName: String,
|
|
|
|
|
// tileType: String,
|
|
|
|
|
// defaultTile: DefaultTile?
|
|
|
|
|
// ) {
|
|
|
|
|
// self.parent = parent
|
|
|
|
|
// self.mapName = mapName
|
|
|
|
|
// self.tileType = tileType
|
|
|
|
|
// self.defaultTile = defaultTile
|
|
|
|
|
// super.init(urlTemplate: "")
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// public override func url(forTilePath path: MKTileOverlayPath) -> URL {
|
|
|
|
|
// if let tileUrl = Bundle.main.url(
|
|
|
|
|
// forResource: "\(path.y)",
|
|
|
|
|
// withExtension: self.tileType,
|
|
|
|
|
// subdirectory: "tiles/\(self.mapName)/\(path.z)/\(path.x)",
|
|
|
|
|
// localization: nil
|
|
|
|
|
// ) {
|
|
|
|
|
// return tileUrl
|
|
|
|
|
// } else if let defaultTile = self.defaultTile, let defaultTileUrl = Bundle.main.url(
|
|
|
|
|
// forResource: defaultTile.tileName,
|
|
|
|
|
// withExtension: defaultTile.tileType,
|
|
|
|
|
// subdirectory: "tiles/\(self.mapName)",
|
|
|
|
|
// localization: nil
|
|
|
|
|
// ) {
|
|
|
|
|
// return defaultTileUrl
|
|
|
|
|
// } else {
|
|
|
|
|
// let urlstring = self.mapName+"\(path.z)/\(path.x)/\(path.y).png"
|
|
|
|
|
// return URL(string: urlstring)!
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2023-08-26 23:17:30 -07:00
|
|
|
// }
|