Commit 035622d5 by Dmitriy Stepanets

Merge branch 'master' into forecast-controller

parents 11ba9772 c897c67a
......@@ -15,7 +15,8 @@ public class LocationManager {
private let log = Logger(componentName: "LocationManager")
private let delegates = MulticastDelegate<LocationManagerDelegate>()
private let weatherUpdateSource: WeatherSource
private let defaultLocation = Location(coordinates: .init(latitude: 37.3230, longitude: -122.0322),
private let defaultLocation = Location(lastTimeUpdated: Date(),
coordinates: .init(latitude: 37.3230, longitude: -122.0322), // Cupertino
timeZone: TimeZone(abbreviation: "PST")!)
private var _currentLocation: Location? {
didSet {
......
......@@ -7,7 +7,8 @@
import Foundation
public struct CurrentWeather {
public struct CurrentWeather: Equatable, Hashable {
public var lastTimeUpdated: Date
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
......@@ -38,6 +39,7 @@ public struct CurrentWeather {
extension CurrentWeather: UpdatableModelObjectInTime {
public func mergedWith(incrementalChanges: CurrentWeather) -> CurrentWeather {
var result = self
result.lastTimeUpdated = incrementalChanges.lastTimeUpdated
result.date = incrementalChanges.date
result.timeZone = incrementalChanges.timeZone
result.weekDay = incrementalChanges.weekDay
......
......@@ -7,7 +7,8 @@
import Foundation
public struct DailyWeather {
public struct DailyWeather: Equatable, Hashable {
public var lastTimeUpdated: Date
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
......@@ -31,6 +32,7 @@ public struct DailyWeather {
extension DailyWeather: UpdatableModelObjectInTime {
public func mergedWith(incrementalChanges: DailyWeather) -> DailyWeather {
var result = self
result.lastTimeUpdated = incrementalChanges.lastTimeUpdated
result.date = incrementalChanges.date
result.timeZone = incrementalChanges.timeZone
result.weekDay = incrementalChanges.weekDay
......
......@@ -7,7 +7,8 @@
import Foundation
public struct DayTimeWeather {
public struct DayTimeWeather: Equatable, Hashable {
public var lastTimeUpdated: Date
public var dayTime: DayTime
public var date: Date
public var timeZone: TimeZone
......@@ -18,6 +19,7 @@ public struct DayTimeWeather {
public var temp: Temperature?
public init(dayTime: DayTime, hourly: HourlyWeather) {
self.lastTimeUpdated = hourly.lastTimeUpdated
self.dayTime = dayTime
self.date = hourly.date
self.timeZone = hourly.timeZone
......
......@@ -7,7 +7,8 @@
import Foundation
public struct HourlyWeather {
public struct HourlyWeather: Equatable, Hashable {
public var lastTimeUpdated: Date
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
......@@ -27,6 +28,7 @@ public struct HourlyWeather {
extension HourlyWeather: UpdatableModelObjectInTime {
public func mergedWith(incrementalChanges: HourlyWeather) -> HourlyWeather {
var result = self
result.lastTimeUpdated = incrementalChanges.lastTimeUpdated
result.date = incrementalChanges.date
result.timeZone = incrementalChanges.timeZone
result.weekDay = incrementalChanges.weekDay
......
......@@ -8,8 +8,9 @@
import Foundation
import CoreLocation
public struct Location: CustomStringConvertible {
public struct Location: Equatable, Hashable {
// MARK: - Data fields
public var lastTimeUpdated: Date
public var coordinates: CLLocationCoordinate2D?
public var imageName: String? = "ny_bridge" //we'll possibly need to switch to URL
......@@ -62,7 +63,10 @@ public struct Location: CustomStringConvertible {
public var cityId: String {
return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")"
}
}
// MARK: - CustomStringConvertible implementation
extension Location: CustomStringConvertible {
public var description: String {
if let coordinates = self.coordinates {
return String(format: "%@ (%.3f, %.3f)", cityId, coordinates.latitude, coordinates.longitude)
......@@ -73,12 +77,11 @@ public struct Location: CustomStringConvertible {
}
}
// MARK: - UpdatableModelObject implementation
extension Location: UpdatableModelObject {
public func mergedWith(incrementalChanges: Location) -> Location {
var result = self
result.lastTimeUpdated = incrementalChanges.lastTimeUpdated
// The preferense is given to self values rather than incrementalChanges in this class
result.coordinates = result.coordinates ?? incrementalChanges.coordinates
result.imageName = result.imageName ?? incrementalChanges.imageName
result.countryCode = result.countryCode ?? incrementalChanges.countryCode
......@@ -95,3 +98,17 @@ extension Location: UpdatableModelObject {
return result
}
}
extension CLLocationCoordinate2D: Hashable, Equatable {
private static let comparisonAccuracyLimit: CLLocationDegrees = 0.00001 // approximately 1 meter – more than enough for our purposes. https://en.wikipedia.org/wiki/Decimal_degrees
public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
// no point in comparing hashes first, I think, since making the hash might be as computationally expensive as this whole thing (I didn't profile it though).
return fabs(lhs.latitude - rhs.latitude) + fabs(lhs.longitude - rhs.longitude) < CLLocationCoordinate2D.comparisonAccuracyLimit
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.latitude)
hasher.combine(self.longitude)
}
}
......@@ -18,10 +18,10 @@ struct WdtDailySummariesArray: Codable {
case items = "daily_summary"
}
func toAppModel(timeZone: TimeZone) throws -> [DailyWeather] {
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [DailyWeather] {
var result = [DailyWeather]()
for item in items {
result.append(try item.toAppModel(timeZone: timeZone))
result.append(try item.toAppModel(timeZone: timeZone, updatedAt: updatedAt))
}
return result
}
......
......@@ -17,10 +17,10 @@ struct WdtHourlySummariesArray: Codable {
case items = "hourly_summary"
}
func toAppModel(timeZone: TimeZone) throws -> [HourlyWeather] {
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [HourlyWeather] {
var result = [HourlyWeather]()
for item in items {
result.append(try item.toAppModel(timeZone: timeZone))
result.append(try item.toAppModel(timeZone: timeZone, updatedAt: updatedAt))
}
return result
}
......
......@@ -41,7 +41,7 @@ struct WdtDailySummary: Codable {
case moonsetUtc = "solunar_moonset_utc"
}
public func toAppModel(timeZone: TimeZone) throws -> DailyWeather {
public func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> DailyWeather {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/YYYY"
dateFormatter.timeZone = timeZone
......@@ -59,7 +59,7 @@ struct WdtDailySummary: Codable {
throw WdtWeatherSourceError.dataMergeError("daily.weekday: \(self.weekDay)")
}
var result = DailyWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped)
var result = DailyWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped)
if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
}
......
......@@ -34,7 +34,7 @@ struct WdtHourlySummary: Codable {
case weatherCode = "wx_code"
}
func toAppModel(timeZone: TimeZone) throws -> HourlyWeather {
public func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> HourlyWeather {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone
......@@ -55,7 +55,7 @@ struct WdtHourlySummary: Codable {
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
var result = HourlyWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
var result = HourlyWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
}
......
......@@ -32,14 +32,14 @@ struct WdtLocation: Codable {
case hourlySummaries = "hourly_summaries"
}
public func toAppModel(timeZone: TimeZone) throws -> Location {
public func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> Location {
var coordinates: CLLocationCoordinate2D? = nil
if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") {
coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
var today = try surfaceObservation.toAppModel(timeZone: timeZone)
let dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone) ?? [HourlyWeather]()
var today = try surfaceObservation.toAppModel(timeZone: timeZone, updatedAt: updatedAt)
let dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [HourlyWeather]()
if let firstDay = dailyWeather.first {
today.minTemp = firstDay.minTemp
......@@ -50,7 +50,8 @@ struct WdtLocation: Codable {
today.moonPhase = firstDay.moonPhase
}
return Location(coordinates: coordinates,
return Location(lastTimeUpdated: updatedAt,
coordinates: coordinates,
imageName: nil,
countryCode: nil,
countryName: country,
......
......@@ -40,7 +40,7 @@ struct WdtSurfaceObservation: Codable {
case sunsetLocalTime = "sunset_local"
}
public func toAppModel(timeZone: TimeZone) throws -> CurrentWeather {
public func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> CurrentWeather {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone
......@@ -60,7 +60,7 @@ struct WdtSurfaceObservation: Codable {
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
var result = CurrentWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
var result = CurrentWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
}
......
......@@ -76,7 +76,8 @@ public class WdtWeatherSource: WeatherSource {
guard let wdtLocation = locationResponse.locations.first else {
return nil
}
let incrementalChanges = try wdtLocation.toAppModel(timeZone: location.timeZone)
let updateTime = Date()
let incrementalChanges = try wdtLocation.toAppModel(timeZone: location.timeZone, updatedAt: updateTime)
let updatedLocation = location.mergedWith(incrementalChanges: incrementalChanges)
return updatedLocation
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment