mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
170 lines
7.2 KiB
Swift
170 lines
7.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramCore
|
|
import MapKit
|
|
import SwiftSignalKit
|
|
|
|
public struct MapSnapshotMediaResourceId {
|
|
public let latitude: Double
|
|
public let longitude: Double
|
|
public let width: Int32
|
|
public let height: Int32
|
|
|
|
public var uniqueId: String {
|
|
return "map-\(latitude)-\(longitude)-\(width)x\(height)"
|
|
}
|
|
|
|
public var hashValue: Int {
|
|
return self.uniqueId.hashValue
|
|
}
|
|
}
|
|
|
|
public class MapSnapshotMediaResource {
|
|
public let latitude: Double
|
|
public let longitude: Double
|
|
public let width: Int32
|
|
public let height: Int32
|
|
|
|
public init(latitude: Double, longitude: Double, width: Int32, height: Int32) {
|
|
self.latitude = latitude
|
|
self.longitude = longitude
|
|
self.width = width
|
|
self.height = height
|
|
}
|
|
|
|
public var id: EngineMediaResource.Id {
|
|
return EngineMediaResource.Id(MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height).uniqueId)
|
|
}
|
|
}
|
|
|
|
let TGGoogleMapsOffset: Int = 268435456
|
|
let TGGoogleMapsRadius = Double(TGGoogleMapsOffset) / Double.pi
|
|
|
|
private func yToLatitude(_ y: Int) -> Double {
|
|
return ((Double.pi / 2.0) - 2 * atan(exp((Double(y - TGGoogleMapsOffset)) / TGGoogleMapsRadius))) * 180.0 / Double.pi;
|
|
}
|
|
|
|
private func latitudeToY(_ latitude: Double) -> Int {
|
|
return Int(round(Double(TGGoogleMapsOffset) - TGGoogleMapsRadius * log((1.0 + sin(latitude * Double.pi / 180.0)) / (1.0 - sin(latitude * Double.pi / 180.0))) / 2.0))
|
|
}
|
|
|
|
private func adjustGMapLatitude(_ latitude: Double, offset: Int, zoom: Int) -> Double {
|
|
let t: Int = (offset << (21 - zoom))
|
|
return yToLatitude(latitudeToY(latitude) + t)
|
|
}
|
|
|
|
private func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
Queue.concurrentDefaultQueue().async {
|
|
let options = MKMapSnapshotter.Options()
|
|
let latitude = adjustGMapLatitude(resource.latitude, offset: -10, zoom: 15)
|
|
options.region = MKCoordinateRegion(center: CLLocationCoordinate2DMake(latitude, resource.longitude), span: MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003))
|
|
options.mapType = .standard
|
|
options.showsPointsOfInterest = false
|
|
options.showsBuildings = true
|
|
options.size = CGSize(width: CGFloat(resource.width + 1), height: CGFloat(resource.height + 10))
|
|
options.scale = 2.0
|
|
let snapshotter = MKMapSnapshotter(options: options)
|
|
snapshotter.start(with: DispatchQueue.global(), completionHandler: { result, error in
|
|
if let image = result?.image {
|
|
if let data = image.jpegData(compressionQuality: 0.9) {
|
|
let tempFile = EngineTempBox.shared.tempFile(fileName: "image.jpg")
|
|
if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) {
|
|
subscriber.putNext(.moveTempFile(file: tempFile))
|
|
subscriber.putCompletion()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
disposable.set(ActionDisposable {
|
|
snapshotter.cancel()
|
|
})
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func chatMapSnapshotData(engine: TelegramEngine, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
|
|
return Signal<Data?, NoError> { subscriber in
|
|
let dataDisposable = engine.resources.custom(
|
|
id: resource.id.stringRepresentation,
|
|
fetch: EngineMediaResource.Fetch {
|
|
return fetchMapSnapshotResource(resource: resource)
|
|
},
|
|
cacheTimeout: .shortLived
|
|
).start(next: { next in
|
|
if next.availableSize != 0 {
|
|
subscriber.putNext(next.availableSize == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
|
}
|
|
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
|
|
|
return ActionDisposable {
|
|
dataDisposable.dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func chatMapSnapshotImage(engine: TelegramEngine, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
|
let signal = chatMapSnapshotData(engine: engine, resource: resource)
|
|
|
|
return signal |> map { fullSizeData in
|
|
return { arguments in
|
|
guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else {
|
|
return nil
|
|
}
|
|
|
|
var fullSizeImage: CGImage?
|
|
if let fullSizeData = fullSizeData {
|
|
let options = NSMutableDictionary()
|
|
options[kCGImageSourceShouldCache as NSString] = false as NSNumber
|
|
if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
|
|
fullSizeImage = image
|
|
}
|
|
|
|
if let fullSizeImage = fullSizeImage {
|
|
let drawingRect = arguments.drawingRect
|
|
var fittedSize = CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height)).aspectFilled(drawingRect.size)
|
|
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
|
|
fittedSize.width = arguments.boundingSize.width
|
|
}
|
|
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
|
|
fittedSize.height = arguments.boundingSize.height
|
|
}
|
|
|
|
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
|
|
|
|
context.withFlippedContext { c in
|
|
c.setBlendMode(.copy)
|
|
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
|
|
c.fill(arguments.drawingRect)
|
|
}
|
|
|
|
c.setBlendMode(.copy)
|
|
|
|
c.interpolationQuality = .medium
|
|
c.draw(fullSizeImage, in: fittedRect)
|
|
|
|
c.setBlendMode(.normal)
|
|
}
|
|
} else {
|
|
context.withFlippedContext { c in
|
|
c.setBlendMode(.copy)
|
|
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
|
|
c.fill(arguments.drawingRect)
|
|
|
|
c.setBlendMode(.normal)
|
|
}
|
|
}
|
|
}
|
|
|
|
addCorners(context, arguments: arguments)
|
|
|
|
return context
|
|
}
|
|
}
|
|
}
|