///|
pub fn Geometry::bounds(self : Geometry) -> Bounds? {
let mut result : Bounds? = None
for coordinate in self.xy {
let b : Bounds = {
min_x: coordinate.x,
min_y: coordinate.y,
max_x: coordinate.x,
max_y: coordinate.y,
}
result = Some(
match result {
None => b
Some(old) => old.union(b)
},
)
}
for part in self.parts {
match part.bounds() {
None => ()
Some(b) =>
result = Some(
match result {
None => b
Some(old) => old.union(b)
},
)
}
}
result
}
///|
pub fn Geometry::coordinate_count(self : Geometry) -> Int32 {
let mut count = self.xy.length()
for part in self.parts {
count += part.coordinate_count()
}
count
}
///|
fn part_ends(geometry : Geometry) -> Array[Int32] {
if geometry.ends.is_empty() && !geometry.xy.is_empty() {
[geometry.xy.length()]
} else {
geometry.ends
}
}
///|
fn validate_geometry_inner(
g : Geometry,
limits : Limits,
depth : Int32,
) -> Int32 raise FgbError {
enforce(depth, limits.max_depth, "geometry depth")
enforce(g.xy.length(), limits.max_coordinates, "coordinate count")
for coordinate in g.xy {
if !finite(coordinate.x) || !finite(coordinate.y) {
raise InvalidArgument("geometry coordinates must be finite")
}
}
let mut previous = 0
for end in g.ends {
if end <= previous || end > g.xy.length() {
raise InvalidArgument("geometry ends must strictly increase within xy")
}
previous = end
}
if !g.ends.is_empty() && previous != g.xy.length() {
raise InvalidArgument("last part end must equal coordinate count")
}
match g.kind {
Unknown => raise InvalidArgument("concrete geometry needs a known type")
Point =>
if g.xy.length() != 1 || !g.parts.is_empty() || !g.ends.is_empty() {
raise InvalidArgument("Point requires exactly one xy pair")
}
MultiPoint =>
if g.xy.is_empty() || !g.parts.is_empty() || !g.ends.is_empty() {
raise InvalidArgument("MultiPoint needs xy pairs without ends or parts")
}
LineString =>
if g.xy.length() < 2 || !g.parts.is_empty() || !g.ends.is_empty() {
raise InvalidArgument("LineString requires at least two xy pairs")
}
MultiLineString => {
if !g.parts.is_empty() || g.xy.is_empty() {
raise InvalidArgument("MultiLineString requires flat xy and ends")
}
let mut start = 0
for end in part_ends(g) {
if end - start < 2 {
raise InvalidArgument(
"each line must contain at least two coordinates",
)
}
start = end
}
}
Polygon => {
if !g.parts.is_empty() || g.xy.is_empty() {
raise InvalidArgument("Polygon requires flat rings")
}
let mut start = 0
for end in part_ends(g) {
if end - start < 4 || g.xy[start] != g.xy[end - 1] {
raise InvalidArgument(
"polygon rings must be closed and contain four coordinates",
)
}
start = end
}
}
MultiPolygon => {
if !g.xy.is_empty() || !g.ends.is_empty() || g.parts.is_empty() {
raise InvalidArgument("MultiPolygon requires Polygon parts")
}
for part in g.parts {
if part.kind != Polygon {
raise InvalidArgument("MultiPolygon children must be Polygon")
}
}
}
}
let mut count = g.xy.length()
for child in g.parts {
count = checked_add(
count,
validate_geometry_inner(child, limits, depth + 1),
)
enforce(count, limits.max_coordinates, "total coordinates")
}
count
}
///|
pub fn Geometry::validate(
self : Geometry,
limits~ : Limits = Limits::default(),
) -> Unit raise FgbError {
limits.validate()
validate_geometry_inner(self, limits, 1) |> ignore
}
///|
fn read_geometry(
data : Bytes,
at : Int32,
kind : GeometryKind,
limits : Limits,
depth : Int32,
budget : Ref[Int32],
) -> Geometry raise FgbError {
enforce(depth, limits.max_depth, "geometry depth")
let table = TableView::open(data, at)
for field in 2..<6 {
if table.reference(field) != 0 {
raise Unsupported("Z/M/T/TM geometry vector")
}
}
let declared = kind_from_code(table.byte(6))
let resolved = if kind == Unknown { declared } else { kind }
if declared != Unknown && declared != resolved {
raise InvalidData(at, "geometry type conflicts with header")
}
let (xy_at, xy_count) = table.vector(
1,
8,
checked_mul(limits.max_coordinates, 2),
)
if xy_count % 2 != 0 {
raise InvalidData(xy_at, "odd xy vector length")
}
budget.val = checked_add(budget.val, xy_count / 2)
enforce(budget.val, limits.max_coordinates, "total coordinates")
let xy : Array[Coordinate] = []
for i in 0..<(xy_count / 2) {
xy.push({ x: f64(data, xy_at + i * 16), y: f64(data, xy_at + i * 16 + 8), })
}
let (ends_at, ends_count) = table.vector(0, 4, limits.max_coordinates)
let ends : Array[Int32] = []
for i in 0.. 0 && resolved != MultiPolygon {
raise Unsupported("parts on a geometry other than MultiPolygon")
}
for i in 0.. ignore
result
}
///|
fn write_geometry(b : FbBuilder, g : Geometry) -> Int32 {
let table = b.table(8)
if !g.ends.is_empty() {
let at = b.vector(g.ends.length(), 4)
for end in g.ends {
b.writer.word(end.reinterpret_as_uint())
}
b.reference(table, 0, at)
}
if !g.xy.is_empty() {
let at = b.vector(g.xy.length() * 2, 8)
for coordinate in g.xy {
b.writer.double(coordinate.x)
b.writer.double(coordinate.y)
}
b.reference(table, 1, at)
}
b.scalar(table, 6, g.kind.code())
if !g.parts.is_empty() {
let vector = b.vector(g.parts.length(), 4)
b.writer.zeros(g.parts.length() * 4)
for i in 0.. Bounds? {
match self.geometry {
Some(g) => g.bounds()
None => None
}
}