251 lines
11 KiB
Swift
251 lines
11 KiB
Swift
//
|
|
// SeismicNetworksIntensityMapViewController.swift
|
|
// Earthquake Network
|
|
//
|
|
// Created by Andrea Busi on 27/02/25.
|
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import MapKit
|
|
|
|
class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
|
|
|
|
private let seismic: EQNSisma
|
|
private var shakemaps: [EQNShakemap] = []
|
|
private var pinStyle: MapPinStyle {
|
|
get { AppPreferences.shared.mapPinStyle }
|
|
set { AppPreferences.shared.mapPinStyle = newValue }
|
|
}
|
|
|
|
override var isFilterViewVisible: Bool { false }
|
|
override var isCloseButtonVisible: Bool { false }
|
|
|
|
// MARK: - UI
|
|
|
|
lazy var descriptionView: UIView = {
|
|
let view = UIView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.backgroundColor = AppTheme.Colors.pureBlue
|
|
|
|
let descriptionLabel = UILabel()
|
|
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
descriptionLabel.numberOfLines = 0
|
|
descriptionLabel.textColor = .white
|
|
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
|
|
descriptionLabel.textAlignment = .center
|
|
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
|
|
|
|
view.addSubview(descriptionLabel)
|
|
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 2.0).isActive = true
|
|
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -2.0).isActive = true
|
|
descriptionLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2.0).isActive = true
|
|
descriptionLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -2.0).isActive = true
|
|
|
|
return view
|
|
}()
|
|
|
|
// MARK: - Init
|
|
|
|
init(
|
|
seismic: EQNSisma
|
|
) {
|
|
self.seismic = seismic
|
|
super.init()
|
|
}
|
|
|
|
@MainActor required init?(coder: NSCoder) {
|
|
fatalError("Plase use init(seismic:) instead")
|
|
}
|
|
|
|
// MARK: - View Lifecycle
|
|
|
|
override func extraUI() {
|
|
super.extraUI()
|
|
|
|
view.addSubview(descriptionView)
|
|
descriptionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
|
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
|
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
|
}
|
|
|
|
override func configureUI() {
|
|
super.configureUI()
|
|
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
|
|
self?.dismiss(animated: true)
|
|
}))
|
|
navigationItem.rightBarButtonItems = [
|
|
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
|
|
self?.shareScreenshot()
|
|
})),
|
|
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
|
|
self?.nextPinStyle()
|
|
})),
|
|
]
|
|
}
|
|
|
|
override func registerMapAnnotationViews() {
|
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
|
}
|
|
|
|
override func loadDataSource() {
|
|
Task {
|
|
let result = try await APIService.shared.fetchShakemap(isoCode: seismic.isoCode)
|
|
elaborateShakemaps(result)
|
|
}
|
|
}
|
|
|
|
override func elaborateMapCenter() {
|
|
setMapCenter(for: seismic.coordinate, span: MKCoordinateSpan(latitudeDelta: 2, longitudeDelta: 2))
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func elaborateShakemaps(_ shakemaps: [EQNShakemap]) {
|
|
self.shakemaps = shakemaps
|
|
|
|
var shakemapPolyline = [MKPolyline]()
|
|
var shakemapAnnotations: [MKAnnotation] = []
|
|
for shakemap in shakemaps {
|
|
// create coordinates for current shakemap
|
|
let coordinates = zip(shakemap.lat, shakemap.lon).map { lat, lon in
|
|
CLLocationCoordinate2D(latitude: Double(lat) / 10_000.0, longitude: Double(lon) / 10_000.0)
|
|
}
|
|
|
|
let intensityColors = getColors(for: shakemap.intensity)
|
|
|
|
// create line to show on map
|
|
let polyline = ShakemapPolyline(coordinates: coordinates, count: coordinates.count)
|
|
polyline.intensity = shakemap.intensity
|
|
polyline.intensityColor = intensityColors.lineColor
|
|
shakemapPolyline.append(polyline)
|
|
|
|
// create annotation to show on top of the line
|
|
let middlePoint = coordinates[coordinates.count / 2]
|
|
let annotation = EQNMapAnnotationShakemap(coordinate: middlePoint, shakemap: shakemap)
|
|
annotation.intensityColor = intensityColors.lineColor
|
|
annotation.intensityTextColor = intensityColors.textColor
|
|
shakemapAnnotations.append(annotation)
|
|
}
|
|
|
|
let seismicAnnotation = EQNMapAnnotationSeismic(seismic: seismic)
|
|
shakemapAnnotations.append(seismicAnnotation)
|
|
|
|
// draw lines
|
|
mapView.addOverlays(shakemapPolyline)
|
|
updateMap(with: shakemapAnnotations)
|
|
}
|
|
|
|
private func nextPinStyle() {
|
|
pinStyle.next()
|
|
reloadMap()
|
|
}
|
|
|
|
private func shareScreenshot() {
|
|
let screenshot = createSnapshot(prepare: {
|
|
descriptionView.isHidden = true
|
|
}, restore: {
|
|
descriptionView.isHidden = false
|
|
})
|
|
|
|
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
|
present(controller, animated: true)
|
|
}
|
|
|
|
// MARK: - MKMapViewDelegate
|
|
|
|
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
|
switch annotation {
|
|
case let shakemapAnnotation as EQNMapAnnotationShakemap:
|
|
return shakemapAnnotation.toAnnotationView(mapView: mapView, style: .light)
|
|
case let seismicAnnotation as EQNMapAnnotationSeismic:
|
|
return seismicAnnotation.toAnnotationView(mapView: mapView, style: pinStyle)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
|
if let polyline = overlay as? ShakemapPolyline {
|
|
let renderer = MKPolylineRenderer(polyline: polyline)
|
|
renderer.strokeColor = polyline.intensityColor
|
|
renderer.lineWidth = 6.0
|
|
return renderer
|
|
}
|
|
return MKOverlayRenderer()
|
|
}
|
|
|
|
private func getColors(for intensity: Float) -> (textColor: UIColor, lineColor: UIColor) {
|
|
let shakemapColors: [String] = [
|
|
"#3E26A8","#3E27AC","#3F28AF","#3F29B2","#402AB4","#402BB7","#412CBA","#412DBD","#422EBF","#422FC2",
|
|
"#4330C5","#4331C8","#4332CA","#4433CD","#4434D0","#4535D2","#4537D5","#4538D7","#4639D9","#463ADC",
|
|
"#463BDE","#463DE0","#473EE1","#473FE3","#4741E5","#4742E6","#4744E8","#4745E9","#4746EB","#4848EC",
|
|
"#4849ED","#484BEE","#484CF0","#484EF1","#484FF2","#4850F3","#4852F4","#4853F5","#4854F6","#4756F7",
|
|
"#4757F7","#4759F8","#475AF9","#475BFA","#475DFA","#465EFB","#4660FB","#4661FC","#4562FC","#4564FD",
|
|
"#4465FD","#4367FD","#4368FE","#426AFE","#416BFE","#406DFE","#3F6EFF","#3E70FF","#3C71FF","#3B73FF",
|
|
"#3974FF","#3876FE","#3677FE","#3579FD","#337AFD","#327CFC","#317DFC","#307FFB","#2F80FA","#2F82FA",
|
|
"#2E83F9","#2E84F8","#2E86F8","#2E87F7","#2D88F6","#2D8AF5","#2D8BF4","#2D8CF3","#2D8EF2","#2C8FF1",
|
|
"#2C90F0","#2B91EF","#2A93EE","#2994ED","#2895EC","#2797EB","#2798EA","#2699E9","#269AE8","#259BE8",
|
|
"#259CE7","#249EE6","#249FE5","#23A0E5","#23A1E4","#22A2E4","#21A3E3","#20A5E3","#1FA6E2","#1EA7E1",
|
|
"#1DA8E1","#1DA9E0","#1CAADF","#1BABDE","#1AACDD","#19ADDC","#17AEDA","#16AFD9","#14B0D8","#12B1D6",
|
|
"#10B2D5","#0EB3D4","#0BB3D2","#08B4D1","#06B5CF","#04B6CE","#02B7CC","#01B7CA","#00B8C9","#00B9C7",
|
|
"#00BAC6","#01BAC4","#02BBC2","#04BBC1","#06BCBF","#09BDBD","#0DBDBC","#10BEBA","#14BEB8","#17BFB6",
|
|
"#1AC0B5","#1DC0B3","#20C1B1","#23C1AF","#25C2AE","#27C2AC","#29C3AA","#2BC3A8","#2CC4A6","#2EC4A5",
|
|
"#2FC5A3","#31C5A1","#32C69F","#33C79D","#35C79B","#36C899","#38C896","#39C994","#3BC992","#3DCA90",
|
|
"#40CA8D","#42CA8B","#45CB89","#48CB86","#4BCB84","#4ECC81","#51CC7F","#54CC7C","#57CC7A","#5ACC77",
|
|
"#5ECD74","#61CD72","#64CD6F","#67CD6C","#6BCD69","#6ECD66","#72CD64","#76CC61","#79CC5E","#7DCC5B",
|
|
"#81CC59","#84CC56","#88CB53","#8BCB51","#8FCB4E","#93CA4B","#96CA48","#9AC946","#9DC943","#A1C840",
|
|
"#A4C83E","#A7C73B","#ABC739","#AEC637","#B2C635","#B5C533","#B8C431","#BBC42F","#BEC32D","#C2C32C",
|
|
"#C5C22A","#C8C129","#CBC128","#CEC027","#D0BF27","#D3BF27","#D6BE27","#D9BE28","#DBBD28","#DEBC29",
|
|
"#E1BC2A","#E3BC2B","#E6BB2D","#E8BB2E","#EABA30","#ECBA32","#EFBA35","#F1BA37","#F3BA39","#F5BA3B",
|
|
"#F7BA3D","#F9BA3E","#FBBB3E","#FCBC3E","#FEBD3D","#FEBE3C","#FEC03B","#FEC13A","#FEC239","#FEC438",
|
|
"#FEC537","#FEC735","#FEC834","#FECA33","#FDCB32","#FDCD31","#FDCE31","#FCD030","#FBD22F","#FBD32E",
|
|
"#FAD52E","#F9D62D","#F9D82C","#F8D92B","#F7DB2A","#F7DD2A","#F6DE29","#F6E028","#F5E128","#F5E327",
|
|
"#F5E526","#F5E626","#F5E825","#F5E924","#F5EB23","#F5EC22","#F5EE21","#F6EF20","#F6F11F","#F6F21E",
|
|
"#F7F41C","#F7F51B","#F8F71A","#F8F818","#F9F916","#F9FB15"
|
|
]
|
|
|
|
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
|
|
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 255
|
|
let indexColor = if minIntensity == maxIntensity {
|
|
0
|
|
} else {
|
|
Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
|
|
}
|
|
|
|
let lineColor = UIColor(hexString: shakemapColors[indexColor]) ?? .white
|
|
let textColor: UIColor = indexColor < 65 ? .white : .black
|
|
|
|
return (textColor: textColor, lineColor: lineColor)
|
|
}
|
|
}
|
|
|
|
extension EQNMapAnnotationShakemap {
|
|
func toAnnotationView(
|
|
mapView: MKMapView,
|
|
style: MapPinStyle,
|
|
isUserSelection: Bool = false
|
|
) -> MKAnnotationView? {
|
|
switch style {
|
|
case .full, .light:
|
|
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
|
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
|
annotationView.magnitude = String(format: "%.1f", shakemap.intensity)
|
|
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
|
annotationView.magnitudeBackgroundColor = intensityColor
|
|
annotationView.canShowCallout = true
|
|
return annotationView
|
|
case .circle:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate class ShakemapPolyline: MKPolyline {
|
|
var intensity: Float = 0
|
|
var intensityColor: UIColor = .white
|
|
}
|