use crate::{Error, Header, Result, Vlr};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Seek, SeekFrom};
const MAIN_VLR_ID: u16 = 34735;
const DOUBLE_VLR_ID: u16 = 34736;
const ASCII_VLR_ID: u16 = 34737;
impl Header {
pub fn remove_crs_vlrs(&mut self) {
self.vlrs.retain(|v| !v.is_crs());
self.evlrs.retain(|v| !v.is_crs());
self.has_wkt_crs = false;
}
pub fn set_wkt_crs(&mut self, wkt_crs_bytes: Vec<u8>) -> Result<()> {
if self.version() < crate::Version::new(1, 4) {
return Err(Error::UnsupportedFeature {
version: self.version(),
feature: "WKT CRS VLR",
});
}
if self.all_vlrs().any(|v| v.is_crs()) {
log::warn!("Header already contains CRL VLR, removing");
self.remove_crs_vlrs();
}
let num_bytes = wkt_crs_bytes.len();
let vlr = Vlr {
user_id: "LASF_Projection".to_string(),
record_id: 2112,
description: String::new(),
data: wkt_crs_bytes,
};
if num_bytes > u16::MAX as usize {
self.evlrs.push(vlr);
} else {
self.vlrs.push(vlr);
};
self.has_wkt_crs = true;
Ok(())
}
pub fn get_wkt_crs_bytes(&self) -> Option<&[u8]> {
self.all_vlrs()
.find(|&v| v.is_wkt_crs())
.map(|cv| cv.data.as_slice())
}
pub fn get_geotiff_crs(&self) -> Result<Option<GeoTiffCrs>> {
let mut main_vlr = None;
let mut double_vlr = None;
let mut ascii_vlr = None;
for vlr in self.all_vlrs().filter(|&v| v.is_geotiff_crs()) {
match vlr.record_id {
MAIN_VLR_ID => {
main_vlr = Some(vlr.data.as_slice());
}
DOUBLE_VLR_ID => {
double_vlr = Some(vlr.data.as_slice());
}
ASCII_VLR_ID => {
ascii_vlr = Some(vlr.data.as_slice());
}
_ => continue,
};
}
if let Some(main_vlr) = main_vlr {
Ok(Some(GeoTiffCrs::read_from(
main_vlr, double_vlr, ascii_vlr,
)?))
} else {
Ok(None)
}
}
}
#[derive(Debug, Clone)]
pub struct GeoTiffCrs {
pub entries: Vec<GeoTiffKeyEntry>,
}
impl GeoTiffCrs {
fn read_from(
main_vlr: &[u8],
double_vlr: Option<&[u8]>,
ascii_vlr: Option<&[u8]>,
) -> Result<Self> {
let mut main_vlr = Cursor::new(main_vlr);
let key_directory_version = main_vlr.read_u16::<LittleEndian>()?;
let key_revision = main_vlr.read_u16::<LittleEndian>()?;
let minor_revision = main_vlr.read_u16::<LittleEndian>()?;
if key_directory_version != 1 || key_revision != 1 || minor_revision != 0 {
return Err(Error::InvalidGeoTiffHeader {
expected_version: 1,
actual_version: key_directory_version,
expected_revision: 1,
actual_revision: key_revision,
expected_minor: 0,
actual_minor: minor_revision,
});
}
let num_keys = main_vlr.read_u16::<LittleEndian>()?;
let mut entries = Vec::with_capacity(num_keys as usize);
for _ in 0..num_keys {
entries.push(GeoTiffKeyEntry::read_from(
&mut main_vlr,
&double_vlr,
&ascii_vlr,
)?);
}
Ok(GeoTiffCrs { entries })
}
}
#[derive(Debug, Clone)]
pub enum GeoTiffData {
U16(u16),
String(String),
Doubles(Vec<f64>),
}
#[derive(Debug, Clone)]
pub struct GeoTiffKeyEntry {
pub id: u16,
pub data: GeoTiffData,
}
impl GeoTiffKeyEntry {
fn read_from(
main_vlr: &mut Cursor<&[u8]>,
double_vlr: &Option<&[u8]>,
ascii_vlr: &Option<&[u8]>,
) -> Result<Self> {
let id = main_vlr.read_u16::<LittleEndian>()?;
let location = main_vlr.read_u16::<LittleEndian>()?;
let count = main_vlr.read_u16::<LittleEndian>()?;
let offset = main_vlr.read_u16::<LittleEndian>()?;
let data = match location {
0 => GeoTiffData::U16(offset),
34736 => {
let mut cursor = Cursor::new(double_vlr.ok_or(Error::UnreadableGeoTiffCrs)?);
let _ = cursor.seek(SeekFrom::Start(offset as u64 * 8_u64))?; let mut doubles = Vec::with_capacity(count as usize);
for _ in 0..count {
doubles.push(cursor.read_f64::<LittleEndian>()?);
}
GeoTiffData::Doubles(doubles)
}
34737 => {
let mut cursor = Cursor::new(ascii_vlr.ok_or(Error::UnreadableGeoTiffCrs)?);
let _ = cursor.seek(SeekFrom::Start(offset as u64))?; let mut string = String::with_capacity(count as usize);
for _ in 0..count {
string.push(cursor.read_u8()? as char);
}
GeoTiffData::String(string)
}
_ => return Err(Error::UndefinedDataForGeoTiffKey(id)),
};
Ok(GeoTiffKeyEntry { id, data })
}
}
#[cfg(test)]
mod tests {
use crate::Reader;
#[cfg(feature = "laz")]
#[test]
fn test_get_epsg_crs_wkt_vlr_autzen() {
let reader = Reader::from_path("tests/data/autzen.copc.laz").expect("Cannot open reader");
let crs = reader
.header()
.get_wkt_crs_bytes()
.expect("Could not get WKT bytes");
let crs_str = String::from_utf8_lossy(crs);
let (horizontal_component, vertical_component) = crs_str.split_once("VERT_CS").unwrap();
let horizontal_crs = "AUTHORITY[\"EPSG\",\"2992\"]";
assert!(horizontal_component.contains(horizontal_crs));
let vertical_crs = "AUTHORITY[\"EPSG\",\"6360\"]";
assert!(vertical_component.contains(vertical_crs));
}
#[cfg(feature = "laz")]
#[test]
fn test_get_epsg_crs_geotiff_vlr_norway() {
let reader =
Reader::from_path("tests/data/32-1-472-150-76.laz").expect("Cannot open reader");
let crs = reader.header().get_geotiff_crs().unwrap().unwrap();
let horizontal = crs
.entries
.iter()
.find(|key| key.id == 2048 || key.id == 3072)
.unwrap()
.data
.clone();
let vertical = crs
.entries
.iter()
.find(|key| key.id == 4096)
.unwrap()
.data
.clone();
if let crate::crs::GeoTiffData::U16(h_code) = horizontal {
assert!(h_code == 25832);
} else {
panic!("Expected GeoTiffData::U16")
}
if let crate::crs::GeoTiffData::U16(v_code) = vertical {
assert!(v_code == 5941);
} else {
panic!("Expected GeoTiffData::U16")
}
}
#[cfg(feature = "laz")]
#[test]
fn test_remove_crs_vlrs() {
let reader =
Reader::from_path("tests/data/32-1-472-150-76.laz").expect("Cannot open reader");
let mut header = reader.header().to_owned();
header.remove_crs_vlrs();
for vlr in header.all_vlrs() {
if vlr.is_crs() {
panic!("CRS VLRs are still in the header")
}
}
}
#[cfg(feature = "laz")]
#[test]
fn test_write_crs_vlr_las_v1_4() {
let reader = Reader::from_path("tests/data/autzen.copc.laz").expect("Cannot open reader");
let mut header = reader.header().to_owned();
header.remove_crs_vlrs();
let random_bytes =
"Test bytes. Just seeing if writing and reading is consitent:)".as_bytes();
header
.set_wkt_crs(random_bytes.to_vec())
.expect("Could not add wkt crs vlr");
let read_bytes = header.get_wkt_crs_bytes().unwrap();
assert!(read_bytes == random_bytes);
}
#[test]
fn test_write_crs_vlr_las_v1_2() {
let reader = Reader::from_path("tests/data/autzen.las").expect("Cannot open reader");
let mut header = reader.header().to_owned();
header.remove_crs_vlrs();
let res = header.set_wkt_crs("just some bytes".as_bytes().to_vec());
assert!(res.is_err());
}
}