wof 1.0.0

The Who's On First rust library and command line.
Documentation
use crate::utils::{GeoCompute, GeoJsonUtils, JsonUtils};
use json::object::Object;
use json::{array, JsonValue};

pub fn export_json_value(json: &JsonValue) -> Result<JsonValue, String> {
  json.assert_is_object()?;
  let geometry = export_geometry(&json)?;
  let properties = export_porperties(&json, &geometry.as_object().unwrap())?;
  let id = export_id(&json)?;
  let bbox = export_bbox(&json)?;

  Ok(json::object! {
    "id" => id,
    "type" => "Feature",
    "properties" => properties,
    "geometry" => geometry,
    "bbox" => bbox
  })
}

fn export_porperties(json: &JsonValue, geometry: &Object) -> Result<JsonValue, String> {
  let obj = json
    .as_object()
    .unwrap()
    .get("properties")
    .ok_or("`properties` key is required.")?;

  let mut properties = obj
    .as_object()
    .ok_or("`properties` key must be an object")?
    .clone();

  if properties.get("edtf:cessation").is_none() {
    properties.insert("edtf:cessation", JsonValue::from("uuuu"));
  }
  if properties.get("edtf:inception").is_none() {
    properties.insert("edtf:inception", JsonValue::from("uuuu"));
  }
  if properties.get("iso:country").is_none() {
    properties.insert("iso:country", JsonValue::from(""));
  }
  if properties.get("mz:hierarchy_label").is_none() {
    properties.insert("mz:hierarchy_label", JsonValue::from(1));
  }
  if properties.get("mz:is_current").is_none() {
    properties.insert("mz:is_current", JsonValue::from(-1));
  }
  if properties.get("src:geom").is_none() {
    properties.insert("src:geom", JsonValue::from("unknown"));
  }
  if properties.get("wof:belongsto").is_none() {
    properties.insert("wof:belongsto", array![]);
  }
  if properties.get("wof:breaches").is_none() {
    properties.insert("wof:breaches", array![]);
  }
  if properties.get("wof:country").is_none() {
    properties.insert("wof:country", JsonValue::from(""));
  }
  if properties.get("wof:hierarchy").is_none() {
    properties.insert("wof:hierarchy", array![]);
  }
  if properties.get("wof:parent_id").is_none() {
    properties.insert("wof:parent_id", JsonValue::from(-1));
  }
  if properties.get("wof:superseded_by").is_none() {
    properties.insert("wof:superseded_by", array![]);
  }
  if properties.get("wof:supersedes").is_none() {
    properties.insert("wof:supersedes", array![]);
  }
  if properties.get("wof:tags").is_none() {
    properties.insert("wof:tags", array![]);
  }
  if properties.get("geom:area").is_none() {
    properties.insert("geom:area", JsonValue::from(geometry.compute_area()));
  }
  if properties.get("geom:area_square_m").is_none() {
    properties.insert(
      "geom:area_square_m",
      JsonValue::from(geometry.compute_area_m()),
    );
  }
  if properties.get("geom:bbox").is_none() {
    properties.insert("geom:bbox", JsonValue::from(geometry.compute_bbox_string()));
  }
  let (lng, lat) = geometry.compute_centroid();
  if properties.get("geom:latitude").is_none() {
    properties.insert("geom:latitude", JsonValue::from(lat));
  }
  if properties.get("geom:longitude").is_none() {
    properties.insert("geom:longitude", JsonValue::from(lng));
  }
  if properties.get("wof:geomhash").is_none() {
    properties.insert("wof:geomhash", JsonValue::from(geometry.compute_md5()));
  }

  Ok(JsonValue::Object(properties))
}

fn export_geometry(json: &JsonValue) -> Result<JsonValue, String> {
  let obj = json
    .as_object()
    .unwrap()
    .get("geometry")
    .ok_or("`geometry` key is required.")?
    .as_object()
    .ok_or("`geometry` key must be an object.")?;

  let coordinates = obj
    .get("coordinates")
    .ok_or("`coordinates` key is required.")?;
  let _type = obj.get("type").ok_or("In `geometry`, `type` is required")?;

  if !coordinates.is_array() {
    return Err(format!("`coordinates` key must be an array."));
  }

  let coords = match _type.as_str() {
    Some("Point") => json::from(
      coordinates
        .as_geom_point()
        .ok_or("`coordinates` malformed for `type` Point.")?,
    ),
    Some("MultiPoint") => json::from(
      coordinates
        .as_geom_multi_point()
        .ok_or("`coordinates` malformed for `type` MultiPoint.")?,
    ),
    Some("LineString") => json::from(
      coordinates
        .as_geom_line()
        .ok_or("`coordinates` malformed for `type` LineString.")?,
    ),
    Some("MultiLineString") => json::from(
      coordinates
        .as_geom_multi_line()
        .ok_or("`coordinates` malformed for `type` MultiLineString.")?,
    ),
    Some("Polygon") => json::from(
      coordinates
        .as_geom_polygon()
        .ok_or("`coordinates` malformed for `type` Polygon.")?,
    ),
    Some("MultiPolygon") => json::from(
      coordinates
        .as_geom_multi_polygon()
        .ok_or("`coordinates` malformed for `type` MultiPolygon.")?,
    ),
    Some(t) => return Err(format!("type {} is not supported.", t)),
    None => return Err(format!("In `geometry`, `type` key is required.")),
  };

  Ok(json::object! {
    "type" => _type.as_str().unwrap(),
    "coordinates" => coords
  })
}

fn export_id(json: &JsonValue) -> Result<i64, String> {
  let properties = json.as_object().unwrap().get("properties").unwrap();
  if let Some(id) = properties.as_object().unwrap().get("wof:id") {
    if id.is_number() {
      return Ok(id.as_i64().unwrap());
    }
  }

  Err(String::from("No id found for this feature."))
}

fn export_bbox(json: &JsonValue) -> Result<Vec<f64>, String> {
  let geom = json.as_object().unwrap().get("geometry").unwrap();
  Ok(geom.as_object().unwrap().compute_bbox())
}