use crate::error::{Result, ShapefileError};
use crate::shp::header::{BoundingBox, HEADER_SIZE, ShapefileHeader};
use crate::shp::shapes::ShapeType;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, SeekFrom, Write};
pub const INDEX_ENTRY_SIZE: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexEntry {
pub offset: i32,
pub content_length: i32,
}
impl IndexEntry {
pub fn new(offset: i32, content_length: i32) -> Self {
Self {
offset,
content_length,
}
}
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let offset = reader
.read_i32::<BigEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading index offset"))?;
let content_length = reader
.read_i32::<BigEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading index content length"))?;
Ok(Self {
offset,
content_length,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer
.write_i32::<BigEndian>(self.offset)
.map_err(ShapefileError::Io)?;
writer
.write_i32::<BigEndian>(self.content_length)
.map_err(ShapefileError::Io)?;
Ok(())
}
}
pub struct ShxReader<R: Read> {
reader: R,
header: ShapefileHeader,
}
impl<R: Read> ShxReader<R> {
pub fn new(mut reader: R) -> Result<Self> {
let header = ShapefileHeader::read(&mut reader)?;
Ok(Self { reader, header })
}
pub fn header(&self) -> &ShapefileHeader {
&self.header
}
pub fn read_all_entries(&mut self) -> Result<Vec<IndexEntry>> {
let mut entries = Vec::new();
loop {
match IndexEntry::read(&mut self.reader) {
Ok(entry) => entries.push(entry),
Err(ShapefileError::UnexpectedEof { .. }) => {
break;
}
Err(ShapefileError::Io(ref e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(e) => return Err(e),
}
}
Ok(entries)
}
pub fn record_count(&self) -> usize {
let file_length_bytes = (self.header.file_length as usize) * 2;
if file_length_bytes < HEADER_SIZE {
return 0;
}
(file_length_bytes - HEADER_SIZE) / INDEX_ENTRY_SIZE
}
}
pub struct ShxWriter<W: Write> {
writer: W,
header: ShapefileHeader,
entries: Vec<IndexEntry>,
}
impl<W: Write> ShxWriter<W> {
pub fn new(writer: W, shape_type: ShapeType, bbox: BoundingBox) -> Self {
let header = ShapefileHeader::new(shape_type, bbox);
Self {
writer,
header,
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, offset: i32, content_length: i32) {
self.entries.push(IndexEntry::new(offset, content_length));
}
pub fn write_all(&mut self) -> Result<()> {
self.header.file_length = 50 + (self.entries.len() as i32 * 4);
self.header.write(&mut self.writer)?;
for entry in &self.entries {
entry.write(&mut self.writer)?;
}
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
self.writer.flush().map_err(ShapefileError::Io)
}
}
impl<W: Write + Seek> ShxWriter<W> {
pub fn update_file_length(&mut self) -> Result<()> {
self.header.file_length = 50 + (self.entries.len() as i32 * 4);
self.writer
.seek(SeekFrom::Start(24))
.map_err(ShapefileError::Io)?;
self.writer
.write_i32::<BigEndian>(self.header.file_length)
.map_err(ShapefileError::Io)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_index_entry_round_trip() {
let entry = IndexEntry::new(50, 100);
let mut buffer = Vec::new();
entry.write(&mut buffer).expect("write index entry");
assert_eq!(buffer.len(), INDEX_ENTRY_SIZE);
let mut cursor = Cursor::new(buffer);
let read_entry = IndexEntry::read(&mut cursor).expect("read index entry");
assert_eq!(read_entry, entry);
}
#[test]
fn test_shx_reader_writer() {
let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0).expect("valid bbox");
let mut buffer = Cursor::new(Vec::new());
{
let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
writer.add_entry(50, 10); writer.add_entry(60, 10); writer.add_entry(70, 10); writer.write_all().expect("write all shx entries");
}
buffer.set_position(0);
let mut reader = ShxReader::new(buffer).expect("create shx reader");
assert_eq!(reader.header().shape_type, ShapeType::Point);
assert_eq!(reader.record_count(), 3);
let entries = reader.read_all_entries().expect("read all shx entries");
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].offset, 50);
assert_eq!(entries[0].content_length, 10);
assert_eq!(entries[1].offset, 60);
assert_eq!(entries[2].offset, 70);
}
#[test]
fn test_shx_file_length_calculation() {
let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0)
.expect("valid bbox for shx file length test");
let mut buffer = Vec::new();
let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
writer.add_entry(50, 10);
writer.add_entry(60, 10);
writer
.write_all()
.expect("write all for file length calculation");
let cursor = Cursor::new(buffer);
let reader = ShxReader::new(cursor).expect("create reader for file length check");
assert_eq!(reader.header().file_length, 58);
}
#[test]
fn test_seekable_update() {
let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0)
.expect("valid bbox for seekable update test");
let mut buffer = Cursor::new(Vec::new());
let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
writer.add_entry(50, 10);
writer.add_entry(60, 10);
writer.write_all().expect("write all for seekable update");
writer.update_file_length().expect("update file length");
buffer.set_position(0);
let reader = ShxReader::new(buffer).expect("create reader after seekable update");
assert_eq!(reader.header().file_length, 58);
}
}