use ::geo::algorithm::bounding_rect::BoundingRect;
use ::geo::geometry::{
Coord, Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon,
Point, Polygon, Rect,
};
use geo_index::rtree::sort::HilbertSort;
use geo_index::rtree::{RTreeBuilder, RTreeIndex, RTreeRef};
pub fn parse_geojson(value: &serde_json::Value) -> Option<Geometry<f64>> {
let obj = value.as_object()?;
let shape_type = obj.get("type").and_then(|v| v.as_str())?;
match shape_type {
"Point" | "point" => {
let coords = obj.get("coordinates")?;
let c = parse_coord(coords)?;
Some(Geometry::Point(Point(c)))
}
"MultiPoint" | "multipoint" => {
let coords = obj.get("coordinates")?.as_array()?;
let points: Option<Vec<Point<f64>>> =
coords.iter().map(|c| parse_coord(c).map(Point)).collect();
Some(Geometry::MultiPoint(MultiPoint(points?)))
}
"LineString" | "linestring" => {
let coords = obj.get("coordinates")?;
let line = parse_linestring(coords)?;
Some(Geometry::LineString(line))
}
"MultiLineString" | "multilinestring" => {
let coords = obj.get("coordinates")?.as_array()?;
let lines: Option<Vec<LineString<f64>>> = coords.iter().map(parse_linestring).collect();
Some(Geometry::MultiLineString(MultiLineString(lines?)))
}
"Polygon" | "polygon" => {
let coords = obj.get("coordinates")?;
let poly = parse_polygon(coords)?;
Some(Geometry::Polygon(poly))
}
"MultiPolygon" | "multipolygon" => {
let coords = obj.get("coordinates")?.as_array()?;
let polys: Option<Vec<Polygon<f64>>> = coords.iter().map(parse_polygon).collect();
Some(Geometry::MultiPolygon(MultiPolygon(polys?)))
}
"GeometryCollection" | "geometrycollection" => {
let geoms = obj.get("geometries")?.as_array()?;
let parsed: Option<Vec<Geometry<f64>>> = geoms.iter().map(parse_geojson).collect();
Some(Geometry::GeometryCollection(GeometryCollection(parsed?)))
}
"envelope" | "Envelope" => {
let coords = obj.get("coordinates")?.as_array()?;
if coords.len() != 2 {
return None;
}
let tl = parse_coord(&coords[0])?; let br = parse_coord(&coords[1])?; Some(Geometry::Rect(Rect::new(
Coord { x: tl.x, y: br.y }, Coord { x: br.x, y: tl.y }, )))
}
_ => None,
}
}
fn parse_coord(value: &serde_json::Value) -> Option<Coord<f64>> {
let arr = value.as_array()?;
if arr.len() < 2 {
return None;
}
let x = arr[0].as_f64()?; let y = arr[1].as_f64()?; Some(Coord { x, y })
}
fn parse_linestring(value: &serde_json::Value) -> Option<LineString<f64>> {
let arr = value.as_array()?;
let coords: Option<Vec<Coord<f64>>> = arr.iter().map(parse_coord).collect();
Some(LineString(coords?))
}
fn parse_polygon(value: &serde_json::Value) -> Option<Polygon<f64>> {
let rings = value.as_array()?;
if rings.is_empty() {
return None;
}
let exterior = parse_linestring(&rings[0])?;
let interiors: Option<Vec<LineString<f64>>> = rings[1..].iter().map(parse_linestring).collect();
Some(Polygon::new(exterior, interiors?))
}
pub fn compute_bbox(geom: &Geometry<f64>) -> Option<(f64, f64, f64, f64)> {
let rect = geom.bounding_rect()?;
Some((rect.min().x, rect.min().y, rect.max().x, rect.max().y))
}
const TAG_POINT: u8 = 0;
const TAG_MULTI_POINT: u8 = 1;
const TAG_LINE_STRING: u8 = 2;
const TAG_MULTI_LINE_STRING: u8 = 3;
const TAG_POLYGON: u8 = 4;
const TAG_MULTI_POLYGON: u8 = 5;
const TAG_GEOMETRY_COLLECTION: u8 = 6;
const TAG_RECT: u8 = 7;
pub fn serialize_shape(geom: &Geometry<f64>) -> Vec<u8> {
let mut buf = Vec::new();
write_shape(geom, &mut buf);
buf
}
fn write_coord(c: &Coord<f64>, buf: &mut Vec<u8>) {
buf.extend_from_slice(&c.x.to_le_bytes());
buf.extend_from_slice(&c.y.to_le_bytes());
}
fn write_coords(coords: &[Coord<f64>], buf: &mut Vec<u8>) {
buf.extend_from_slice(&(coords.len() as u32).to_le_bytes());
for c in coords {
write_coord(c, buf);
}
}
fn write_shape(geom: &Geometry<f64>, buf: &mut Vec<u8>) {
match geom {
Geometry::Point(p) => {
buf.push(TAG_POINT);
write_coord(&p.0, buf);
}
Geometry::MultiPoint(mp) => {
buf.push(TAG_MULTI_POINT);
buf.extend_from_slice(&(mp.0.len() as u32).to_le_bytes());
for p in &mp.0 {
write_coord(&p.0, buf);
}
}
Geometry::LineString(ls) => {
buf.push(TAG_LINE_STRING);
write_coords(&ls.0, buf);
}
Geometry::MultiLineString(mls) => {
buf.push(TAG_MULTI_LINE_STRING);
buf.extend_from_slice(&(mls.0.len() as u32).to_le_bytes());
for ls in &mls.0 {
write_coords(&ls.0, buf);
}
}
Geometry::Polygon(poly) => {
buf.push(TAG_POLYGON);
let num_rings = 1 + poly.interiors().len();
buf.extend_from_slice(&(num_rings as u32).to_le_bytes());
write_coords(&poly.exterior().0, buf);
for ring in poly.interiors() {
write_coords(&ring.0, buf);
}
}
Geometry::MultiPolygon(mp) => {
buf.push(TAG_MULTI_POLYGON);
buf.extend_from_slice(&(mp.0.len() as u32).to_le_bytes());
for poly in &mp.0 {
let num_rings = 1 + poly.interiors().len();
buf.extend_from_slice(&(num_rings as u32).to_le_bytes());
write_coords(&poly.exterior().0, buf);
for ring in poly.interiors() {
write_coords(&ring.0, buf);
}
}
}
Geometry::GeometryCollection(gc) => {
buf.push(TAG_GEOMETRY_COLLECTION);
buf.extend_from_slice(&(gc.0.len() as u32).to_le_bytes());
for g in &gc.0 {
let child = serialize_shape(g);
buf.extend_from_slice(&(child.len() as u32).to_le_bytes());
buf.extend_from_slice(&child);
}
}
Geometry::Rect(r) => {
buf.push(TAG_RECT);
write_coord(&r.min(), buf);
write_coord(&r.max(), buf);
}
Geometry::Line(l) => {
buf.push(TAG_LINE_STRING);
let coords = vec![l.start, l.end];
write_coords(&coords, buf);
}
Geometry::Triangle(t) => {
buf.push(TAG_POLYGON);
buf.extend_from_slice(&1u32.to_le_bytes()); let coords = vec![t.0, t.1, t.2, t.0]; write_coords(&coords, buf);
}
}
}
pub fn deserialize_shape(data: &[u8]) -> Option<(Geometry<f64>, usize)> {
if data.is_empty() {
return None;
}
let tag = data[0];
let mut pos = 1;
match tag {
TAG_POINT => {
let c = read_coord(data, &mut pos)?;
Some((Geometry::Point(Point(c)), pos))
}
TAG_MULTI_POINT => {
let count = read_u32(data, &mut pos)? as usize;
let mut points = Vec::with_capacity(count);
for _ in 0..count {
points.push(Point(read_coord(data, &mut pos)?));
}
Some((Geometry::MultiPoint(MultiPoint(points)), pos))
}
TAG_LINE_STRING => {
let coords = read_coords(data, &mut pos)?;
Some((Geometry::LineString(LineString(coords)), pos))
}
TAG_MULTI_LINE_STRING => {
let count = read_u32(data, &mut pos)? as usize;
let mut lines = Vec::with_capacity(count);
for _ in 0..count {
lines.push(LineString(read_coords(data, &mut pos)?));
}
Some((Geometry::MultiLineString(MultiLineString(lines)), pos))
}
TAG_POLYGON => {
let poly = read_polygon(data, &mut pos)?;
Some((Geometry::Polygon(poly), pos))
}
TAG_MULTI_POLYGON => {
let count = read_u32(data, &mut pos)? as usize;
let mut polys = Vec::with_capacity(count);
for _ in 0..count {
polys.push(read_polygon(data, &mut pos)?);
}
Some((Geometry::MultiPolygon(MultiPolygon(polys)), pos))
}
TAG_GEOMETRY_COLLECTION => {
let count = read_u32(data, &mut pos)? as usize;
let mut geoms = Vec::with_capacity(count);
for _ in 0..count {
let len = read_u32(data, &mut pos)? as usize;
let (g, _) = deserialize_shape(&data[pos..pos + len])?;
geoms.push(g);
pos += len;
}
Some((Geometry::GeometryCollection(GeometryCollection(geoms)), pos))
}
TAG_RECT => {
let min = read_coord(data, &mut pos)?;
let max = read_coord(data, &mut pos)?;
Some((Geometry::Rect(Rect::new(min, max)), pos))
}
_ => None,
}
}
fn read_f64(data: &[u8], pos: &mut usize) -> Option<f64> {
if *pos + 8 > data.len() {
return None;
}
let val = f64::from_le_bytes(data[*pos..*pos + 8].try_into().ok()?);
*pos += 8;
Some(val)
}
fn read_u32(data: &[u8], pos: &mut usize) -> Option<u32> {
if *pos + 4 > data.len() {
return None;
}
let val = u32::from_le_bytes(data[*pos..*pos + 4].try_into().ok()?);
*pos += 4;
Some(val)
}
fn read_coord(data: &[u8], pos: &mut usize) -> Option<Coord<f64>> {
let x = read_f64(data, pos)?;
let y = read_f64(data, pos)?;
Some(Coord { x, y })
}
fn read_coords(data: &[u8], pos: &mut usize) -> Option<Vec<Coord<f64>>> {
let count = read_u32(data, pos)? as usize;
let mut coords = Vec::with_capacity(count);
for _ in 0..count {
coords.push(read_coord(data, pos)?);
}
Some(coords)
}
fn read_polygon(data: &[u8], pos: &mut usize) -> Option<Polygon<f64>> {
let num_rings = read_u32(data, pos)? as usize;
if num_rings == 0 {
return None;
}
let exterior = LineString(read_coords(data, pos)?);
let mut interiors = Vec::with_capacity(num_rings.saturating_sub(1));
for _ in 1..num_rings {
interiors.push(LineString(read_coords(data, pos)?));
}
Some(Polygon::new(exterior, interiors))
}
#[derive(Clone)]
pub struct GeoShapeStore {
count: usize,
bbox_min_xs: Vec<f64>,
bbox_min_ys: Vec<f64>,
bbox_max_xs: Vec<f64>,
bbox_max_ys: Vec<f64>,
shape_offsets: Vec<u32>,
shape_data: Vec<u8>,
rtree_data: Vec<u8>,
}
impl GeoShapeStore {
pub fn new() -> Self {
Self {
count: 0,
bbox_min_xs: Vec::new(),
bbox_min_ys: Vec::new(),
bbox_max_xs: Vec::new(),
bbox_max_ys: Vec::new(),
shape_offsets: Vec::new(),
shape_data: Vec::new(),
rtree_data: Vec::new(),
}
}
pub fn add(&mut self, geom: &Geometry<f64>) {
let offset = self.shape_data.len() as u32;
let serialized = serialize_shape(geom);
self.shape_data.extend_from_slice(&serialized);
self.shape_offsets.push(offset);
if let Some((min_x, min_y, max_x, max_y)) = compute_bbox(geom) {
self.bbox_min_xs.push(min_x);
self.bbox_min_ys.push(min_y);
self.bbox_max_xs.push(max_x);
self.bbox_max_ys.push(max_y);
} else {
self.bbox_min_xs.push(f64::NAN);
self.bbox_min_ys.push(f64::NAN);
self.bbox_max_xs.push(f64::NAN);
self.bbox_max_ys.push(f64::NAN);
}
self.count += 1;
}
pub fn add_null(&mut self) {
self.bbox_min_xs.push(f64::NAN);
self.bbox_min_ys.push(f64::NAN);
self.bbox_max_xs.push(f64::NAN);
self.bbox_max_ys.push(f64::NAN);
self.shape_offsets.push(u32::MAX);
self.count += 1;
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn shape_offsets_ref(&self) -> &[u32] {
&self.shape_offsets
}
pub fn rtree_data(&self) -> &[u8] {
&self.rtree_data
}
pub fn is_rect_shape(&self, doc_id: u32) -> bool {
let i = doc_id as usize;
if i >= self.count {
return false;
}
let offset = self.shape_offsets[i];
if offset == u32::MAX {
return false;
}
let o = offset as usize;
o < self.shape_data.len() && self.shape_data[o] == TAG_RECT
}
pub fn get_bbox(&self, doc_id: u32) -> Option<(f64, f64, f64, f64)> {
let i = doc_id as usize;
if i >= self.count {
return None;
}
let min_x = self.bbox_min_xs[i];
if min_x.is_nan() {
return None;
}
Some((
min_x,
self.bbox_min_ys[i],
self.bbox_max_xs[i],
self.bbox_max_ys[i],
))
}
pub fn get_shape(&self, doc_id: u32) -> Option<Geometry<f64>> {
let i = doc_id as usize;
if i >= self.count {
return None;
}
let offset = self.shape_offsets[i];
if offset == u32::MAX {
return None;
}
let (geom, _) = deserialize_shape(&self.shape_data[offset as usize..])?;
Some(geom)
}
pub fn build_rtree(&self) -> Vec<u8> {
let valid_count = self
.shape_offsets
.iter()
.filter(|&&o| o != u32::MAX)
.count();
if valid_count == 0 {
return Vec::new();
}
let mut builder = RTreeBuilder::<f64>::new(valid_count as u32);
for i in 0..self.count {
if self.shape_offsets[i] != u32::MAX {
builder.add(
self.bbox_min_xs[i],
self.bbox_min_ys[i],
self.bbox_max_xs[i],
self.bbox_max_ys[i],
);
}
}
let tree = builder.finish::<HilbertSort>();
tree.into_inner()
}
pub fn search_rtree(
rtree_data: &[u8],
query_bbox: (f64, f64, f64, f64),
shape_offsets: &[u32],
) -> Vec<u32> {
if rtree_data.is_empty() {
return Vec::new();
}
let tree = match RTreeRef::<f64>::try_new(&rtree_data) {
Ok(t) => t,
Err(_) => return Vec::new(),
};
let (min_x, min_y, max_x, max_y) = query_bbox;
let rtree_indices = tree.search(min_x, min_y, max_x, max_y);
let doc_id_map: Vec<u32> = shape_offsets
.iter()
.enumerate()
.filter(|(_, o)| **o != u32::MAX)
.map(|(i, _)| i as u32)
.collect();
rtree_indices
.iter()
.filter_map(|&idx| doc_id_map.get(idx as usize).copied())
.collect()
}
pub fn to_bytes(&self) -> Vec<u8> {
let rtree_data = self.build_rtree();
let mut buf = Vec::new();
buf.extend_from_slice(&(self.count as u32).to_le_bytes());
for v in &self.bbox_min_xs {
buf.extend_from_slice(&v.to_le_bytes());
}
for v in &self.bbox_min_ys {
buf.extend_from_slice(&v.to_le_bytes());
}
for v in &self.bbox_max_xs {
buf.extend_from_slice(&v.to_le_bytes());
}
for v in &self.bbox_max_ys {
buf.extend_from_slice(&v.to_le_bytes());
}
for v in &self.shape_offsets {
buf.extend_from_slice(&v.to_le_bytes());
}
buf.extend_from_slice(&(self.shape_data.len() as u32).to_le_bytes());
buf.extend_from_slice(&self.shape_data);
buf.extend_from_slice(&(rtree_data.len() as u32).to_le_bytes());
buf.extend_from_slice(&rtree_data);
buf
}
pub fn from_bytes(data: &[u8]) -> Self {
if data.len() < 4 {
return Self::new();
}
let count = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
let mut pos = 4;
let read_f64_vec = |data: &[u8], pos: &mut usize, n: usize| -> Vec<f64> {
let mut v = Vec::with_capacity(n);
for _ in 0..n {
let val = f64::from_le_bytes(data[*pos..*pos + 8].try_into().unwrap());
v.push(val);
*pos += 8;
}
v
};
let bbox_min_xs = read_f64_vec(data, &mut pos, count);
let bbox_min_ys = read_f64_vec(data, &mut pos, count);
let bbox_max_xs = read_f64_vec(data, &mut pos, count);
let bbox_max_ys = read_f64_vec(data, &mut pos, count);
let mut shape_offsets = Vec::with_capacity(count);
for _ in 0..count {
let v = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
shape_offsets.push(v);
pos += 4;
}
let shape_data_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
let shape_data = data[pos..pos + shape_data_len].to_vec();
pos += shape_data_len;
let rtree_data = if pos + 4 <= data.len() {
let rtree_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
if rtree_len > 0 && pos + rtree_len <= data.len() {
data[pos..pos + rtree_len].to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
};
Self {
count,
bbox_min_xs,
bbox_min_ys,
bbox_max_xs,
bbox_max_ys,
shape_offsets,
shape_data,
rtree_data,
}
}
pub fn rtree_offset_in_bytes(data: &[u8]) -> Option<(usize, usize)> {
if data.len() < 4 {
return None;
}
let count = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
let mut pos = 4 + count * 8 * 4 + count * 4;
if pos + 4 > data.len() {
return None;
}
let shape_data_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4 + shape_data_len;
if pos + 4 > data.len() {
return None;
}
let rtree_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
if rtree_len == 0 || pos + rtree_len > data.len() {
return None;
}
Some((pos, rtree_len))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_point() {
let v = json!({"type": "Point", "coordinates": [-73.98, 40.75]});
let g = parse_geojson(&v).unwrap();
if let Geometry::Point(p) = g {
assert!((p.x() - (-73.98)).abs() < 1e-10);
assert!((p.y() - 40.75).abs() < 1e-10);
} else {
panic!("expected Point");
}
}
#[test]
fn parse_polygon() {
let v = json!({
"type": "Polygon",
"coordinates": [[[0.0,0.0],[10.0,0.0],[10.0,10.0],[0.0,10.0],[0.0,0.0]]]
});
let g = parse_geojson(&v).unwrap();
assert!(matches!(g, Geometry::Polygon(_)));
}
#[test]
fn parse_envelope() {
let v = json!({"type": "envelope", "coordinates": [[-77.0, 39.0], [-75.0, 38.0]]});
let g = parse_geojson(&v).unwrap();
if let Geometry::Rect(r) = g {
assert!((r.min().x - (-77.0)).abs() < 1e-10);
assert!((r.min().y - 38.0).abs() < 1e-10);
assert!((r.max().x - (-75.0)).abs() < 1e-10);
assert!((r.max().y - 39.0).abs() < 1e-10);
} else {
panic!("expected Rect");
}
}
#[test]
fn parse_invalid_returns_none() {
assert!(parse_geojson(&json!({"type": "Unknown"})).is_none());
assert!(parse_geojson(&json!(42)).is_none());
}
#[test]
fn serialize_roundtrip_point() {
let geom = Geometry::Point(Point::new(-73.98, 40.75));
let bytes = serialize_shape(&geom);
let (decoded, consumed) = deserialize_shape(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(geom, decoded);
}
#[test]
fn serialize_roundtrip_polygon() {
let poly = Polygon::new(
LineString(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 10.0, y: 10.0 },
Coord { x: 0.0, y: 10.0 },
Coord { x: 0.0, y: 0.0 },
]),
vec![],
);
let geom = Geometry::Polygon(poly);
let bytes = serialize_shape(&geom);
let (decoded, _) = deserialize_shape(&bytes).unwrap();
assert_eq!(geom, decoded);
}
#[test]
fn serialize_roundtrip_rect() {
let geom = Geometry::Rect(Rect::new(
Coord { x: -77.0, y: 38.0 },
Coord { x: -75.0, y: 39.0 },
));
let bytes = serialize_shape(&geom);
let (decoded, _) = deserialize_shape(&bytes).unwrap();
assert_eq!(geom, decoded);
}
#[test]
fn store_add_and_get() {
let mut store = GeoShapeStore::new();
let poly = Geometry::Polygon(Polygon::new(
LineString(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 10.0, y: 10.0 },
Coord { x: 0.0, y: 10.0 },
Coord { x: 0.0, y: 0.0 },
]),
vec![],
));
store.add(&poly);
store.add_null();
assert_eq!(store.len(), 2);
assert!(store.get_bbox(0).is_some());
assert!(store.get_bbox(1).is_none());
assert_eq!(store.get_shape(0).unwrap(), poly);
assert!(store.get_shape(1).is_none());
}
#[test]
fn store_serialization_roundtrip() {
let mut store = GeoShapeStore::new();
store.add(&Geometry::Point(Point::new(1.0, 2.0)));
store.add(&Geometry::Polygon(Polygon::new(
LineString(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 5.0, y: 0.0 },
Coord { x: 5.0, y: 5.0 },
Coord { x: 0.0, y: 0.0 },
]),
vec![],
)));
let bytes = store.to_bytes();
let restored = GeoShapeStore::from_bytes(&bytes);
assert_eq!(restored.len(), 2);
assert_eq!(restored.get_shape(0).unwrap(), store.get_shape(0).unwrap());
assert_eq!(restored.get_shape(1).unwrap(), store.get_shape(1).unwrap());
}
#[test]
fn rtree_search() {
let mut store = GeoShapeStore::new();
store.add(&Geometry::Polygon(Polygon::new(
LineString(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 10.0, y: 10.0 },
Coord { x: 0.0, y: 10.0 },
Coord { x: 0.0, y: 0.0 },
]),
vec![],
)));
store.add(&Geometry::Polygon(Polygon::new(
LineString(vec![
Coord { x: 20.0, y: 20.0 },
Coord { x: 30.0, y: 20.0 },
Coord { x: 30.0, y: 30.0 },
Coord { x: 20.0, y: 30.0 },
Coord { x: 20.0, y: 20.0 },
]),
vec![],
)));
let rtree_data = store.build_rtree();
assert!(!rtree_data.is_empty());
let hits =
GeoShapeStore::search_rtree(&rtree_data, (5.0, 5.0, 15.0, 15.0), &store.shape_offsets);
assert_eq!(hits, vec![0]);
let hits = GeoShapeStore::search_rtree(
&rtree_data,
(25.0, 25.0, 35.0, 35.0),
&store.shape_offsets,
);
assert_eq!(hits, vec![1]);
let hits =
GeoShapeStore::search_rtree(&rtree_data, (0.0, 0.0, 30.0, 30.0), &store.shape_offsets);
assert!(hits.contains(&0) && hits.contains(&1));
}
}