Give a geographic location, download and read LIDAR from GeoNB

This commit is contained in:
Matthew Gordon 2022-02-26 15:09:13 -05:00
parent 054d107d3c
commit c54de32d30
5 changed files with 1776 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
*~
\#*#

1595
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "pteropus"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde_json = "~1.0"
tokio = {version="~1.17", features=["rt-multi-thread", "macros"]}
tokio-util = {version="~0.7", features=["io"]}
reqwest = {version="~0.11", features=["json", "stream"]}
proj = "~0.25"
geo-types = "~0.7"
clap = {version="~3.1", features=["derive"]}
anyhow = "~1.0"
futures = "~0.3"
bytes = "~1.1"
las = {version="~0.7", features=["laz"]}

111
src/geonb.rs Normal file
View File

@ -0,0 +1,111 @@
use {futures::stream::TryStreamExt, geo_types::Point, tokio_util::io::StreamReader};
#[derive(Debug)]
pub struct Error {
pub message: String,
}
impl Error {
pub fn new(message: &str) -> Error {
let message = message.to_string();
Error { message }
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "GeoNB Error: \"{}\"", self.message)
}
}
impl std::error::Error for Error {}
fn convert_err(err: reqwest::Error) -> std::io::Error {
todo!()
}
pub async fn get_lidar_tile_around_point(
location: Point<f64>,
) -> Result<impl tokio::io::AsyncRead, anyhow::Error> {
let client = reqwest::Client::new();
let query_response_json = &client
.get("https://geonb.snb.ca/arcgis/rest/services/GeoNB_SNB_LidarIndex/MapServer/1/query")
.query(&[
("f", "json"),
("geometryType", "esriGeometryPoint"),
("geometry", &format!("{},{}", location.x(), location.y())),
("returnIdsOnly", "true"),
])
.send()
.await?
.json::<serde_json::Value>()
.await?;
println!(
"{}",
serde_json::to_string_pretty(&query_response_json).expect("JSON")
);
let object_id = query_response_json
.get("objectIds")
.and_then(|object_ids| object_ids.get(0))
.and_then(&serde_json::Value::as_i64)
.ok_or_else(|| Error::new("Could not find \"objectId\" in response."))?;
let object_response_json = client
.get(format!(
"https://geonb.snb.ca/arcgis/rest/services/GeoNB_SNB_LidarIndex/MapServer/1/{}",
object_id
))
.query(&[("f", "json")])
.send()
.await?
.json::<serde_json::Value>()
.await?;
println!(
"{}",
serde_json::to_string_pretty(&object_response_json).expect("JSON")
);
let laz_file_url = object_response_json
.get("feature")
.ok_or_else(|| Error::new("Could not find \"feature\" in response."))?
.get("attributes")
.ok_or_else(|| Error::new("Could not find \"attributes\" in response."))?
.get("FILE_URL")
.ok_or_else(|| Error::new("Could not find \"FILE_URL\" in response."))?
.as_str()
.ok_or_else(|| Error::new("Expected \"FILE_URL\" to be a string but it was not."))?;
println!("LAZ URL: {}", laz_file_url);
Ok(StreamReader::new(
client
.get(laz_file_url)
.send()
.await?
.bytes_stream()
.map_err(convert_err),
))
}
pub async fn test() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
print!(
"{}",
serde_json::to_string_pretty(&client
.get("https://geonb.snb.ca/arcgis/rest/services/GeoNB_SNB_LidarIndex/MapServer/1/query")
.query(&[("f", "json"),("geometryType","esriGeometryPoint"),("geometry","2470000,7443000"),("returnIdsOnly","true")])
.send()
.await?
.json::<serde_json::Value>().await?).expect("JSON")
);
print!(
"{}",
serde_json::to_string_pretty(&client
.get("https://geonb.snb.ca/arcgis/rest/services/GeoNB_SNB_LidarIndex/MapServer/1/14601")
.query(&[("f", "json")])
.send()
.await?
.json::<serde_json::Value>().await?).expect("JSON")
);
Ok(())
}

48
src/main.rs Normal file
View File

@ -0,0 +1,48 @@
use {clap::Parser, geo_types::Point, las::Read as LasRead, proj::Proj, tokio::io::AsyncReadExt};
mod geonb;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Args {
// Latitude to fetch LIDAR tile at
#[clap(long)]
latitude: f64,
// Longitude to fetch LIDAR tile at
#[clap(long)]
longitude: f64,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let args = Args::parse();
let location = Proj::new_known_crs("+proj=longlat +datum=WGS84", "EPSG:2953", None)
.unwrap()
.convert(Point::new(args.longitude, args.latitude))
.unwrap();
println!("{:?}", location);
let mut las_reader =
tokio::io::BufReader::new(geonb::get_lidar_tile_around_point(location).await?);
let mut las_bytes = Vec::new();
let mut buffer = [0_u8; 4096];
let mut byte_count = 0;
loop {
let num_bytes = las_reader.read(&mut buffer).await?;
if num_bytes == 0 {
break;
}
byte_count += num_bytes;
print!("{} bytes read\r", byte_count);
las_bytes.extend_from_slice(&buffer[0..num_bytes]);
}
println!();
let mut las_reader = las::Reader::new(std::io::Cursor::new(las_bytes))?;
for wrapped_point in las_reader.points().take(10) {
let point = wrapped_point.unwrap();
println!("Point coordinates: ({}, {}, {})", point.x, point.y, point.z);
}
Ok(())
}