oxigdal_shapefile/shx/
mod.rs1use crate::error::{Result, ShapefileError};
10use crate::shp::header::{BoundingBox, HEADER_SIZE, ShapefileHeader};
11use crate::shp::shapes::ShapeType;
12use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
13use std::io::{Read, Seek, SeekFrom, Write};
14
15pub const INDEX_ENTRY_SIZE: usize = 8;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct IndexEntry {
21 pub offset: i32,
23 pub content_length: i32,
25}
26
27impl IndexEntry {
28 pub fn new(offset: i32, content_length: i32) -> Self {
30 Self {
31 offset,
32 content_length,
33 }
34 }
35
36 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
38 let offset = reader
39 .read_i32::<BigEndian>()
40 .map_err(|_| ShapefileError::unexpected_eof("reading index offset"))?;
41
42 let content_length = reader
43 .read_i32::<BigEndian>()
44 .map_err(|_| ShapefileError::unexpected_eof("reading index content length"))?;
45
46 Ok(Self {
47 offset,
48 content_length,
49 })
50 }
51
52 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
54 writer
55 .write_i32::<BigEndian>(self.offset)
56 .map_err(ShapefileError::Io)?;
57
58 writer
59 .write_i32::<BigEndian>(self.content_length)
60 .map_err(ShapefileError::Io)?;
61
62 Ok(())
63 }
64}
65
66pub struct ShxReader<R: Read> {
68 reader: R,
69 header: ShapefileHeader,
70}
71
72impl<R: Read> ShxReader<R> {
73 pub fn new(mut reader: R) -> Result<Self> {
75 let header = ShapefileHeader::read(&mut reader)?;
76 Ok(Self { reader, header })
77 }
78
79 pub fn header(&self) -> &ShapefileHeader {
81 &self.header
82 }
83
84 pub fn read_all_entries(&mut self) -> Result<Vec<IndexEntry>> {
86 let mut entries = Vec::new();
87
88 loop {
89 match IndexEntry::read(&mut self.reader) {
90 Ok(entry) => entries.push(entry),
91 Err(ShapefileError::UnexpectedEof { .. }) => {
92 break;
94 }
95 Err(ShapefileError::Io(ref e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
96 break;
97 }
98 Err(e) => return Err(e),
99 }
100 }
101
102 Ok(entries)
103 }
104
105 pub fn record_count(&self) -> usize {
107 let file_length_bytes = (self.header.file_length as usize) * 2;
110 if file_length_bytes < HEADER_SIZE {
111 return 0;
112 }
113 (file_length_bytes - HEADER_SIZE) / INDEX_ENTRY_SIZE
114 }
115}
116
117pub struct ShxWriter<W: Write> {
119 writer: W,
120 header: ShapefileHeader,
121 entries: Vec<IndexEntry>,
122}
123
124impl<W: Write> ShxWriter<W> {
125 pub fn new(writer: W, shape_type: ShapeType, bbox: BoundingBox) -> Self {
127 let header = ShapefileHeader::new(shape_type, bbox);
128 Self {
129 writer,
130 header,
131 entries: Vec::new(),
132 }
133 }
134
135 pub fn add_entry(&mut self, offset: i32, content_length: i32) {
137 self.entries.push(IndexEntry::new(offset, content_length));
138 }
139
140 pub fn write_all(&mut self) -> Result<()> {
142 self.header.file_length = 50 + (self.entries.len() as i32 * 4);
145
146 self.header.write(&mut self.writer)?;
148
149 for entry in &self.entries {
151 entry.write(&mut self.writer)?;
152 }
153
154 Ok(())
155 }
156
157 pub fn flush(&mut self) -> Result<()> {
159 self.writer.flush().map_err(ShapefileError::Io)
160 }
161}
162
163impl<W: Write + Seek> ShxWriter<W> {
164 pub fn update_file_length(&mut self) -> Result<()> {
166 self.header.file_length = 50 + (self.entries.len() as i32 * 4);
168
169 self.writer
171 .seek(SeekFrom::Start(24))
172 .map_err(ShapefileError::Io)?;
173
174 self.writer
176 .write_i32::<BigEndian>(self.header.file_length)
177 .map_err(ShapefileError::Io)?;
178
179 Ok(())
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use std::io::Cursor;
187
188 #[test]
189 fn test_index_entry_round_trip() {
190 let entry = IndexEntry::new(50, 100);
191
192 let mut buffer = Vec::new();
193 entry.write(&mut buffer).expect("write index entry");
194
195 assert_eq!(buffer.len(), INDEX_ENTRY_SIZE);
196
197 let mut cursor = Cursor::new(buffer);
198 let read_entry = IndexEntry::read(&mut cursor).expect("read index entry");
199
200 assert_eq!(read_entry, entry);
201 }
202
203 #[test]
204 fn test_shx_reader_writer() {
205 let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0).expect("valid bbox");
206 let mut buffer = Cursor::new(Vec::new());
207
208 {
210 let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
211 writer.add_entry(50, 10); writer.add_entry(60, 10); writer.add_entry(70, 10); writer.write_all().expect("write all shx entries");
215 }
216
217 buffer.set_position(0);
219 let mut reader = ShxReader::new(buffer).expect("create shx reader");
220
221 assert_eq!(reader.header().shape_type, ShapeType::Point);
222 assert_eq!(reader.record_count(), 3);
223
224 let entries = reader.read_all_entries().expect("read all shx entries");
225 assert_eq!(entries.len(), 3);
226 assert_eq!(entries[0].offset, 50);
227 assert_eq!(entries[0].content_length, 10);
228 assert_eq!(entries[1].offset, 60);
229 assert_eq!(entries[2].offset, 70);
230 }
231
232 #[test]
233 fn test_shx_file_length_calculation() {
234 let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0)
235 .expect("valid bbox for shx file length test");
236 let mut buffer = Vec::new();
237
238 let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
239 writer.add_entry(50, 10);
240 writer.add_entry(60, 10);
241
242 writer
245 .write_all()
246 .expect("write all for file length calculation");
247
248 let cursor = Cursor::new(buffer);
249 let reader = ShxReader::new(cursor).expect("create reader for file length check");
250 assert_eq!(reader.header().file_length, 58);
251 }
252
253 #[test]
254 fn test_seekable_update() {
255 let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0)
256 .expect("valid bbox for seekable update test");
257 let mut buffer = Cursor::new(Vec::new());
258
259 let mut writer = ShxWriter::new(&mut buffer, ShapeType::Point, bbox);
260 writer.add_entry(50, 10);
261 writer.add_entry(60, 10);
262 writer.write_all().expect("write all for seekable update");
263
264 writer.update_file_length().expect("update file length");
266
267 buffer.set_position(0);
269 let reader = ShxReader::new(buffer).expect("create reader after seekable update");
270 assert_eq!(reader.header().file_length, 58);
271 }
272}