changeset 34:9966460e2930

Encapsulate GeoJSON and parse_flexible_timestamp
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 03 Dec 2020 07:51:38 +0100
parents 150e76fd3d0e
children 097f1c1c5f2b
files src/db.rs src/main.rs src/types.rs src/util.rs
diffstat 4 files changed, 69 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/src/db.rs	Thu Dec 03 00:05:18 2020 +0100
+++ b/src/db.rs	Thu Dec 03 07:51:38 2020 +0100
@@ -8,10 +8,7 @@
     last: &Option<i32>,
     limit: &Option<i64>,
 ) -> Option<(types::GeoJSON, i32)> {
-    let mut returnable = types::GeoJSON {
-        typ: "FeatureCollection".into(),
-        features: vec![],
-    };
+    let mut returnable = types::GeoJSON::new();
     let check_for_new = db.prepare_cached(
         r"SELECT id, t, lat, long, spd, ele FROM geohub.geodata
         WHERE (client = $1) and (id > $2) AND (secret = public.digest($3, 'sha256') or secret is null)
@@ -25,7 +22,7 @@
     if let Ok(rows) = rows {
         // If there are unknown entries, return those.
         if rows.len() > 0 {
-            returnable.features = Vec::with_capacity(rows.len());
+            returnable.reserve_features(rows.len());
             let mut last = 0;
 
             for row in rows.iter() {
@@ -37,9 +34,7 @@
                     row.get(4),
                     row.get(5),
                 );
-                returnable
-                    .features
-                    .push(types::geofeature_from_row(ts, lat, long, spd, ele));
+                returnable.push_feature(types::geofeature_from_row(ts, lat, long, spd, ele));
                 if id > last {
                     last = id;
                 }
--- a/src/main.rs	Thu Dec 03 00:05:18 2020 +0100
+++ b/src/main.rs	Thu Dec 03 07:51:38 2020 +0100
@@ -4,6 +4,7 @@
 mod ids;
 mod notifier;
 mod types;
+mod util;
 
 use std::sync::{mpsc, Arc, Mutex};
 use std::time;
@@ -11,34 +12,9 @@
 use postgres;
 use rocket;
 
-use chrono::TimeZone;
-
 #[rocket_contrib::database("geohub")]
 struct DBConn(postgres::Connection);
 
-/// Parse timestamps flexibly. Without any zone information, UTC is assumed.
-fn flexible_timestamp_parse(ts: String) -> Option<chrono::DateTime<chrono::Utc>> {
-    let fmtstrings = &[
-        "%Y-%m-%dT%H:%M:%S%.f%:z",
-        "%Y-%m-%dT%H:%M:%S%.fZ",
-        "%Y-%m-%d %H:%M:%S%.f",
-    ];
-    for fs in fmtstrings {
-        let (naive, withtz) = (
-            chrono::NaiveDateTime::parse_from_str(ts.as_str(), fs).ok(),
-            chrono::DateTime::parse_from_str(ts.as_str(), fs).ok(),
-        );
-        if let Some(p) = withtz {
-            return Some(p.with_timezone(&chrono::Utc));
-        }
-        if let Some(p) = naive {
-            let utcd = chrono::Utc.from_utc_datetime(&p);
-            return Some(utcd);
-        }
-    }
-    None
-}
-
 /// Almost like retrieve/json, but sorts in descending order and doesn't work with intervals (only
 /// limit). Used for backfilling recent points in the UI.
 #[rocket::get("/geo/<name>/retrieve/last?<secret>&<last>&<limit>")]
@@ -137,22 +113,18 @@
     to: Option<String>,
     limit: Option<i64>,
 ) -> rocket_contrib::json::Json<types::GeoJSON> {
-    let mut returnable = types::GeoJSON {
-        typ: "FeatureCollection".into(),
-        features: vec![],
-    };
-
-    let from_ts = from
-        .and_then(flexible_timestamp_parse)
-        .unwrap_or(chrono::DateTime::from_utc(
-            chrono::NaiveDateTime::from_timestamp(0, 0),
-            chrono::Utc,
-        ));
+    let from_ts =
+        from.and_then(util::flexible_timestamp_parse)
+            .unwrap_or(chrono::DateTime::from_utc(
+                chrono::NaiveDateTime::from_timestamp(0, 0),
+                chrono::Utc,
+            ));
     let to_ts = to
-        .and_then(flexible_timestamp_parse)
+        .and_then(util::flexible_timestamp_parse)
         .unwrap_or(chrono::Utc::now());
     let limit = limit.unwrap_or(16384);
 
+    let mut returnable = types::GeoJSON::new();
     let stmt = db.0.prepare_cached(
         r"SELECT t, lat, long, spd, ele FROM geohub.geodata
         WHERE (client = $1) and (t between $2 and $3) AND (secret = public.digest($4, 'sha256') or secret is null)
@@ -160,13 +132,11 @@
         LIMIT $5").unwrap(); // Must succeed.
     let rows = stmt.query(&[&name, &from_ts, &to_ts, &secret, &limit]);
     if let Ok(rows) = rows {
-        returnable.features = Vec::with_capacity(rows.len());
+        returnable.reserve_features(rows.len());
         for row in rows.iter() {
             let (ts, lat, long, spd, ele) =
                 (row.get(0), row.get(1), row.get(2), row.get(3), row.get(4));
-            returnable
-                .features
-                .push(types::geofeature_from_row(ts, lat, long, spd, ele));
+            returnable.push_feature(types::geofeature_from_row(ts, lat, long, spd, ele));
         }
     }
 
@@ -194,7 +164,7 @@
     }
     let mut ts = chrono::Utc::now();
     if let Some(time) = time {
-        ts = flexible_timestamp_parse(time).unwrap_or(ts);
+        ts = util::flexible_timestamp_parse(time).unwrap_or(ts);
     }
     let stmt = db.0.prepare_cached("INSERT INTO geohub.geodata (client, lat, long, spd, t, ele, secret) VALUES ($1, $2, $3, $4, $5, $6, public.digest($7, 'sha256'))").unwrap();
     let channel = format!(
--- a/src/types.rs	Thu Dec 03 00:05:18 2020 +0100
+++ b/src/types.rs	Thu Dec 03 07:51:38 2020 +0100
@@ -3,24 +3,43 @@
 ///
 #[derive(serde::Serialize, Debug, Clone)]
 pub struct GeoProperties {
-    pub time: chrono::DateTime<chrono::Utc>,
-    pub altitude: Option<f64>,
-    pub speed: Option<f64>,
+    time: chrono::DateTime<chrono::Utc>,
+    altitude: Option<f64>,
+    speed: Option<f64>,
 }
 
 #[derive(serde::Serialize, Debug, Clone)]
 pub struct GeoGeometry {
     #[serde(rename = "type")]
-    pub typ: String, // always "Point"
-    pub coordinates: Vec<f64>, // always [long, lat]
+    typ: String, // always "Point"
+    coordinates: Vec<f64>, // always [long, lat]
 }
 
 #[derive(serde::Serialize, Debug, Clone)]
 pub struct GeoFeature {
     #[serde(rename = "type")]
-    pub typ: String, // always "Feature"
-    pub properties: GeoProperties,
-    pub geometry: GeoGeometry,
+    typ: String, // always "Feature"
+    properties: GeoProperties,
+    geometry: GeoGeometry,
+}
+
+#[derive(serde::Serialize, Debug, Clone)]
+pub struct GeoJSON {
+    #[serde(rename = "type")]
+    typ: String, // always "FeatureCollection"
+    features: Vec<GeoFeature>,
+}
+
+impl GeoJSON {
+    pub fn new() -> GeoJSON {
+        GeoJSON { typ: "FeatureCollection".into(), features: vec![] }
+    }
+    pub fn reserve_features(&mut self, cap: usize) {
+        self.features.reserve(cap);
+    }
+    pub fn push_feature(&mut self, feat: GeoFeature) {
+        self.features.push(feat);
+    }
 }
 
 pub fn geofeature_from_row(
@@ -44,9 +63,3 @@
     }
 }
 
-#[derive(serde::Serialize, Debug, Clone)]
-pub struct GeoJSON {
-    #[serde(rename = "type")]
-    pub typ: String, // always "FeatureCollection"
-    pub features: Vec<GeoFeature>,
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util.rs	Thu Dec 03 07:51:38 2020 +0100
@@ -0,0 +1,27 @@
+use chrono;
+
+use chrono::TimeZone;
+
+/// Parse timestamps flexibly. Without any zone information, UTC is assumed.
+pub fn flexible_timestamp_parse(ts: String) -> Option<chrono::DateTime<chrono::Utc>> {
+    let fmtstrings = &[
+        "%Y-%m-%dT%H:%M:%S%.f%:z",
+        "%Y-%m-%dT%H:%M:%S%.fZ",
+        "%Y-%m-%d %H:%M:%S%.f",
+    ];
+    for fs in fmtstrings {
+        let (naive, withtz) = (
+            chrono::NaiveDateTime::parse_from_str(ts.as_str(), fs).ok(),
+            chrono::DateTime::parse_from_str(ts.as_str(), fs).ok(),
+        );
+        if let Some(p) = withtz {
+            return Some(p.with_timezone(&chrono::Utc));
+        }
+        if let Some(p) = naive {
+            let utcd = chrono::Utc.from_utc_datetime(&p);
+            return Some(utcd);
+        }
+    }
+    None
+}
+