Skip to content
This repository has been archived by the owner on Jul 19, 2023. It is now read-only.

Commit

Permalink
Updated documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
devmaximilian committed Jan 1, 2020
1 parent 073c04c commit 5674990
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 70 deletions.
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# MetobsKit

A description of this package.
This package is a wrapper for the PMP3g API provided by [SMHI](https://smhi.se).


### Usage

Note that this is just a simple example demonstrating how the package can be used.

```swift
/// Get an instance of the weather forecast service
let service = ForecastService.shared

/// Request the current weather forecast for Stockholm
service.get(latitude: 59.3258414, longitude: 17.7018733)
.observe { result in
switch result {
case let .value(observation):
/// Use the current forecast
guard let forecast = observation.current else {
return
}

/// Get the air temperature
let temperature = forecast.get(parameter: .airTemperature)

/// Use the air temperature in some way
case let .error(error):
/// This error should be handled in a real use-case
fatalError("Failed to get forecast!")
}
}
```

### Attribution

This package utilizes data provided by [SMHI](https://smhi.se).

### Terms of use

Make sure to read SMHI:s [terms of use](https://www.smhi.se/data/oppna-data/villkor-for-anvandning) before using this package.

### Legal disclaimer

The developer and this package are not affiliated with or endorsed by SMHI. Any products and services provided through this package are not supported or warrantied by SMHI.

### License

See LICENSE for license details concerning this package and SMHI:s [terms of use](https://www.smhi.se/data/oppna-data/villkor-for-anvandning) for license details concerning the data provided by their API.
46 changes: 0 additions & 46 deletions Sources/MetobsKit/Helpers.swift

This file was deleted.

10 changes: 8 additions & 2 deletions Sources/MetobsKit/Models/Forecast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@
// SOFTWARE.
//

import Foundation

/// A `Forecast` is a collection of `Value`s for a set of `Parameter`s
public struct Forecast: Codable {
/// A timestamp for when the `Forecast` is valid
public let validTime: String

/// An array of `Value` instances
public let parameters: [Value]

/// Get `Value` for a `Parameter`
/// - Parameter parameter: The `Parameter` to get `Value` for
public func get(parameter: Parameter) -> Value {
return self.parameters.first { (value) -> Bool in
value.name == parameter
} ?? .unknown
}
}
2 changes: 0 additions & 2 deletions Sources/MetobsKit/Models/Geometry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// A typealias for an array of `Double`
public typealias GeometryCoordinate = [Double]

Expand Down
2 changes: 0 additions & 2 deletions Sources/MetobsKit/Models/Level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// A `Level` representing the measurement's base-level
public enum Level: String {
/// Above sea level
Expand Down
7 changes: 5 additions & 2 deletions Sources/MetobsKit/Models/Observation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// An `Observation` is a collection of `Forecast` instances
public struct Observation: Codable {
/// A timestamp for when the `Forecast` was approved
Expand All @@ -37,4 +35,9 @@ public struct Observation: Codable {

/// An array of `Forecast` instances that reflect the overall `Forecast` over time
public let timeSeries: [Forecast]

/// The current `Forecast`
public var current: Forecast? {
return self.timeSeries.first
}
}
4 changes: 1 addition & 3 deletions Sources/MetobsKit/Models/Parameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// A `Parameter` type
/// - Note: See https://opendata.smhi.se/apidocs/metfcst/parameters.html
public enum Parameter: String {
Expand Down Expand Up @@ -147,7 +145,7 @@ extension Parameter: Codable {
}
}

// MARK: Public methods
// MARK: Public functions

/// Encode `Parameter` using `Encoder`
public func encode(to encoder: Encoder) throws {
Expand Down
12 changes: 10 additions & 2 deletions Sources/MetobsKit/Models/Value.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// A `Value` value representation for a `Forecast` parameter
public struct Value: Codable {
/// A `Parameter` type representing the underlying value's type
Expand All @@ -40,4 +38,14 @@ public struct Value: Codable {

/// An array of raw parameter values
public let values: [Double]

/// The first value of the raw parameter values
public var value: Double {
return self.values.first ?? 0
}

/// Unknown `Value`
public static var unknown: Value {
return .init(name: .unknown, levelType: .unknownLevel, level: 0, unit: "", values: [])
}
}
73 changes: 65 additions & 8 deletions Sources/MetobsKit/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,44 @@
// SOFTWARE.
//

import Foundation

/// A service that provides weather forecasts
public class ForecastService: NSObject {
// MARK: Private properties

/// The service endpoint to send requests to
private var serviceEndpoint: String {
return "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/LONGITUDE/lat/LATITUDE/data.json"
}

/// A shared instance of forecast service
private static var sharedInstance: ForecastService?

// MARK: Private functions

/// Wrapps a network request as a `Promise<Value>`
private func request<Value: Decodable>(url: URL) -> Promise<Value> {
let promise = Promise<Value>()

URLSession.shared.dataTask(with: url) { data, response, error in

// Make sure request did not result in error
// Make sure request did not result in an error
if let error = error {
return promise.reject(with: error)
}

// Make sure acceptable response code present on response
// Make sure an acceptable response code is present on the response
if let response = response as? HTTPURLResponse {
if 400 ... 599 ~= response.statusCode {
let error = URLError(.init(rawValue: response.statusCode))
return promise.reject(with: error)
}
}

// Make sure the request returned data
// Make sure the request returned data (and not an empty response)
guard let data = data else {
return promise.reject(with: URLError(.zeroByteResource))
}

// Attempt to decode data
// Attempt to decode JSON data from the response
do {
let value: Value = try data.decoded()
return promise.resolve(with: value)
Expand All @@ -60,11 +71,45 @@ public class ForecastService: NSObject {
return promise
}

/// Constructs a `URL` to use when requesting forecasts
/// - Parameters:
/// - latitude: The latitude to use for request
/// - longitude: The longitude to use for request
private func buildURL(latitude: Double, longitude: Double) -> Promise<URL> {
// Remove decimals exceeding six positions as it will cause a 404 response
let values: (lat: Double, lon: Double) = (
lat: latitude.rounded(toPrecision: 6),
lon: longitude.rounded(toPrecision: 6)
)

let constructedURL = self.serviceEndpoint
.replacingOccurrences(of: "LONGITUDE", with: "\(values.lon)")
.replacingOccurrences(of: "LATITUDE", with: "\(values.lat)")

// Construct the URL
guard let url = URL(string: constructedURL) else {
return Promise<URL>(value: nil, error: URLError(.badURL))
}

return Promise<URL>(value: url)
}

// MARK: Public functions

/// Get the weather forecast `Observation` for a specific set of coordinates
/// - Note: See https://www.smhi.se/data/utforskaren-oppna-data/meteorologisk-prognosmodell-pmp3g-2-8-km-upplosning-api
/// for information about limitations (such as coordinate limitations)
/// - Parameters:
/// - latitude: The coordinate latitude
/// - longitude: The coordinate longitude
/// - Returns: A `Promise` with the value `Observation`
public func get(latitude: Double, longitude: Double) -> Promise<Observation> {
let observationPromise: Promise<Observation> = Promise<Observation>()

let urlPromise = buildURL(latitude: latitude, longitude: longitude)
// Request the forecast for the provided coordinates
let urlPromise = self.buildURL(latitude: latitude, longitude: longitude)

// Observe the request and forward the result
urlPromise.observe { urlResult in
switch urlResult {
case let .value(value):
Expand All @@ -84,4 +129,16 @@ public class ForecastService: NSObject {

return observationPromise
}

// MARK: Shared instance

/// A shared forecast-service instance
public static var shared: ForecastService {
guard let existingInstance = ForecastService.sharedInstance else {
let instance = ForecastService()
ForecastService.sharedInstance = instance
return instance
}
return existingInstance
}
}
13 changes: 11 additions & 2 deletions Sources/MetobsKit/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// SOFTWARE.
//

import Foundation

/// A `Future` wrapper for a `Value`
open class Future<Value> {
internal var result: Result<Value>? {
Expand Down Expand Up @@ -110,3 +108,14 @@ public extension Data {
return try JSONDecoder().decode(T.self, from: self)
}
}

/// An extension to add the rounded method
extension Double {
/// Rounds the `Double` to a specified precision-level (number of decimals)
/// - Note: This method is present as the forecast service only accepts a maximum of six decimals
/// - Parameter precision: The precision-level to use
func rounded(toPrecision precision: Int) -> Double {
let multiplier: Double = pow(10, Double(precision))
return (self * multiplier).rounded() / multiplier
}
}

0 comments on commit 5674990

Please sign in to comment.