@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>PreviewsEnabled</key> |
|||
<false/> |
|||
</dict> |
|||
</plist> |
@ -0,0 +1,20 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>BuildLocationStyle</key> |
|||
<string>UseAppPreferences</string> |
|||
<key>CustomBuildLocationType</key> |
|||
<string>RelativeToDerivedData</string> |
|||
<key>DerivedDataCustomLocation</key> |
|||
<string>DerivedData</string> |
|||
<key>DerivedDataLocationStyle</key> |
|||
<string>WorkspaceRelativePath</string> |
|||
<key>IssueFilterStyle</key> |
|||
<string>ShowActiveSchemeOnly</string> |
|||
<key>LiveSourceIssuesEnabled</key> |
|||
<true/> |
|||
<key>ShowSharedSchemesAutomaticallyEnabled</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
@ -1,58 +0,0 @@ |
|||
{ |
|||
"images" : [ |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "16x16" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "2x", |
|||
"size" : "16x16" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "32x32" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "2x", |
|||
"size" : "32x32" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "128x128" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "2x", |
|||
"size" : "128x128" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "256x256" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "2x", |
|||
"size" : "256x256" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "512x512" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "2x", |
|||
"size" : "512x512" |
|||
} |
|||
], |
|||
"info" : { |
|||
"author" : "xcode", |
|||
"version" : 1 |
|||
} |
|||
} |
@ -1,6 +0,0 @@ |
|||
{ |
|||
"info" : { |
|||
"author" : "xcode", |
|||
"version" : 1 |
|||
} |
|||
} |
@ -1,177 +0,0 @@ |
|||
// |
|||
// ContentView.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import SwiftUI |
|||
|
|||
let DOMAIN_MIN: Double = 0 |
|||
let DOMAIN_MAX: Double = 300 |
|||
let HEIGHT: CGFloat = 20 |
|||
|
|||
let etab: [String: String] = [ |
|||
"Rain" : ":cloud_rain:".emojiUnescapedString, |
|||
"Snow" : ":cloud_snow:".emojiUnescapedString, |
|||
"Clear" : ":sunny:".emojiUnescapedString, |
|||
] |
|||
|
|||
struct ContentView_Previews: PreviewProvider { |
|||
static var previews: some View { |
|||
Group { |
|||
ContentView() |
|||
} |
|||
} |
|||
} |
|||
|
|||
struct ContentView: View { |
|||
|
|||
@ObservedObject var model = AppModel() |
|||
|
|||
var body: some View { |
|||
VStack { |
|||
List { |
|||
ForEach(model.readings) { |
|||
(r) in DayView(reading: r, min: model.min, max: model.max) |
|||
} |
|||
}.frame(width: 540, height: 330, alignment: .topLeading) |
|||
} |
|||
} |
|||
} |
|||
|
|||
struct DayView: View { |
|||
|
|||
let reading: F5DayCast |
|||
let min: Double |
|||
let max: Double |
|||
|
|||
var body : some View { |
|||
|
|||
HStack { |
|||
Text(reading.day.split(separator: " ")[0]).frame(width: 3*12, height: HEIGHT, alignment: .center) |
|||
Text(reading.day.split(separator: " ")[1]).frame(width: 3*12, height: HEIGHT, alignment: .center) |
|||
Text(etab[reading.condition, default: "Clear"]).frame(width: 2*12, height: HEIGHT, alignment: .center) |
|||
Spacer().frame(width: CGFloat(reading.low.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX)-0), |
|||
height: HEIGHT, alignment: .leading)//.background(Color.red) |
|||
Text(String(format: "%.f°F", reading.low)).frame(width: 3*12, height: HEIGHT, alignment: .trailing) |
|||
Spacer().frame(width: 10.0, height: HEIGHT, alignment: .leading) |
|||
GeometryReader { g in |
|||
Path { path in |
|||
let w = g.size.width |
|||
let h = g.size.height |
|||
path.move(to: CGPoint(x: 0, y: h/2)) |
|||
path.addLine(to: CGPoint(x:w, y: h/2)) |
|||
}.stroke(style: StrokeStyle(lineWidth: HEIGHT/2, lineCap: .round)).foregroundColor(Color.primary) |
|||
}.frame(width: CGFloat(reading.high.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX)-reading.low.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX)), |
|||
height: HEIGHT, alignment: .leading) |
|||
Spacer().frame(width: 10.0, height: HEIGHT, alignment: .leading) |
|||
Text(String(format: "%.f°F", reading.high)).frame(width: 3*12, height: HEIGHT, alignment: .leading) |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
struct F5DayCast: Identifiable { |
|||
|
|||
var id = UUID() |
|||
var day: String = "" |
|||
var low: Double = 0.0 |
|||
var high: Double = 0.0 |
|||
var condition: String = "" |
|||
|
|||
} |
|||
|
|||
extension Double { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |
|||
|
|||
extension FloatingPoint { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |
|||
|
|||
extension BinaryInteger { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |
|||
|
|||
class AppModel: NSObject, ObservableObject { |
|||
|
|||
// {"V1":"Wed 10/28","V2":38,"V3":47,"conditions":"Rain","c_alpha":0.75} |
|||
|
|||
@Published var readings: [F5DayCast] = [] |
|||
@Published var min: Double = Double.infinity |
|||
@Published var max: Double = -Double.infinity |
|||
|
|||
override init() { |
|||
super.init() |
|||
getReadings() |
|||
} |
|||
|
|||
func getReadings() { |
|||
|
|||
let url = URL(string: "https://tycho/data/f5.json")! |
|||
let task = URLSession.shared.dataTask(with: url) { data, response, error in |
|||
|
|||
if let error = error { |
|||
debugPrint("Error fetching data: \(error)") |
|||
return |
|||
} |
|||
|
|||
guard let httpResponse = response as? HTTPURLResponse, |
|||
(200...299).contains(httpResponse.statusCode) else { return } |
|||
|
|||
if let mimeType = httpResponse.mimeType, mimeType == "application/json", |
|||
|
|||
let data = data, |
|||
let res = String(data: data, encoding: .utf8) { |
|||
|
|||
let lines = res.split(whereSeparator: \.isNewline) |
|||
|
|||
self.readings = lines.map { line in |
|||
|
|||
let v = try? JSONDecoder().decode(JSON.self, from: line.data(using: .utf8)!) |
|||
|
|||
if let m = v!.V2.doubleValue { |
|||
self.min = (m < self.min) ? m : self.min |
|||
} |
|||
|
|||
if let m = v!.V3.doubleValue { |
|||
self.max = (m > self.max) ? m : self.max |
|||
} |
|||
|
|||
return( |
|||
F5DayCast( |
|||
day: v!.V1.stringValue ?? "ERROR", |
|||
low: v!.V2.doubleValue ?? -1.0, |
|||
high: v!.V3.doubleValue ?? -1.0, |
|||
condition: v!.conditions.stringValue ?? "ERROR" |
|||
) |
|||
) |
|||
|
|||
} |
|||
|
|||
debugPrint(24.rescale(from: self.min...self.max, to: 0...200)) |
|||
debugPrint(57.rescale(from: self.min...self.max, to: 0...200)) |
|||
debugPrint(30.rescale(from: self.min...self.max, to: 0...200)) |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
task.resume() |
|||
|
|||
} |
|||
|
|||
} |
@ -1,17 +0,0 @@ |
|||
// |
|||
// blahApp.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import SwiftUI |
|||
|
|||
@main |
|||
struct blahApp: App { |
|||
var body: some Scene { |
|||
WindowGroup { |
|||
ContentView().navigationTitle("F5 Weather • ECMWF • Berwick, Maine") |
|||
} |
|||
} |
|||
} |
@ -1,99 +0,0 @@ |
|||
// |
|||
// string+emoji.swift |
|||
// cli2 |
|||
// |
|||
// Created by boB Rudis on 10/28/20. |
|||
// |
|||
|
|||
import Foundation |
|||
|
|||
|
|||
extension String { |
|||
|
|||
fileprivate static var emojiUnescapeRegExp = createEmojiUnescapeRegExp() |
|||
fileprivate static var emojiEscapeRegExp = createEmojiEscapeRegExp() |
|||
fileprivate static var indexedShortnames = indexShortnames() |
|||
fileprivate static var indexedCodepoints = indexCodepoints() |
|||
|
|||
fileprivate static func createEmojiUnescapeRegExp() -> NSRegularExpression? { |
|||
let v = Emoji.allCases.flatMap { $0.shortnames } |
|||
.map { ":\(NSRegularExpression.escapedPattern(for: $0)):" } |
|||
do { |
|||
let regex = try NSRegularExpression(pattern: v.joined(separator: "|"), options: []) |
|||
return regex |
|||
} catch { |
|||
print(error) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
fileprivate static func createEmojiEscapeRegExp() -> NSRegularExpression? { |
|||
let v = Emoji.allCases.flatMap { $0.codepoints } |
|||
.map { NSRegularExpression.escapedPattern(for: $0) } |
|||
.sorted() |
|||
.reversed() |
|||
do { |
|||
let regex = try NSRegularExpression(pattern: v.joined(separator: "|"), options: []) |
|||
return regex |
|||
} catch { |
|||
print(error) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
fileprivate static func indexShortnames() -> [String: Int] { |
|||
let emojis = Emoji.allCases |
|||
return emojis.reduce([String: Int](), { dict, emoji -> [String: Int] in |
|||
guard let index = emojis.firstIndex(of: emoji) else { return [:] } |
|||
return emoji.shortnames.reduce(dict, { eDict, shortname -> [String: Int] in |
|||
var finalDict = eDict |
|||
finalDict[shortname] = index |
|||
return finalDict |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
fileprivate static func indexCodepoints() -> [String: Int] { |
|||
let emojis = Emoji.allCases |
|||
return emojis.reduce([String: Int](), { dict, emoji -> [String: Int] in |
|||
guard let index = emojis.firstIndex(of: emoji) else { return [:] } |
|||
return emoji.codepoints.reduce(dict, { eDict, codepoint -> [String: Int] in |
|||
var finalDict = eDict |
|||
finalDict[codepoint] = index |
|||
return finalDict |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
public var emojiUnescapedString: String { |
|||
var s = self as NSString |
|||
let ms = String.emojiUnescapeRegExp?.matches(in: self, options: [], range: NSMakeRange(0, s.length)) |
|||
ms?.reversed().forEach { m in |
|||
let r = m.range |
|||
let p = s.substring(with: r) |
|||
let px = p[p.index(after: p.startIndex) ..< p.index(before: p.endIndex)] |
|||
let index = String.indexedShortnames[String(px)] |
|||
if let i = index { |
|||
let e = Emoji.allCases[i] |
|||
s = s.replacingCharacters(in: r, with: e.codepoints.first!) as NSString |
|||
} |
|||
} |
|||
return s as String |
|||
} |
|||
|
|||
public var emojiEscapedString: String { |
|||
var s = self as NSString |
|||
let ms = String.emojiEscapeRegExp?.matches(in: self, options: [], range: NSMakeRange(0, s.length)) |
|||
ms?.reversed().forEach { m in |
|||
let r = m.range |
|||
let p = s.substring(with: r) |
|||
let index = String.indexedCodepoints[p] |
|||
if let i = index { |
|||
let e = Emoji.allCases[i] |
|||
s = s.replacingCharacters(in: r, with: ":\(e.shortnames.first!):") as NSString |
|||
} |
|||
} |
|||
return s as String |
|||
} |
|||
|
|||
} |
@ -1,6 +1,10 @@ |
|||
{ |
|||
"colors" : [ |
|||
{ |
|||
"color" : { |
|||
"platform" : "osx", |
|||
"reference" : "findHighlightColor" |
|||
}, |
|||
"idiom" : "universal" |
|||
} |
|||
], |
@ -0,0 +1,68 @@ |
|||
{ |
|||
"images" : [ |
|||
{ |
|||
"size" : "16x16", |
|||
"scale" : "1x", |
|||
"filename" : "icon_16x16.png", |
|||
"idiom" : "mac" |
|||
}, |
|||
{ |
|||
"scale" : "2x", |
|||
"filename" : "icon_16x16@2x.png", |
|||
"idiom" : "mac", |
|||
"size" : "16x16" |
|||
}, |
|||
{ |
|||
"filename" : "icon_32x32.png", |
|||
"scale" : "1x", |
|||
"size" : "32x32", |
|||
"idiom" : "mac" |
|||
}, |
|||
{ |
|||
"filename" : "icon_32x32@2x.png", |
|||
"size" : "32x32", |
|||
"idiom" : "mac", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"scale" : "1x", |
|||
"size" : "128x128", |
|||
"filename" : "icon_128x128.png" |
|||
}, |
|||
{ |
|||
"filename" : "icon_128x128@2x.png", |
|||
"scale" : "2x", |
|||
"size" : "128x128", |
|||
"idiom" : "mac" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"filename" : "icon_256x256.png", |
|||
"size" : "256x256", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"filename" : "icon_256x256@2x.png", |
|||
"scale" : "2x", |
|||
"size" : "256x256", |
|||
"idiom" : "mac" |
|||
}, |
|||
{ |
|||
"filename" : "icon_512x512.png", |
|||
"scale" : "1x", |
|||
"size" : "512x512", |
|||
"idiom" : "mac" |
|||
}, |
|||
{ |
|||
"scale" : "2x", |
|||
"idiom" : "mac", |
|||
"size" : "512x512", |
|||
"filename" : "icon_512x512@2x.png" |
|||
} |
|||
], |
|||
"info" : { |
|||
"version" : 1, |
|||
"author" : "xcode" |
|||
} |
|||
} |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 178 KiB |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"info" : { |
|||
"version" : 1, |
|||
"author" : "xcode" |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
// |
|||
// ContentView.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import SwiftUI |
|||
|
|||
struct ContentView: View { |
|||
|
|||
@EnvironmentObject var model: AppModel |
|||
|
|||
var body: some View { |
|||
VStack { |
|||
List { |
|||
ForEach(model.readings) { |
|||
(r) in DayView(reading: r, min: model.min, max: model.max) |
|||
} |
|||
} |
|||
}.frame( minWidth: 528, idealWidth: 528, maxWidth: 528, |
|||
minHeight: 256, idealHeight: 330, maxHeight: 330) |
|||
} |
|||
|
|||
} |
|||
|
|||
struct DayView: View { |
|||
|
|||
let reading: F5DayCast |
|||
let min: Double |
|||
let max: Double |
|||
|
|||
var body : some View { |
|||
|
|||
HStack { |
|||
Text(reading.day.split(separator: " ")[0]).frame(width: 3*12, height: HEIGHT, alignment: .center) |
|||
Text(reading.day.split(separator: " ")[1]).frame(width: 3*12, height: HEIGHT, alignment: .center) |
|||
Image(systemName: etab[reading.condition, default: "sun.max.fill"]) |
|||
.renderingMode(.original) |
|||
.font(.title3) |
|||
.frame(width: 3*12, height: HEIGHT, alignment: .center) |
|||
.foregroundColor(ecol[reading.condition, default: Color.yellow]) |
|||
Spacer().frame(width: CGFloat(reading.low.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX)-0), |
|||
height: HEIGHT, alignment: .leading) |
|||
Text(String(format: "%.f°", reading.low)).frame(width: 2*12, height: HEIGHT, alignment: .trailing) |
|||
Spacer().frame(width: 10.0, height: HEIGHT, alignment: .leading) |
|||
GeometryReader { g in |
|||
Path { path in |
|||
let w = g.size.width |
|||
let h = g.size.height |
|||
path.move(to: CGPoint(x: 0, y: h/2)) |
|||
path.addLine(to: CGPoint(x:w, y: h/2)) |
|||
}.stroke(style: StrokeStyle(lineWidth: HEIGHT/2, lineCap: .round)).foregroundColor(Color.primary) |
|||
}.frame(width: CGFloat(reading.high.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX) - |
|||
reading.low.rescale(from: self.min...self.max, to: DOMAIN_MIN...DOMAIN_MAX)), |
|||
height: HEIGHT, alignment: .leading) |
|||
Spacer().frame(width: 10.0, height: HEIGHT, alignment: .leading) |
|||
Text(String(format: "%.f°", reading.high)).frame(width: 2*12, height: HEIGHT, alignment: .leading) |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,29 @@ |
|||
// |
|||
// blahApp.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import SwiftUI |
|||
|
|||
var model = AppModel() |
|||
|
|||
@main |
|||
struct blahApp: App { |
|||
var body: some Scene { |
|||
WindowGroup { |
|||
ContentView() |
|||
.navigationTitle("F5 Weather • ECMWF • Berwick, Maine") |
|||
.environmentObject(model) |
|||
}.commands { |
|||
CommandMenu("Utilities") { |
|||
Button(action: { |
|||
model.getReadings() |
|||
}) { |
|||
Text("Refresh") |
|||
}.keyboardShortcut("r", modifiers: .command) |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
// |
|||
// globals.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import Foundation |
|||
import SwiftUI |
|||
import os |
|||
|
|||
let DOMAIN_MIN: Double = 0 |
|||
let DOMAIN_MAX: Double = 300 |
|||
let HEIGHT: CGFloat = 20 |
|||
|
|||
let logger = Logger() |
|||
|
|||
let etab: [String: String] = [ |
|||
"Rain" : "cloud.rain.fill", |
|||
"Snow" : "cloud.snow.fill", |
|||
"Clear" : "sun.max.fill" |
|||
] |
|||
|
|||
let ecol: [String: Color] = [ |
|||
"Rain" : Color.blue, |
|||
"Snow" : Color.blue, |
|||
"Clear" : Color.yellow |
|||
] |
@ -0,0 +1,92 @@ |
|||
// |
|||
// model.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import Foundation |
|||
|
|||
struct F5DayCast: Identifiable { |
|||
|
|||
var id = UUID() |
|||
var day: String = "" |
|||
var low: Double = 0.0 |
|||
var high: Double = 0.0 |
|||
var condition: String = "" |
|||
|
|||
} |
|||
|
|||
class AppModel: NSObject, ObservableObject { |
|||
|
|||
// {"V1":"Wed 10/28","V2":38,"V3":47,"conditions":"Rain","c_alpha":0.75} |
|||
|
|||
@Published var readings: [F5DayCast] = [] |
|||
@Published var min: Double = Double.infinity |
|||
@Published var max: Double = -Double.infinity |
|||
|
|||
override init() { |
|||
super.init() |
|||
getReadings() |
|||
} |
|||
|
|||
func getReadings() { |
|||
|
|||
let urlString = "https://rud.is/f5wx/conditions.json?q=\(Date().timeIntervalSince1970)" |
|||
|
|||
logger.info("Retrieving \(urlString)") |
|||
|
|||
let url = URL(string: urlString)! |
|||
let task = URLSession.shared.dataTask(with: url) { data, response, error in |
|||
|
|||
if let error = error { |
|||
debugPrint("Error fetching data: \(error)") |
|||
return |
|||
} |
|||
|
|||
guard let httpResponse = response as? HTTPURLResponse, |
|||
(200...299).contains(httpResponse.statusCode) else { return } |
|||
|
|||
if let mimeType = httpResponse.mimeType, mimeType == "application/json", |
|||
|
|||
let data = data, |
|||
let res = String(data: data, encoding: .utf8) { |
|||
|
|||
DispatchQueue.main.async { |
|||
|
|||
let lines = res.split(whereSeparator: \.isNewline) |
|||
|
|||
self.readings = lines.map { line in |
|||
|
|||
let v = try? JSONDecoder().decode(JSON.self, from: line.data(using: .utf8)!) |
|||
|
|||
if let m = v!.V2.doubleValue { |
|||
self.min = (m < self.min) ? m : self.min |
|||
} |
|||
|
|||
if let m = v!.V3.doubleValue { |
|||
self.max = (m > self.max) ? m : self.max |
|||
} |
|||
|
|||
return( |
|||
F5DayCast( |
|||
day: v!.V1.stringValue ?? "ERROR", |
|||
low: v!.V2.doubleValue ?? -1.0, |
|||
high: v!.V3.doubleValue ?? -1.0, |
|||
condition: v!.conditions.stringValue ?? "ERROR" |
|||
) |
|||
) |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
task.resume() |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,32 @@ |
|||
// |
|||
// utils.swift |
|||
// blah |
|||
// |
|||
// Created by hrbrmstr on 10/28/20. |
|||
// |
|||
|
|||
import Foundation |
|||
|
|||
extension Double { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |
|||
|
|||
extension FloatingPoint { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |
|||
|
|||
extension BinaryInteger { |
|||
func rescale(from input: ClosedRange<Self>, to output: ClosedRange<Self>) -> Self { |
|||
let x = (output.upperBound - output.lowerBound) * (self - input.lowerBound) |
|||
let y = (input.upperBound - input.lowerBound) |
|||
return x / y + output.lowerBound |
|||
} |
|||
} |