use std::fmt;
use std::io::{Read, Write};
use std::mem::size_of;
use std::ops::Index;
use std::slice::SliceIndex;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use super::io::*;
use super::traits::{GrowablePoint, ShrinkablePoint};
use super::EsriShape;
use super::{ConcreteReadableShape, GenericBBox};
use super::{Error, ShapeType};
use super::{HasShapeType, WritableShape};
use super::{Point, PointM, PointZ};
#[cfg(feature = "geo-types")]
use geo_types;
#[derive(Debug, Clone, PartialEq)]
pub struct GenericMultipoint<PointType> {
pub(crate) bbox: GenericBBox<PointType>,
pub(crate) points: Vec<PointType>,
}
impl<PointType: ShrinkablePoint + GrowablePoint + Copy> GenericMultipoint<PointType> {
pub fn new(points: Vec<PointType>) -> Self {
let bbox = GenericBBox::<PointType>::from_points(&points);
Self { bbox, points }
}
}
impl<PointType> GenericMultipoint<PointType> {
#[inline]
pub fn bbox(&self) -> &GenericBBox<PointType> {
&self.bbox
}
#[inline]
pub fn points(&self) -> &[PointType] {
&self.points
}
#[inline]
pub fn point(&self, index: usize) -> Option<&PointType> {
self.points.get(index)
}
#[inline]
pub fn into_inner(self) -> Vec<PointType> {
self.points
}
}
impl<PointType> From<Vec<PointType>> for GenericMultipoint<PointType>
where
PointType: ShrinkablePoint + GrowablePoint + Copy,
{
fn from(points: Vec<PointType>) -> Self {
Self::new(points)
}
}
impl<PointType, I: SliceIndex<[PointType]>> Index<I> for GenericMultipoint<PointType> {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
Index::index(&self.points, index)
}
}
macro_rules! impl_from_multipoint_to_vec_for_point_type {
($PointType:ty) => {
impl From<GenericMultipoint<$PointType>> for Vec<$PointType> {
fn from(multipoints: GenericMultipoint<$PointType>) -> Self {
multipoints.points
}
}
};
}
impl_from_multipoint_to_vec_for_point_type!(Point);
impl_from_multipoint_to_vec_for_point_type!(PointM);
impl_from_multipoint_to_vec_for_point_type!(PointZ);
#[cfg(feature = "geo-types")]
impl<PointType> From<GenericMultipoint<PointType>> for geo_types::MultiPoint<f64>
where
geo_types::Point<f64>: From<PointType>,
{
fn from(multi_points: GenericMultipoint<PointType>) -> Self {
multi_points
.points
.into_iter()
.map(geo_types::Point::from)
.collect::<Vec<geo_types::Point<f64>>>()
.into()
}
}
#[cfg(feature = "geo-types")]
impl<PointType> From<geo_types::MultiPoint<f64>> for GenericMultipoint<PointType>
where
PointType: From<geo_types::Point<f64>> + ShrinkablePoint + GrowablePoint + Copy,
{
fn from(mp: geo_types::MultiPoint<f64>) -> Self {
let points = mp.into_iter().map(|p| p.into()).collect();
Self::new(points)
}
}
pub type Multipoint = GenericMultipoint<Point>;
impl Multipoint {
pub(crate) fn size_of_record(num_points: i32) -> usize {
let mut size = 0usize;
size += 4 * size_of::<f64>(); size += size_of::<i32>(); size += size_of::<Point>() * num_points as usize;
size
}
}
impl fmt::Display for Multipoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Multipoint({} points)", self.points.len())
}
}
impl HasShapeType for Multipoint {
fn shapetype() -> ShapeType {
ShapeType::Multipoint
}
}
impl ConcreteReadableShape for Multipoint {
fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
let mut bbox = GenericBBox::<Point>::default();
bbox_read_xy_from(&mut bbox, source)?;
let num_points = source.read_i32::<LittleEndian>()?;
if record_size == Self::size_of_record(num_points) as i32 {
let points = read_xy_in_vec_of::<Point, T>(source, num_points)?;
Ok(Self { bbox, points })
} else {
Err(Error::InvalidShapeRecordSize)
}
}
}
impl WritableShape for Multipoint {
fn size_in_bytes(&self) -> usize {
let mut size = 0usize;
size += 4 * size_of::<f64>(); size += size_of::<i32>(); size += 2 * size_of::<f64>() * self.points.len();
size
}
fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
bbox_write_xy_to(&self.bbox, dest)?;
dest.write_i32::<LittleEndian>(self.points.len() as i32)?;
for point in self.points.iter() {
dest.write_f64::<LittleEndian>(point.x)?;
dest.write_f64::<LittleEndian>(point.y)?;
}
Ok(())
}
}
impl EsriShape for Multipoint {
fn x_range(&self) -> [f64; 2] {
self.bbox.x_range()
}
fn y_range(&self) -> [f64; 2] {
self.bbox.y_range()
}
}
pub type MultipointM = GenericMultipoint<PointM>;
impl MultipointM {
pub(crate) fn size_of_record(num_points: i32, is_m_used: bool) -> usize {
let mut size = Multipoint::size_of_record(num_points);
if is_m_used {
size += 2 * size_of::<f64>(); size += size_of::<f64>() * num_points as usize; }
size
}
}
impl fmt::Display for MultipointM {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MultipointM({} points)", self.points.len())
}
}
impl HasShapeType for MultipointM {
fn shapetype() -> ShapeType {
ShapeType::MultipointM
}
}
impl ConcreteReadableShape for MultipointM {
fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
let mut bbox = GenericBBox::<PointM>::default();
bbox_read_xy_from(&mut bbox, source)?;
let num_points = source.read_i32::<LittleEndian>()?;
let size_with_m = Self::size_of_record(num_points, true) as i32;
let size_without_m = Self::size_of_record(num_points, false) as i32;
if (record_size != size_with_m) & (record_size != size_without_m) {
Err(Error::InvalidShapeRecordSize)
} else {
let m_is_used = size_with_m == record_size;
let mut points = read_xy_in_vec_of::<PointM, T>(source, num_points)?;
if m_is_used {
bbox_read_m_range_from(&mut bbox, source)?;
read_ms_into(source, &mut points)?;
}
Ok(Self { bbox, points })
}
}
}
impl WritableShape for MultipointM {
fn size_in_bytes(&self) -> usize {
let mut size = 0usize;
size += 4 * size_of::<f64>();
size += size_of::<i32>();
size += 3 * size_of::<f64>() * self.points.len();
size += 2 * size_of::<f64>();
size
}
fn write_to<T: Write>(&self, mut dest: &mut T) -> Result<(), Error> {
bbox_write_xy_to(&self.bbox, dest)?;
dest.write_i32::<LittleEndian>(self.points.len() as i32)?;
write_points(&mut dest, &self.points)?;
bbox_write_m_range_to(&self.bbox, dest)?;
write_ms(&mut dest, &self.points)?;
Ok(())
}
}
impl EsriShape for MultipointM {
fn x_range(&self) -> [f64; 2] {
self.bbox.x_range()
}
fn y_range(&self) -> [f64; 2] {
self.bbox.y_range()
}
fn m_range(&self) -> [f64; 2] {
self.bbox.m_range()
}
}
pub type MultipointZ = GenericMultipoint<PointZ>;
impl fmt::Display for MultipointZ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MultipointZ({} points)", self.points.len())
}
}
impl MultipointZ {
pub(crate) fn size_of_record(num_points: i32, is_m_used: bool) -> usize {
let mut size = Multipoint::size_of_record(num_points);
size += 2 * size_of::<f64>(); size += size_of::<f64>() * num_points as usize; if is_m_used {
size += 2 * size_of::<f64>(); size += size_of::<f64>() * num_points as usize; }
size
}
}
impl HasShapeType for MultipointZ {
fn shapetype() -> ShapeType {
ShapeType::MultipointZ
}
}
impl ConcreteReadableShape for MultipointZ {
fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
let mut bbox = GenericBBox::<PointZ>::default();
bbox_read_xy_from(&mut bbox, source)?;
let num_points = source.read_i32::<LittleEndian>()?;
let size_with_m = Self::size_of_record(num_points, true) as i32;
let size_without_m = Self::size_of_record(num_points, false) as i32;
if (record_size != size_with_m) & (record_size != size_without_m) {
Err(Error::InvalidShapeRecordSize)
} else {
let m_is_used = size_with_m == record_size;
let mut points = read_xy_in_vec_of::<PointZ, T>(source, num_points)?;
bbox_read_z_range_from(&mut bbox, source)?;
read_zs_into(source, &mut points)?;
if m_is_used {
bbox_read_m_range_from(&mut bbox, source)?;
read_ms_into(source, &mut points)?;
}
Ok(Self { bbox, points })
}
}
}
impl WritableShape for MultipointZ {
fn size_in_bytes(&self) -> usize {
let mut size = 0usize;
size += 4 * size_of::<f64>();
size += size_of::<i32>();
size += 4 * size_of::<f64>() * self.points.len();
size += 2 * size_of::<f64>();
size += 2 * size_of::<f64>();
size
}
fn write_to<T: Write>(&self, mut dest: &mut T) -> Result<(), Error> {
bbox_write_xy_to(&self.bbox, dest)?;
dest.write_i32::<LittleEndian>(self.points.len() as i32)?;
write_points(&mut dest, &self.points)?;
bbox_write_z_range_to(&self.bbox, dest)?;
write_zs(&mut dest, &self.points)?;
bbox_write_m_range_to(&self.bbox, dest)?;
write_ms(&mut dest, &self.points)?;
Ok(())
}
}
impl EsriShape for MultipointZ {
fn x_range(&self) -> [f64; 2] {
self.bbox.x_range()
}
fn y_range(&self) -> [f64; 2] {
self.bbox.y_range()
}
fn z_range(&self) -> [f64; 2] {
self.bbox.z_range()
}
fn m_range(&self) -> [f64; 2] {
self.bbox.m_range()
}
}
#[cfg(test)]
#[cfg(feature = "geo-types")]
mod test_geo_types_conversions {
use super::*;
use crate::{geo_types, NO_DATA};
use geo_types::Coordinate;
#[test]
fn test_multipoint_to_geo_types_multipoint() {
let shapefile_points = vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)];
let geo_types_coords = shapefile_points
.iter()
.copied()
.map(Coordinate::<f64>::from)
.collect::<Vec<Coordinate<f64>>>();
let expected_shapefile_multipoint = Multipoint::new(shapefile_points);
let expected_geo_types_multipoint = geo_types::MultiPoint::from(geo_types_coords);
let geo_types_multipoint: geo_types::MultiPoint<f64> =
expected_shapefile_multipoint.clone().into();
let shapefile_multipoint: Multipoint = expected_geo_types_multipoint.clone().into();
assert_eq!(geo_types_multipoint, expected_geo_types_multipoint);
assert_eq!(shapefile_multipoint, expected_shapefile_multipoint);
}
#[test]
fn test_multipoint_m_to_geo_types_multipoint() {
let points = vec![
PointM::new(120.0, 56.0, 42.2),
PointM::new(6.0, 18.7, 462.54),
];
let shapefile_multipoint = MultipointM::new(points);
let geo_types_multipoint = geo_types::MultiPoint::from(shapefile_multipoint);
let mut iter = geo_types_multipoint.into_iter();
let p1 = iter.next().unwrap();
let p2 = iter.next().unwrap();
assert_eq!(p1.x(), 120.0);
assert_eq!(p1.y(), 56.0);
assert_eq!(p2.x(), 6.0);
assert_eq!(p2.y(), 18.7);
let geo_types_multipoint: geo_types::MultiPoint<_> = vec![p1, p2].into();
let shapefile_multipoint = MultipointM::from(geo_types_multipoint);
assert_eq!(shapefile_multipoint.points[0].x, 120.0);
assert_eq!(shapefile_multipoint.points[0].y, 56.0);
assert_eq!(shapefile_multipoint.points[0].m, NO_DATA);
assert_eq!(shapefile_multipoint.points[1].x, 6.0);
assert_eq!(shapefile_multipoint.points[1].y, 18.7);
assert_eq!(shapefile_multipoint.points[0].m, NO_DATA);
}
#[test]
fn test_multipoint_z_to_geo_types_multipoint() {
let points = vec![
PointZ::new(1.0, 1.0, 17.0, 18.0),
PointZ::new(2.0, 2.0, 15.0, 16.0),
];
let shapefile_multipoint = MultipointZ::new(points);
let geo_types_multipoint = geo_types::MultiPoint::from(shapefile_multipoint);
let mut iter = geo_types_multipoint.into_iter();
let p1 = iter.next().unwrap();
let p2 = iter.next().unwrap();
assert_eq!(p1.x(), 1.0);
assert_eq!(p1.y(), 1.0);
assert_eq!(p2.x(), 2.0);
assert_eq!(p2.y(), 2.0);
let geo_types_multipoint: geo_types::MultiPoint<_> = vec![p1, p2].into();
let shapefile_multipoint = MultipointZ::from(geo_types_multipoint);
assert_eq!(shapefile_multipoint.points[0].x, 1.0);
assert_eq!(shapefile_multipoint.points[0].y, 1.0);
assert_eq!(shapefile_multipoint.points[0].z, 0.0);
assert_eq!(shapefile_multipoint.points[0].m, NO_DATA);
assert_eq!(shapefile_multipoint.points[1].x, 2.0);
assert_eq!(shapefile_multipoint.points[1].y, 2.0);
assert_eq!(shapefile_multipoint.points[0].z, 0.0);
assert_eq!(shapefile_multipoint.points[0].m, NO_DATA);
}
}
#[cfg(test)]
mod tests {
use super::{MultipointZ, PointZ};
#[test]
fn test_multipoint_index() {
let points = vec![
PointZ::new(1.0, 1.0, 17.0, 18.0),
PointZ::new(2.0, 2.0, 15.0, 16.0),
];
let multipoint = MultipointZ::new(points.clone());
assert_eq!(multipoint[0], points[0]);
assert_eq!(multipoint[1], points[1]);
assert_eq!(multipoint[..1], points[..1]);
}
}