Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gpkg, GeoJSON のジオメトリ生成コードを汎化する #154

Merged
merged 2 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 105 additions & 37 deletions nusamai-geojson/src/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,91 @@
use nusamai_geometry::{MultiLineString, MultiPoint, MultiPolygon, Polygon};
use nusamai_geometry::{CoordNum, MultiLineString, MultiPoint, MultiPolygon};

/// Create a GeoJSON geometry from `nusamai_geometry::MultiPolygon`
/// Create a GeoJSON MultiPolygon from `nusamai_geometry::MultiPolygon`.
pub fn multipolygon_to_geometry(mpoly: &MultiPolygon<3>) -> geojson::Geometry {
let rings_list = mpoly
.iter()
.map(|poly| {
poly.rings()
.map(|c| c.iter_closed().map(|v| v.to_vec()).collect())
.collect::<Vec<_>>()
})
.collect();
geojson::Value::MultiPolygon(rings_list).into()
multipolygon_to_geometry_with_mapping(mpoly, |c| c.to_vec())
}

/// Create a GeoJSON geometry from vertices and MultiPolygon indices.
/// Create a GeoJSON MultiPolygon from vertices and indices.
pub fn indexed_multipolygon_to_geometry(
vertices: &[[f64; 3]],
mpoly: &MultiPolygon<1, u32>,
mpoly_idx: &MultiPolygon<1, u32>,
) -> geojson::Geometry {
let rings_list: Vec<geojson::PolygonType> = mpoly
multipolygon_to_geometry_with_mapping(mpoly_idx, |idx| vertices[idx[0] as usize].to_vec())
}

/// Create a GeoJSON MultiPolygon from `nusamai_geometry::MultiPolygon` with a mapping function.
pub fn multipolygon_to_geometry_with_mapping<const D: usize, T: CoordNum>(
mpoly: &MultiPolygon<D, T>,
mapping: impl Fn(&[T]) -> Vec<f64>,
) -> geojson::Geometry {
let coords: Vec<geojson::PolygonType> = mpoly
.iter()
.map(|poly| indexed_polygon_to_rings(vertices, &poly))
.map(|poly| {
poly.rings()
.map(|ls| {
ls.iter_closed()
.map(&mapping) // Get the actual coord values
.collect()
})
.collect::<Vec<_>>()
})
.collect();
geojson::Value::MultiPolygon(rings_list).into()
geojson::Value::MultiPolygon(coords).into()
}

fn indexed_polygon_to_rings(
vertices: &[[f64; 3]],
poly_idx: &Polygon<1, u32>,
) -> geojson::PolygonType {
poly_idx
.rings()
.map(|ls| {
ls.iter_closed()
.map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values
.collect()
})
.collect()
/// Create a GeoJSON MultiLineString from `nusamai_geometry::MultiLineString`.
pub fn multilinestring_to_geometry(mls: &MultiLineString<3>) -> geojson::Geometry {
multilinestring_to_geometry_with_mapping(mls, |c| c.to_vec())
}

/// Create a GeoJSON geometry from vertices and MultiLineString indices.
/// Create a GeoJSON MultiLineString from vertices and indices.
pub fn indexed_multilinestring_to_geometry(
vertices: &[[f64; 3]],
mls_idx: &MultiLineString<1, u32>,
) -> geojson::Geometry {
let mls_coords = mls_idx
multilinestring_to_geometry_with_mapping(mls_idx, |idx| vertices[idx[0] as usize].to_vec())
}

/// Create a GeoJSON MultiLineString from `nusamai_geometry::MultiPolygon` with a mapping function.
pub fn multilinestring_to_geometry_with_mapping<const D: usize, T: CoordNum>(
mls: &MultiLineString<D, T>,
mapping: impl Fn(&[T]) -> Vec<f64>,
) -> geojson::Geometry {
let coords = mls
.iter()
.map(|ls_idx| {
ls_idx
.iter()
.map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values
.map(&mapping) // Get the actual coord values
.collect()
})
.collect();
geojson::Value::MultiLineString(mls_coords).into()
geojson::Value::MultiLineString(coords).into()
}

/// Create a GeoJSON MultiPoint from `nusamai_geometry::MultiPoint`.
pub fn multipoint_to_geometry(mpoint: &MultiPoint<3>) -> geojson::Geometry {
multipoint_to_geometry_with_mapping(mpoint, |c| c.to_vec())
}

/// Create a GeoJSON geometry from vertices and MultiPoint indices.
/// Create a GeoJSON MultiPoint from vertices and indices.
pub fn indexed_multipoint_to_geometry(
vertices: &[[f64; 3]],
mpoint: &MultiPoint<1, u32>,
mpoint_idx: &MultiPoint<1, u32>,
) -> geojson::Geometry {
multipoint_to_geometry_with_mapping(mpoint_idx, |idx| vertices[idx[0] as usize].to_vec())
}

/// Create a GeoJSON MultiPoint from `nusamai_geometry::MultiPoint` with a mapping function.
pub fn multipoint_to_geometry_with_mapping<const D: usize, T: CoordNum>(
mpoint: &MultiPoint<D, T>,
mapping: impl Fn(&[T]) -> Vec<f64>,
) -> geojson::Geometry {
let mpoint_coords = mpoint
let coords = mpoint
.iter()
.map(|p| vertices[p[0] as usize].to_vec()) // Get the actual coord values
.map(&mapping) // Get the actual coord values
.collect();
geojson::Value::MultiPoint(mpoint_coords).into()
geojson::Value::MultiPoint(coords).into()
}

#[cfg(test)]
Expand Down Expand Up @@ -266,6 +286,33 @@
};
}

#[test]
fn test_multilinestring() {
let mut mls = MultiLineString::<3>::new();
mls.add_linestring([[11., 12., 13.], [21., 22., 23.], [31., 32., 33.]]);
mls.add_linestring([[111., 112., 113.], [121., 122., 123.], [131., 132., 133.]]);

let geom = multilinestring_to_geometry(&mls);
let geojson::Value::MultiLineString(mls) = geom.value else {
panic!("The result is not a GeoJSON MultiPolygon");

Check warning on line 297 in nusamai-geojson/src/conversion.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-geojson/src/conversion.rs#L297

Added line #L297 was not covered by tests
};
assert_eq!(
mls,
vec![
vec![
vec![11., 12., 13.],
vec![21., 22., 23.],
vec![31., 32., 33.],
],
vec![
vec![111., 112., 113.],
vec![121., 122., 123.],
vec![131., 132., 133.],
],
]
);
}

#[test]
fn test_indexed_multilinestring() {
let vertices = vec![
Expand Down Expand Up @@ -318,6 +365,27 @@
}
}

#[test]
fn test_multipoint() {
let mut mpoint = MultiPoint::<3>::new();
mpoint.push(&[11., 12., 13.]);
mpoint.push(&[21., 22., 23.]);
mpoint.push(&[31., 32., 33.]);

let geom = multipoint_to_geometry(&mpoint);
let geojson::Value::MultiPoint(mpoint) = geom.value else {
panic!("The result is not a GeoJSON MultiPolygon");

Check warning on line 377 in nusamai-geojson/src/conversion.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-geojson/src/conversion.rs#L377

Added line #L377 was not covered by tests
};
assert_eq!(
mpoint,
vec![
vec![11., 12., 13.],
vec![21., 22., 23.],
vec![31., 32., 33.],
]
);
}

#[test]
fn test_indexed_multipoint() {
let vertices = vec![[0., 0., 111.], [1., 2., 222.], [3., 4., 333.]];
Expand Down
164 changes: 55 additions & 109 deletions nusamai-gpkg/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//!
//! cf. https://www.geopackage.org/spec130/#gpb_format

use nusamai_geometry::{MultiPolygon, Polygon};
use nusamai_geometry::{CoordNum, MultiPolygon, Polygon};
use std::io::Write;

#[repr(u8)]
pub enum WkbByteOrder {
Expand Down Expand Up @@ -44,136 +45,80 @@ pub enum WkbGeometryType {
GeometryCollectionZM = 3007,
}

fn geometry_header(srs_id: i32) -> Vec<u8> {
let mut header: Vec<u8> = Vec::with_capacity(8);
header.extend_from_slice(&[0x47, 0x50]); // Magic number
header.push(0x00); // Version
header.push(0b00000001); // Flags
header.extend_from_slice(&i32::to_le_bytes(srs_id)); // SRS ID
header
fn write_geometry_header<W: Write>(writer: &mut W, srs_id: i32) -> std::io::Result<()> {
writer.write_all(&[0x47, 0x50])?; // Magic number
writer.write_all(&[
0x00, // Version
0b00000001, // Flags
])?;
writer.write_all(&i32::to_le_bytes(srs_id))?; // SRS ID
Ok(())
}

fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> Vec<Vec<Vec<f64>>> {
let rings: Vec<_> = poly
.rings()
.map(|ls| {
let coords: Vec<_> = ls
.iter_closed()
.map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values
.collect();
coords
})
.collect();

rings
fn write_polygon_body<W: Write, const D: usize, T: CoordNum>(
writer: &mut W,
poly: &Polygon<D, T>,
mapping: impl Fn(&[T]) -> [f64; 3],
) -> std::io::Result<()> {
// Byte order: Little endian (1)
writer.write_all(&[WkbByteOrder::LittleEndian as u8])?;

// Geometry type: wkbPolygonZ (1003)
writer.write_all(&(WkbGeometryType::PolygonZ as u32).to_le_bytes())?;

// numRings
writer.write_all(&(poly.rings().count() as u32).to_le_bytes())?;

for ring in poly.rings() {
// numPoints
writer.write_all(&(ring.iter_closed().count() as u32).to_le_bytes())?;

for idx in ring.iter_closed() {
let [x, y, z] = mapping(idx);
writer.write_all(&f64::to_le_bytes(x))?;
writer.write_all(&f64::to_le_bytes(y))?;
writer.write_all(&f64::to_le_bytes(z))?;
}
}
Ok(())
}

pub fn multipolygon_to_bytes(
pub fn write_indexed_multipolygon<W: Write>(
writer: &mut W,
vertices: &[[f64; 3]],
mpoly: &MultiPolygon<'_, 1, u32>,
srs_id: i32,
) -> Vec<u8> {
let mut bytes: Vec<u8> = geometry_header(srs_id);
) -> std::io::Result<()> {
write_geometry_header(writer, srs_id)?;
write_multipolygon_body(writer, mpoly, |idx| vertices[idx[0] as usize])?;
Ok(())
}

fn write_multipolygon_body<W: Write, const D: usize, T: CoordNum>(
writer: &mut W,
mpoly: &MultiPolygon<'_, D, T>,
mapping: impl Fn(&[T]) -> [f64; 3],
) -> std::io::Result<()> {
// Byte order: Little endian (1)
bytes.push(WkbByteOrder::LittleEndian as u8);
writer.write_all(&[WkbByteOrder::LittleEndian as u8])?;

// Geometry type: wkbMultiPolygonZ (1006)
bytes.extend_from_slice(&(WkbGeometryType::MultiPolygonZ as u32).to_le_bytes());
writer.write_all(&(WkbGeometryType::MultiPolygonZ as u32).to_le_bytes())?;

// numPolygons
bytes.extend_from_slice(&(mpoly.len() as u32).to_le_bytes());
writer.write_all(&(mpoly.len() as u32).to_le_bytes())?;

for poly in mpoly {
// Byte order: Little endian (1)
bytes.push(WkbByteOrder::LittleEndian as u8);

// Geometry type: wkbPolygonZ (1003)
bytes.extend_from_slice(&(WkbGeometryType::PolygonZ as u32).to_le_bytes());

let rings = polygon_to_rings(vertices, &poly);

// numRings
bytes.extend_from_slice(&(rings.len() as u32).to_le_bytes());

for ring in rings {
// numPoints
bytes.extend_from_slice(&(ring.len() as u32).to_le_bytes());

for coord in ring {
bytes.extend_from_slice(&f64::to_le_bytes(coord[0]));
bytes.extend_from_slice(&f64::to_le_bytes(coord[1]));
bytes.extend_from_slice(&f64::to_le_bytes(coord[2]));
}
}
write_polygon_body(writer, &poly, &mapping)?;
}

bytes
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_polygon_to_rings() {
let vertices: Vec<[f64; 3]> = vec![
// exterior (vertex 0~3)
[0., 0., 111.],
[5., 0., 111.],
[5., 5., 111.],
[0., 5., 111.],
// interior 1 (vertex 4~7)
[1., 1., 111.],
[2., 1., 111.],
[2., 2., 111.],
[1., 2., 111.],
// interior 2 (vertex 8~11)
[3., 3., 111.],
[4., 3., 111.],
[4., 4., 111.],
[3., 4., 111.],
];

let mut poly = Polygon::<'_, 1, u32>::new();
poly.add_ring([[0], [1], [2], [3]]);
poly.add_ring([[4], [5], [6], [7]]);
poly.add_ring([[8], [9], [10], [11]]);

let rings = polygon_to_rings(&vertices, &poly);

assert_eq!(rings.len(), 3);

for (i, ri) in rings.iter().enumerate() {
match i {
0 => {
assert_eq!(ri.len(), 5);
assert_eq!(ri[0], vec![0., 0., 111.]);
assert_eq!(ri[1], vec![5., 0., 111.]);
assert_eq!(ri[2], vec![5., 5., 111.]);
assert_eq!(ri[3], vec![0., 5., 111.]);
assert_eq!(ri[4], vec![0., 0., 111.]);
}
1 => {
assert_eq!(ri.len(), 5);
assert_eq!(ri[0], vec![1., 1., 111.]);
assert_eq!(ri[1], vec![2., 1., 111.]);
assert_eq!(ri[2], vec![2., 2., 111.]);
assert_eq!(ri[3], vec![1., 2., 111.]);
assert_eq!(ri[4], vec![1., 1., 111.]);
}
2 => {
assert_eq!(ri.len(), 5);
assert_eq!(ri[0], vec![3., 3., 111.]);
assert_eq!(ri[1], vec![4., 3., 111.]);
assert_eq!(ri[2], vec![4., 4., 111.]);
assert_eq!(ri[3], vec![3., 4., 111.]);
}
_ => panic!("Unexpected ring index"),
}
}
}

#[test]
fn test_multipolygon_to_bytes() {
let vertices: Vec<[f64; 3]> = vec![
Expand All @@ -194,7 +139,8 @@ mod tests {
mpoly.add_exterior([[0], [1], [2], [3], [0]]);
mpoly.add_interior([[4], [5], [6], [7], [4]]);

let bytes = multipolygon_to_bytes(&vertices, &mpoly, 1234);
let mut bytes = Vec::new();
write_indexed_multipolygon(&mut bytes, &vertices, &mpoly, 1234).unwrap();

assert_eq!(bytes.len(), 274);

Expand Down
Loading