geolite 0.0.2

open source geocode solution
use prost::Message;
use std::{
  fs,
  io::{Read, Seek},
};

#[derive(prost::Message)]
struct header_block_msg {
  #[prost(message, optional, tag = "1")]
  bbox: Option<header_bbox_msg>,
  #[prost(string, repeated, tag = "4")]
  required_features: Vec<String>,
  #[prost(string, repeated, tag = "5")]
  optional_features: Vec<String>,
  #[prost(string, optional, tag = "16")]
  writingprogram: Option<String>,
  #[prost(string, optional, tag = "17")]
  source: Option<String>,
  #[prost(int64, optional, tag = "32")]
  osmosis_replication_timestamp: Option<i64>,
  #[prost(int64, optional, tag = "33")]
  osmosis_replication_sequence_number: Option<i64>,
  #[prost(string, optional, tag = "34")]
  osmosis_replication_base_url: Option<String>,
}

#[derive(prost::Message)]
struct header_bbox_msg {
  #[prost(sint64, tag = "1")]
  left: i64,
  #[prost(sint64, tag = "2")]
  right: i64,
  #[prost(sint64, tag = "3")]
  top: i64,
  #[prost(sint64, tag = "4")]
  bottom: i64,
}

pub struct header_bbox {
  pub bottom: f64,
  pub left: f64,
  pub top: f64,
  pub right: f64,
}

pub struct header_output {
  pub bbox: Option<header_bbox>,
  pub writingprogram: Option<String>,
  pub source: Option<String>,
  pub replication_timestamp: Option<i64>,
}

pub fn run(pbf: &str, conn: &rusqlite::Connection, file_id: u32) -> header_output {
  let chunk = crate::database::osm_pbf_blob_chunks::get_header_chunk(conn, file_id)
    .expect("no header chunk found in sqlite — run extract osm-pbf-blob-chunks first");

  let mut file = fs::File::open(pbf).expect("failed to open pbf file");
  file
    .seek(std::io::SeekFrom::Start(chunk.data_first_byte))
    .expect("failed to seek to header blob");

  let mut blob_buf = vec![0u8; chunk.data_size as usize];
  file
    .read_exact(&mut blob_buf)
    .expect("failed to read header blob");

  let blob = super::osm_data::blob_msg::decode(blob_buf.as_slice()).expect("failed to decode blob");
  let raw = super::decompress_blob(&blob);

  let h = header_block_msg::decode(raw.as_slice()).expect("failed to decode header block");

  const NANO: f64 = 1e-9;

  let bbox_wkt = h.bbox.as_ref().map(|b| {
    let left = b.left as f64 * NANO;
    let right = b.right as f64 * NANO;
    let top = b.top as f64 * NANO;
    let bot = b.bottom as f64 * NANO;
    format!("POLYGON(({left} {bot},{right} {bot},{right} {top},{left} {top},{left} {bot}))")
      .into_bytes()
  });

  let required_features = if h.required_features.is_empty() {
    None
  } else {
    Some(serde_json::to_vec(&h.required_features).expect("failed to serialize required_features"))
  };

  let optional_features = if h.optional_features.is_empty() {
    None
  } else {
    Some(serde_json::to_vec(&h.optional_features).expect("failed to serialize optional_features"))
  };

  crate::database::osm_pbf_files::update_osm_header(
    conn,
    pbf,
    bbox_wkt,
    required_features,
    optional_features,
    h.writingprogram.as_deref(),
    h.source.as_deref(),
    h.osmosis_replication_timestamp,
    h.osmosis_replication_sequence_number
      .and_then(|v| u32::try_from(v).ok()),
    h.osmosis_replication_base_url.as_deref(),
  );

  header_output {
    bbox: h.bbox.map(|b| header_bbox {
      bottom: b.bottom as f64 * NANO,
      left: b.left as f64 * NANO,
      top: b.top as f64 * NANO,
      right: b.right as f64 * NANO,
    }),
    writingprogram: h.writingprogram,
    source: h.source,
    replication_timestamp: h.osmosis_replication_timestamp,
  }
}