Last active
April 6, 2020 02:52
-
-
Save mpiannucci/9dadb9d17cb920ee53a81f9d3c6b68db to your computer and use it in GitHub Desktop.
BuoyFinder Map Overlays
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import MapKit | |
import PlaygroundSupport | |
class SwellDataOverlay : NSObject, MKOverlay { | |
let coordinate: CLLocationCoordinate2D | |
let boundingMapRect: MKMapRect | |
let magnitude: Double | |
let angle: Double | |
let color: UIColor | |
init(stationLocation: CLLocationCoordinate2D, magnitude: Double, angle: Double, color: UIColor) { | |
coordinate = stationLocation | |
boundingMapRect = MKMapRect(origin: MKMapPoint(coordinate), size: MKMapSize(width: 200, height: 200)) | |
self.magnitude = magnitude | |
self.color = color | |
self.angle = angle | |
super.init() | |
} | |
} | |
class SwellDataOverlayRenderer : MKOverlayPathRenderer { | |
override func createPath() { | |
guard let swellDataOverlay = overlay as? SwellDataOverlay else { | |
return | |
} | |
let radius: CGFloat = CGFloat(6e5 * swellDataOverlay.magnitude) | |
let angle: CGFloat = CGFloat(swellDataOverlay.angle) | |
let angleWidth: CGFloat = 40.0 | |
let startAngle = (angle - (angleWidth * 0.5) - 90) * CGFloat.pi / 180.0 | |
let endAngle = (angle + (angleWidth * 0.5) - 90) * CGFloat.pi / 180.0 | |
let path = CGMutablePath() | |
let originMapPoint = MKMapPoint(swellDataOverlay.coordinate) | |
let origin = point(for: originMapPoint) | |
path.move(to: origin) | |
path.addArc(center: origin, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) | |
path.addLine(to: origin) | |
self.path = path | |
} | |
} | |
class MapDelegate : NSObject, MKMapViewDelegate { | |
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { | |
let reuseIdentifier = "stationAnnotationView" | |
var stationAnnotationView: MKMarkerAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier, for: annotation) as? MKMarkerAnnotationView | |
if (stationAnnotationView == nil) { | |
stationAnnotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier) | |
} | |
stationAnnotationView?.markerTintColor = .systemGreen | |
stationAnnotationView?.glyphTintColor = .black | |
stationAnnotationView?.glyphImage = UIImage(systemName: "antenna.radiowaves.left.and.right") | |
return stationAnnotationView | |
} | |
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { | |
guard let swellDataOverlay = overlay as? SwellDataOverlay else { | |
return MKOverlayRenderer(overlay: overlay) | |
} | |
let renderer = SwellDataOverlayRenderer(overlay: swellDataOverlay) | |
renderer.lineCap = .round | |
renderer.lineWidth = 1.0 | |
renderer.strokeColor = swellDataOverlay.color.withAlphaComponent(0.5) | |
renderer.fillColor = swellDataOverlay.color.withAlphaComponent(0.3) | |
return renderer | |
} | |
} | |
struct StationMapView : UIViewRepresentable { | |
let station: Station | |
let mapDelegate = MapDelegate() | |
typealias UIViewType = MKMapView | |
func makeUIView(context: UIViewRepresentableContext<StationMapView>) -> MKMapView { | |
let mapView = MKMapView() | |
mapView.delegate = mapDelegate | |
let delta = 1.5 | |
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: "stationAnnotationView") | |
var region = MKCoordinateRegion() | |
region.center.latitude = station.location.latitude | |
region.center.longitude = station.location.longitude | |
region.span.latitudeDelta = delta | |
region.span.longitudeDelta = delta | |
mapView.setRegion( region, animated: true ) | |
let stationAnnotation = MKPointAnnotation() | |
stationAnnotation.coordinate = station.location | |
//stationAnnotation.title = station.id | |
stationAnnotation.subtitle = "\(station.id) • NDBC • Active" | |
mapView.addAnnotation(stationAnnotation) | |
let stationSecondSwellOverlay = SwellDataOverlay(stationLocation: station.location, magnitude: 0.5, angle: 165.0, color: .systemYellow) | |
mapView.addOverlay(stationSecondSwellOverlay) | |
let stationThirdSwellOverlay = SwellDataOverlay(stationLocation: station.location, magnitude: 0.8, angle: 205, color: .systemOrange) | |
mapView.addOverlay(stationThirdSwellOverlay) | |
let stationFirstSwellOverlay = SwellDataOverlay(stationLocation: station.location, magnitude: 1.0, angle: 180.0, color: .systemRed) | |
mapView.addOverlay(stationFirstSwellOverlay) | |
mapView.isScrollEnabled = false | |
mapView.isZoomEnabled = false | |
mapView.isPitchEnabled = false | |
return mapView | |
} | |
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<StationMapView>) { | |
// | |
} | |
} | |
struct ConditionTile : View { | |
let iconName: String | |
let title: String | |
let subtitle: String? | |
var body: some View { | |
VStack { | |
Image(systemName: iconName) | |
.resizable() | |
.scaledToFit() | |
.frame(width: 24, height: 24) | |
Text(title).allowsTightening(true).minimumScaleFactor(0.5).lineLimit(1) | |
Text(subtitle ?? "") | |
.allowsTightening(true) | |
.minimumScaleFactor(0.5) | |
.lineLimit(1) | |
} | |
.frame(width: 56, height: 56) | |
.padding() | |
.background(Color(.systemGray6)) | |
.cornerRadius(20.0) | |
} | |
} | |
struct StationView : View { | |
let station: Station | |
var body: some View { | |
return NavigationView { | |
ScrollView{ | |
HStack { | |
Image(systemName: "location.north.fill") | |
.resizable() | |
.rotationEffect(.degrees(170 - 180)) | |
.frame(width: 36, height: 36) | |
.padding() | |
VStack (alignment: .leading ) { | |
Text("4.8 ft @ 7 s SSE") | |
.font(Font.system(size: 22.0)) | |
.bold() | |
Text("Significant Wave Height") | |
.font(.subheadline) | |
.foregroundColor(Color(.systemGray)) | |
.minimumScaleFactor(0.5) | |
Text("Reported 12:30 PM") | |
.font(.subheadline) | |
.foregroundColor(Color(.systemGray)) | |
.minimumScaleFactor(0.5) | |
} | |
}.padding() | |
StationMapView(station: station) | |
.frame(height: 150.0) | |
.cornerRadius(8.0) | |
.padding() | |
VStack { | |
HStack { | |
Image(systemName: "1.circle.fill") | |
.foregroundColor(Color(.systemRed).opacity(0.7)) | |
Text("3.8 ft @ 7 s 165° SSE") | |
.font(.callout) | |
} | |
HStack { | |
Image(systemName: "2.circle.fill") | |
.foregroundColor(Color(.systemOrange).opacity(0.7)) | |
Text("2 ft @ 9 s 205° SSW") | |
.font(.callout) | |
} | |
} | |
HStack { | |
ConditionTile(iconName: "thermometer", title: "42° F", subtitle: "Water") | |
ConditionTile(iconName: "wind", title: "14 mph", subtitle: "WNW") | |
ConditionTile(iconName: "gauge.badge.plus", title: "29.10 in", subtitle: "Rising") | |
}.padding() | |
Text("Previous Observations").font(.headline).padding() | |
} | |
.navigationBarTitle(station.name) | |
.navigationBarItems(trailing: Button (action: { | |
}) { | |
Image(systemName: "magnifyingglass") | |
}) | |
} | |
} | |
} | |
let blockIslandStation = Station(id: "44097", name: "Block Island, RI", latitude: 40.969, longitude: -71.127) | |
PlaygroundPage.current.setLiveView(StationView(station: blockIslandStation)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment