diff --git a/README.md b/README.md
index fb238fc..d0ba776 100644
--- a/README.md
+++ b/README.md
@@ -61,14 +61,14 @@ by providing a latitude and longitude.
For example, to get forecasts for all metrics for the Stationsplein in Utrecht,
use:
-```
+```http
GET /forecast?address=Stationsplein,Utrecht&metrics[]=all
```
or directly by using its geocoded position:
-```
+```http
GET /forecast?lat=52.0902&lon=5.1114&metrics[]=all
```
@@ -80,7 +80,7 @@ When querying, the metrics need to be selected. It can be one of: `AQI`, `NO2`,
Note that the parameter "array" notation as well as the repeated parameter
notation are supported. For example:
-```
+```http
GET /forecast?address=Stationsplein,Utrecht&metrics[]=AQI&metrics[]=pollen
GET /forecast?address=Stationsplein,Utrecht&metrics=AQI&metrics=pollen
GET /forecast?address=Stationsplein,Utrecht&metrics=all
@@ -143,13 +143,13 @@ Currently, only the `PAQI`, `pollen` and `UVI` metrics are backed by a map.
For example, to get the current pollen map with a crosshair on Stationsplein in
Utrecht, use:
-```
+```http
GET /map?address=Stationsplein,Utrecht&metric=pollen
```
or directly by using its geocoded position:
-```
+```http
GET /map?lat=52.0902&lon=5.1114&metric=pollen
```
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..292523d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,275 @@
+#![doc = include_str!("../README.md")]
+#![warn(
+ clippy::all,
+ missing_debug_implementations,
+ rust_2018_idioms,
+ rustdoc::broken_intra_doc_links
+)]
+#![deny(missing_docs)]
+
+use std::future::Future;
+use std::sync::{Arc, Mutex};
+
+use rocket::http::ContentType;
+use rocket::response::content::Custom;
+use rocket::serde::json::Json;
+use rocket::{get, routes, Build, Rocket, State};
+
+pub(crate) use self::forecast::Metric;
+use self::forecast::{forecast, Forecast};
+pub(crate) use self::maps::{mark_map, Maps, MapsHandle};
+use self::position::{resolve_address, Position};
+
+pub(crate) mod forecast;
+pub(crate) mod maps;
+pub(crate) mod position;
+pub(crate) mod providers;
+
+/// Handler for retrieving the forecast for an address.
+#[get("/forecast?
&")]
+async fn forecast_address(
+ address: String,
+ metrics: Vec,
+ maps_handle: &State,
+) -> Option> {
+ let position = resolve_address(address).await?;
+ let forecast = forecast(position, metrics, maps_handle).await;
+
+ Some(Json(forecast))
+}
+
+/// Handler for retrieving the forecast for a geocoded position.
+#[get("/forecast?&&", rank = 2)]
+async fn forecast_geo(
+ lat: f64,
+ lon: f64,
+ metrics: Vec,
+ maps_handle: &State,
+) -> Json {
+ let position = Position::new(lat, lon);
+ let forecast = forecast(position, metrics, maps_handle).await;
+
+ Json(forecast)
+}
+
+/// Handler for showing the current map with the geocoded position of an address for a specific
+/// metric.
+///
+/// Note: This handler is mosly used for debugging purposes!
+#[get("/map?&")]
+async fn map_address(
+ address: String,
+ metric: Metric,
+ maps_handle: &State,
+) -> Option>> {
+ let position = resolve_address(address).await?;
+ let image_data = mark_map(position, metric, maps_handle).await;
+
+ image_data.map(|id| Custom(ContentType::PNG, id))
+}
+
+/// Handler for showing the current map with the geocoded position for a specific metric.
+///
+/// Note: This handler is mosly used for debugging purposes!
+#[get("/map?&&", rank = 2)]
+async fn map_geo(
+ lat: f64,
+ lon: f64,
+ metric: Metric,
+ maps_handle: &State,
+) -> Option>> {
+ let position = Position::new(lat, lon);
+ let image_data = mark_map(position, metric, maps_handle).await;
+
+ image_data.map(|id| Custom(ContentType::PNG, id))
+}
+
+/// Sets up Rocket.
+fn rocket(maps_handle: MapsHandle) -> Rocket {
+ rocket::build().manage(maps_handle).mount(
+ "/",
+ routes![forecast_address, forecast_geo, map_address, map_geo],
+ )
+}
+
+/// Sets up Rocket and the maps cache refresher task.
+pub fn setup() -> (Rocket, impl Future