1use std::io::{Read, Write};
4
5use super::EsriShape;
6use super::{ShapeType, NO_DATA};
7use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
8use std::mem::size_of;
9
10use super::ConcreteReadableShape;
11use super::Error;
12use super::{is_no_data, HasShapeType, WritableShape};
13use std::fmt;
14
15#[cfg(feature = "geo-types")]
16use geo_types;
17
18#[derive(PartialEq, Debug, Default, Copy, Clone)]
20pub struct Point {
21 pub x: f64,
22 pub y: f64,
23}
24
25impl Point {
26 pub fn new(x: f64, y: f64) -> Self {
44 Self { x, y }
45 }
46}
47
48impl HasShapeType for Point {
49 fn shapetype() -> ShapeType {
50 ShapeType::Point
51 }
52}
53
54impl ConcreteReadableShape for Point {
55 fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
56 if record_size == 2 * size_of::<f64>() as i32 {
57 let x = source.read_f64::<LittleEndian>()?;
58 let y = source.read_f64::<LittleEndian>()?;
59 Ok(Self { x, y })
60 } else {
61 Err(Error::InvalidShapeRecordSize)
62 }
63 }
64}
65
66impl WritableShape for Point {
67 fn size_in_bytes(&self) -> usize {
68 2 * size_of::<f64>()
69 }
70
71 fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
72 dest.write_f64::<LittleEndian>(self.x)?;
73 dest.write_f64::<LittleEndian>(self.y)?;
74 Ok(())
75 }
76}
77
78impl EsriShape for Point {
79 fn x_range(&self) -> [f64; 2] {
80 [self.x, self.x]
81 }
82
83 fn y_range(&self) -> [f64; 2] {
84 [self.y, self.y]
85 }
86}
87
88impl fmt::Display for Point {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 write!(f, "Point(x: {}, y: {})", self.x, self.y)
91 }
92}
93
94#[cfg(feature = "geo-types")]
95impl From<Point> for geo_types::Point<f64> {
96 fn from(p: Point) -> Self {
97 geo_types::Point::new(p.x, p.y)
98 }
99}
100
101#[cfg(feature = "geo-types")]
102impl From<geo_types::Point<f64>> for Point {
103 fn from(p: geo_types::Point<f64>) -> Self {
104 Point::new(p.x(), p.y())
105 }
106}
107
108#[cfg(feature = "geo-types")]
109impl From<geo_types::Coordinate<f64>> for Point {
110 fn from(c: geo_types::Coordinate<f64>) -> Self {
111 Point::new(c.x, c.y)
112 }
113}
114
115#[cfg(feature = "geo-types")]
116impl From<Point> for geo_types::Coordinate<f64> {
117 fn from(p: Point) -> Self {
118 geo_types::Coordinate { x: p.x, y: p.y }
119 }
120}
121
122#[derive(PartialEq, Debug, Copy, Clone)]
128pub struct PointM {
129 pub x: f64,
130 pub y: f64,
131 pub m: f64,
132}
133
134impl PointM {
135 pub fn new(x: f64, y: f64, m: f64) -> Self {
155 Self { x, y, m }
156 }
157}
158
159impl HasShapeType for PointM {
160 fn shapetype() -> ShapeType {
161 ShapeType::PointM
162 }
163}
164
165impl ConcreteReadableShape for PointM {
166 fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
167 if record_size == 3 * size_of::<f64>() as i32 {
168 let x = source.read_f64::<LittleEndian>()?;
169 let y = source.read_f64::<LittleEndian>()?;
170 let m = source.read_f64::<LittleEndian>()?;
171 Ok(Self { x, y, m })
172 } else {
173 Err(Error::InvalidShapeRecordSize)
174 }
175 }
176}
177
178impl WritableShape for PointM {
179 fn size_in_bytes(&self) -> usize {
180 3 * size_of::<f64>()
181 }
182
183 fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
184 dest.write_f64::<LittleEndian>(self.x)?;
185 dest.write_f64::<LittleEndian>(self.y)?;
186 dest.write_f64::<LittleEndian>(self.m)?;
187 Ok(())
188 }
189}
190
191impl EsriShape for PointM {
192 fn x_range(&self) -> [f64; 2] {
193 [self.x, self.x]
194 }
195
196 fn y_range(&self) -> [f64; 2] {
197 [self.y, self.y]
198 }
199
200 fn m_range(&self) -> [f64; 2] {
201 if is_no_data(self.m) {
202 [0.0, 0.0]
203 } else {
204 [self.m, self.m]
205 }
206 }
207}
208
209impl fmt::Display for PointM {
210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211 if is_no_data(self.m) {
212 write!(f, "Point(x: {}, y: {}, m: NO_DATA)", self.x, self.y)
213 } else {
214 write!(f, "Point(x: {}, y: {}, m: {})", self.x, self.y, self.m)
215 }
216 }
217}
218
219impl Default for PointM {
220 fn default() -> Self {
221 Self {
222 x: 0.0,
223 y: 0.0,
224 m: NO_DATA,
225 }
226 }
227}
228
229#[cfg(feature = "geo-types")]
230impl From<PointM> for geo_types::Point<f64> {
231 fn from(p: PointM) -> Self {
232 geo_types::Point::new(p.x, p.y)
233 }
234}
235
236#[cfg(feature = "geo-types")]
237impl From<geo_types::Point<f64>> for PointM {
238 fn from(p: geo_types::Point<f64>) -> Self {
239 PointM {
240 x: p.x(),
241 y: p.y(),
242 ..Default::default()
243 }
244 }
245}
246
247#[cfg(feature = "geo-types")]
248impl From<geo_types::Coordinate<f64>> for PointM {
249 fn from(c: geo_types::Coordinate<f64>) -> Self {
250 PointM::new(c.x, c.y, NO_DATA)
251 }
252}
253
254#[cfg(feature = "geo-types")]
255impl From<PointM> for geo_types::Coordinate<f64> {
256 fn from(p: PointM) -> Self {
257 geo_types::Coordinate { x: p.x, y: p.y }
258 }
259}
260
261#[derive(PartialEq, Debug, Copy, Clone)]
267pub struct PointZ {
268 pub x: f64,
269 pub y: f64,
270 pub z: f64,
271 pub m: f64,
272}
273
274impl PointZ {
275 pub fn new(x: f64, y: f64, z: f64, m: f64) -> Self {
288 Self { x, y, z, m }
289 }
290
291 fn read_xyz<R: Read>(source: &mut R) -> std::io::Result<Self> {
292 let x = source.read_f64::<LittleEndian>()?;
293 let y = source.read_f64::<LittleEndian>()?;
294 let z = source.read_f64::<LittleEndian>()?;
295 Ok(Self {
296 x,
297 y,
298 z,
299 m: NO_DATA,
300 })
301 }
302}
303
304impl HasShapeType for PointZ {
305 fn shapetype() -> ShapeType {
306 ShapeType::PointZ
307 }
308}
309
310impl ConcreteReadableShape for PointZ {
311 fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
312 if record_size == 3 * size_of::<f64>() as i32 {
313 let point = Self::read_xyz(source)?;
314 Ok(point)
315 } else if record_size == 4 * size_of::<f64>() as i32 {
316 let mut point = Self::read_xyz(source)?;
317 point.m = source.read_f64::<LittleEndian>()?;
318 Ok(point)
319 } else {
320 Err(Error::InvalidShapeRecordSize)
321 }
322 }
323}
324
325impl WritableShape for PointZ {
326 fn size_in_bytes(&self) -> usize {
327 4 * size_of::<f64>()
328 }
329
330 fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
331 dest.write_f64::<LittleEndian>(self.x)?;
332 dest.write_f64::<LittleEndian>(self.y)?;
333 dest.write_f64::<LittleEndian>(self.z)?;
334 dest.write_f64::<LittleEndian>(self.m)?;
335 Ok(())
336 }
337}
338
339impl EsriShape for PointZ {
340 fn x_range(&self) -> [f64; 2] {
341 [self.x, self.x]
342 }
343
344 fn y_range(&self) -> [f64; 2] {
345 [self.y, self.y]
346 }
347
348 fn z_range(&self) -> [f64; 2] {
349 [self.z, self.z]
350 }
351
352 fn m_range(&self) -> [f64; 2] {
353 if is_no_data(self.m) {
354 [0.0, 0.0]
355 } else {
356 [self.m, self.m]
357 }
358 }
359}
360
361impl Default for PointZ {
362 fn default() -> Self {
363 Self {
364 x: 0.0,
365 y: 0.0,
366 z: 0.0,
367 m: NO_DATA,
368 }
369 }
370}
371
372impl fmt::Display for PointZ {
373 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374 if is_no_data(self.m) {
375 write!(
376 f,
377 "Point(x: {}, y: {}, z: {}, m: NO_DATA)",
378 self.x, self.y, self.z
379 )
380 } else {
381 write!(
382 f,
383 "Point(x: {}, y: {}, z: {}, m: {})",
384 self.x, self.y, self.z, self.m
385 )
386 }
387 }
388}
389
390#[cfg(feature = "geo-types")]
391impl From<PointZ> for geo_types::Point<f64> {
392 fn from(p: PointZ) -> Self {
393 geo_types::Point::new(p.x, p.y)
394 }
395}
396
397#[cfg(feature = "geo-types")]
398impl From<geo_types::Point<f64>> for PointZ {
399 fn from(p: geo_types::Point<f64>) -> Self {
400 PointZ {
401 x: p.x(),
402 y: p.y(),
403 ..Default::default()
404 }
405 }
406}
407
408#[cfg(feature = "geo-types")]
409impl From<geo_types::Coordinate<f64>> for PointZ {
410 fn from(c: geo_types::Coordinate<f64>) -> Self {
411 PointZ::new(c.x, c.y, 0.0, NO_DATA)
412 }
413}
414
415#[cfg(feature = "geo-types")]
416impl From<PointZ> for geo_types::Coordinate<f64> {
417 fn from(p: PointZ) -> Self {
418 geo_types::Coordinate { x: p.x, y: p.y }
419 }
420}
421
422#[cfg(test)]
423#[cfg(feature = "geo-types")]
424mod test_geo_types {
425 use super::*;
426 #[test]
427 fn geo_types_point_conversion() {
428 let p = Point::new(14.0, 42.65);
429 let gp: geo_types::Point<f64> = p.into();
430
431 assert_eq!(gp.x(), 14.0);
432 assert_eq!(gp.y(), 42.65);
433
434 let p: Point = gp.into();
435 assert_eq!(p.x, 14.0);
436 assert_eq!(p.y, 42.65);
437 }
438
439 #[test]
440 fn geo_types_point_m_conversion() {
441 let p = PointM::new(14.0, 42.65, 652.3);
442 let gp: geo_types::Point<f64> = p.into();
443
444 assert_eq!(gp.x(), 14.0);
445 assert_eq!(gp.y(), 42.65);
446
447 let p: PointM = gp.into();
448 assert_eq!(p.x, 14.0);
449 assert_eq!(p.y, 42.65);
450 assert_eq!(p.m, NO_DATA);
451 }
452
453 #[test]
454 fn geo_types_point_z_conversion() {
455 let p = PointZ::new(14.0, 42.65, 111.0, 652.3);
456 let gp: geo_types::Point<f64> = p.into();
457
458 assert_eq!(gp.x(), 14.0);
459 assert_eq!(gp.y(), 42.65);
460
461 let p: PointZ = gp.into();
462 assert_eq!(p.x, 14.0);
463 assert_eq!(p.y, 42.65);
464 assert_eq!(p.z, 0.0);
465 assert_eq!(p.m, NO_DATA);
466 }
467}