// // SegnalazioniMapViewController.swift // Earthquake Network // // Created by Andrea Busi on 06/03/21. // Copyright © 2021 Earthquake Network. All rights reserved. // import Foundation import MapKit class SegnalazioniMapViewController: EQNBaseMapViewController { struct MapCircle { let color: UIColor let circle: MKCircle } /// Contains circles and related colors to draw overlays on the map private var mapCircles = [MapCircle]() /// Reports currently showned on the map private var filteredReports = [EQNSegnalazione]() // MARK: - View Lifecycle override func viewDidLoad() { super.viewDidLoad() } // MARK: - Public override func registerMapAnnotationViews() { mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier) } override func loadDataSource() { guard let list = EQNManager.manager().elencoSelagnazioniManuali else { return } // set the base filter setDefaultFilter(for: list) // filter report based on selected filter let filterDate = filter.date filteredReports = list.filter { $0.date > filterDate } // create annotations to display on the map let annotations = filteredReports.compactMap { EQNMapAnnotationUserReport(report: $0) } // create circles to group cluster of reports and show on the map let mapCircles = elaborateCircles(for: filteredReports) addMapCircles(mapCircles) // update map and center updateMap(with: annotations) } override func elaborateMapCenter() { var centerLocation: CLLocation? // Se c'è un cluster distante dall'utente meno del raggio di notifica sulle segnalazioni, // allora mostro all'utente quel cluster if let userPosition = CLLocationManager().location { let nearestCluser = mapCircles .map { CLLocation(latitude: $0.circle.coordinate.latitude, longitude: $0.circle.coordinate.longitude) } .sorted(by: { abs(userPosition.distance(from: $0)) < abs(userPosition.distance(from: $1)) }) .first // controlliamo che sia inferiore al raggio impostato per le notifiche if let radius = Double(EQNNotificheSegnalazioniUtente.shared().distanzaPosizione), let nearestCluser = nearestCluser, abs(nearestCluser.distance(from: userPosition)) < radius { centerLocation = nearestCluser } } // altrimenti mostro il cluster più recente if centerLocation == nil, let newestReport = filteredReports.sorted(by: { $0.date > $1.date }).first { // cerco il cerchio che contiene la segnalazione più recente // tra i cerchi trovati, prendo quello più piccolo let newestCircle = mapCircles .map { $0.circle } .filter { (circle) -> Bool in let location = CLLocation(latitude: circle.coordinate.latitude, longitude: circle.coordinate.longitude) let distance = abs(newestReport.coordinate.distance(from: location)) return distance < circle.radius } .sorted(by: { $0.radius < $1.radius }) .first if let newestCircle = newestCircle { centerLocation = CLLocation(latitude: newestCircle.coordinate.latitude, longitude: newestCircle.coordinate.longitude) } } if let centerLocation = centerLocation { setMapCenter(for: centerLocation) } } override func didTapAnnotation(_ annotation: MKAnnotation) { guard let annotation = annotation as? EQNMapAnnotationUserReport, let report = annotation.report else { return } let difference = Int(Date().timeIntervalSince(report.date) / 60.0) let title = EQNUtility.formattedString(forTimeDifference: difference) + " - \(report.intensity.description)" var message = "" + "🏢 " + report.address + "\n⏱ " + EQNUtility.formattedDate(from: report.date) + " \(NSLocalizedString("share_yourtime", comment: ""))" if !report.message.isEmpty { message += "\n💬 \(report.message)" } let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("main_share", comment: ""), style: .default, handler: { [unowned self] _ in self.openShareActivity(for: report) })) alert.addAction(UIAlertAction(title: NSLocalizedString("official_close", comment: ""), style: .cancel, handler: nil)) present(alert, animated: true, completion: nil) } // MARK: - Private private func elaborateCircles(for reports: [EQNSegnalazione]) -> [MapCircle] { let vector_latitude = reports.map { $0.coordinate.coordinate.latitude } let vector_longitude = reports.map { $0.coordinate.coordinate.longitude } let vector_date = reports.map { $0.date } let vector_state = reports.map { $0.intensity.rawValue } let minutes: TimeInterval = filter.minutes var cluster_code = 0 var vector_cluster = [Int](repeating: 0, count: vector_latitude.count) for i in 0.. 0 { vector_cluster[i] = vector_cluster[j] } else { if vector_cluster[i] > 0 { vector_cluster[j] = vector_cluster[i] } else { cluster_code += 1 vector_cluster[i] = cluster_code vector_cluster[j] = cluster_code } } } } } } } //calcola i centri dei cluster e l'intensità di ciascun cluster var lat_centre = [Double](repeating: 0, count: cluster_code) var lon_centre = [Double](repeating: 0, count: cluster_code) var cluster_freq = [Int](repeating: 0, count: cluster_code) var cluster_intensity = [Double](repeating: 0, count: cluster_code) for k in 0.. 0 { lat_centre[k] = lat_centre[k]/Double(cluster_freq[k]) lon_centre[k] = lon_centre[k]/Double(cluster_freq[k]) cluster_intensity[k] = cluster_intensity[k]/Double(cluster_freq[k]) } } var lat_farest = [Double](repeating: 0, count: cluster_code) var lon_farest = [Double](repeating: 0, count: cluster_code) var max_distance = [Double](repeating: 0, count: cluster_code) //per ogni cluster calcola il punto più lontano dal centro for k in 0..= max_distance[k] { lat_farest[k] = vector_latitude[i] lon_farest[k] = vector_longitude[i] max_distance[k] = distance } } } } let circles = Array(0.. MapCircle in var value_distance = max_distance[i] / 20.0 if value_distance > 1.0 { value_distance = 1.0 } let value_intensity = (cluster_intensity[i]-1.0) / 2.0 let value_reference = max(value_distance, value_intensity) let color: UIColor if value_reference <= 0.5 { let red = round(value_reference * 510) color = UIColor(red: CGFloat(red / 255.0), green: 230.0/255.0, blue: 0.0, alpha: 1.0) } else { let green = round(230 - (value_reference - 0.5) * 460) color = UIColor(red: 255.0, green: CGFloat(green / 255.0), blue: 0.0, alpha: 1.0) } let centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i]) let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i]) let radius: CLLocationDistance = centre.distance(from: farest) + 4000 let circle = MKCircle(center: centre.coordinate, radius: radius) return MapCircle(color: color, circle: circle) } return circles } private func addMapCircles(_ circles: [MapCircle]) { // elimino vecchie circonferenze let previousCircles = mapCircles.map { $0.circle } mapView.removeOverlays(previousCircles) // !!note: is important to assign here the circles // otherwise `addOverlays` will not work mapCircles = circles // creo nuovi cerchi let overlays = circles.map { $0.circle } mapView.addOverlays(overlays) } private func getDeltaMinute(_ date: Date) -> TimeInterval { Date().timeIntervalSince(date) / 60.0 } private func openShareActivity(for report: EQNSegnalazione) { // create message to share let intensity = report.intensity.description let difference = Int(Date().timeIntervalSince(report.date) / 60.0) let time = EQNUtility.formattedString(forTimeDifference: difference) let message = [ NSLocalizedString("share_hashtag", comment: ""), intensity, NSLocalizedString("share_felt", comment: ""), report.address, time + ".", NSLocalizedString("share_notified", comment: "") ].joined(separator: " ") let controller = UIActivityViewController(activityItems: [message], applicationActivities: []) present(controller, animated: true) } // MARK: - Map override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? { guard let annotation = annotation as? EQNMapAnnotationUserReport else { return nil } let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView annotationView.image = annotation.image annotationView.title = annotation.title return annotationView } func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { if let overlay = overlay as? MKCircle, let mapCircle = mapCircles.first(where: { $0.circle == overlay }) { let circle = MKCircleRenderer(overlay: overlay) circle.strokeColor = mapCircle.color circle.fillColor = mapCircle.color.withAlphaComponent(0.1) circle.lineWidth = 2.0 return circle } return MKOverlayRenderer(overlay: overlay) } }