///|
fn coordinate_z_or_zero(coordinate : Coordinate) -> Double {
match coordinate.z {
Some(value) => value
None => 0.0
}
}
///|
fn point_bounds_3d(coordinate : Coordinate) -> BoundingBox3D {
let z = coordinate_z_or_zero(coordinate)
{
min_x: coordinate.x,
min_y: coordinate.y,
min_z: z,
max_x: coordinate.x,
max_y: coordinate.y,
max_z: z,
}
}
///|
fn extend_bounds_3d(
bounds : BoundingBox3D,
coordinate : Coordinate,
) -> BoundingBox3D {
let z = coordinate_z_or_zero(coordinate)
{
min_x: if coordinate.x < bounds.min_x {
coordinate.x
} else {
bounds.min_x
},
min_y: if coordinate.y < bounds.min_y {
coordinate.y
} else {
bounds.min_y
},
min_z: if z < bounds.min_z {
z
} else {
bounds.min_z
},
max_x: if coordinate.x > bounds.max_x {
coordinate.x
} else {
bounds.max_x
},
max_y: if coordinate.y > bounds.max_y {
coordinate.y
} else {
bounds.max_y
},
max_z: if z > bounds.max_z {
z
} else {
bounds.max_z
},
}
}
///|
fn bounds_3d_from_coordinates(
coordinates : Array[Coordinate],
) -> BoundingBox3D? {
let mut result : BoundingBox3D? = None
for coordinate in coordinates {
result = match result {
Some(current) => Some(extend_bounds_3d(current, coordinate))
None => Some(point_bounds_3d(coordinate))
}
}
result
}
///|
fn bounds_3d_from_rings(rings : Array[Array[Coordinate]]) -> BoundingBox3D? {
let mut result : BoundingBox3D? = None
for ring in rings {
result = merge_bounds_3d(result, bounds_3d_from_coordinates(ring))
}
result
}
///|
fn bounds_3d_from_polygons(
polygons : Array[Array[Array[Coordinate]]],
) -> BoundingBox3D? {
let mut result : BoundingBox3D? = None
for polygon in polygons {
result = merge_bounds_3d(result, bounds_3d_from_rings(polygon))
}
result
}
///|
fn merge_bounds_3d(
left : BoundingBox3D?,
right : BoundingBox3D?,
) -> BoundingBox3D? {
match (left, right) {
(None, None) => None
(Some(value), None) => Some(value)
(None, Some(value)) => Some(value)
(Some(a), Some(b)) =>
Some({
min_x: if a.min_x < b.min_x {
a.min_x
} else {
b.min_x
},
min_y: if a.min_y < b.min_y {
a.min_y
} else {
b.min_y
},
min_z: if a.min_z < b.min_z {
a.min_z
} else {
b.min_z
},
max_x: if a.max_x > b.max_x {
a.max_x
} else {
b.max_x
},
max_y: if a.max_y > b.max_y {
a.max_y
} else {
b.max_y
},
max_z: if a.max_z > b.max_z {
a.max_z
} else {
b.max_z
},
})
}
}
///|
pub fn Geometry::bounds_3d(self : Geometry) -> BoundingBox3D? {
match self {
Point(coordinate) => Some(point_bounds_3d(coordinate))
EmptyPoint => None
LineString(coordinates) => bounds_3d_from_coordinates(coordinates)
Polygon(rings) => bounds_3d_from_rings(rings)
MultiPoint(coordinates) => bounds_3d_from_coordinates(coordinates)
MultiLineString(lines) => bounds_3d_from_rings(lines)
MultiPolygon(polygons) => bounds_3d_from_polygons(polygons)
GeometryCollection(geometries) => {
let mut result : BoundingBox3D? = None
for geometry in geometries {
result = merge_bounds_3d(result, geometry.bounds_3d())
}
result
}
}
}
///|
pub fn Geometry::dimension(self : Geometry) -> CoordinateDimension? {
match geometry_dimension_summary(self) {
Uniform(value) => Some(value)
_ => None
}
}
///|
priv enum CentroidKind {
PointKind
LineKind
AreaKind
}
///|
priv struct CentroidData {
kind : CentroidKind
weight : Double
x_sum : Double
y_sum : Double
z_sum : Double
z_complete : Bool
}
///|
fn centroid_kind_rank(kind : CentroidKind) -> Int32 {
match kind {
PointKind => 0
LineKind => 1
AreaKind => 2
}
}
///|
fn merge_centroid_data(
left : CentroidData?,
right : CentroidData?,
) -> CentroidData? {
match (left, right) {
(None, None) => None
(Some(value), None) => Some(value)
(None, Some(value)) => Some(value)
(Some(a), Some(b)) => {
let rank_a = centroid_kind_rank(a.kind)
let rank_b = centroid_kind_rank(b.kind)
if rank_a > rank_b {
Some(a)
} else if rank_b > rank_a {
Some(b)
} else {
Some({
kind: a.kind,
weight: a.weight + b.weight,
x_sum: a.x_sum + b.x_sum,
y_sum: a.y_sum + b.y_sum,
z_sum: a.z_sum + b.z_sum,
z_complete: a.z_complete && b.z_complete,
})
}
}
}
}
///|
fn centroid_data_from_points(coordinates : Array[Coordinate]) -> CentroidData? {
if coordinates.length() == 0 {
return None
}
let mut x_sum = 0.0
let mut y_sum = 0.0
let mut z_sum = 0.0
let mut z_complete = true
for coordinate in coordinates {
x_sum += coordinate.x
y_sum += coordinate.y
match coordinate.z {
Some(value) => z_sum += value
None => z_complete = false
}
}
Some({
kind: PointKind,
weight: coordinates.length().to_double(),
x_sum,
y_sum,
z_sum,
z_complete,
})
}
///|
fn segment_length(
start : Coordinate,
end : Coordinate,
) -> (Double, Double?, Bool) {
let dx = end.x - start.x
let dy = end.y - start.y
match (start.z, end.z) {
(Some(start_z), Some(end_z)) => {
let dz = end_z - start_z
(
(dx * dx + dy * dy + dz * dz).sqrt(),
Some((start_z + end_z) / 2.0),
true,
)
}
_ => ((dx * dx + dy * dy).sqrt(), None, false)
}
}
///|
fn centroid_data_from_line(coordinates : Array[Coordinate]) -> CentroidData? {
if coordinates.length() == 0 {
return None
}
if coordinates.length() == 1 {
return centroid_data_from_points(coordinates)
}
let mut weight = 0.0
let mut x_sum = 0.0
let mut y_sum = 0.0
let mut z_sum = 0.0
let mut z_complete = true
let end_index = coordinates.length() - 1
for index in 0.. z_sum += value * length
None => z_complete = false
}
if !has_z {
z_complete = false
}
}
if weight == 0.0 {
return centroid_data_from_points(coordinates)
}
Some({ kind: LineKind, weight, x_sum, y_sum, z_sum, z_complete, })
}
///|
fn centroid_ring_area2(ring : Array[Coordinate]) -> Double {
let mut area = 0.0
let end_index = ring.length() - 1
for index in 0.. (Double, Double, Double, Double?, Bool)? {
if ring.length() < 4 {
return None
}
let area2 = centroid_ring_area2(ring)
if area2 == 0.0 || area2.is_nan() || area2.is_inf() {
return None
}
let mut x_sum = 0.0
let mut y_sum = 0.0
let mut z_sum = 0.0
let mut z_count = 0
let mut z_complete = true
let end_index = ring.length() - 1
for index in 0.. {
z_sum += value
z_count += 1
}
None => z_complete = false
}
}
let area = area2.abs() / 2.0
let centroid_x = x_sum / (3.0 * area2)
let centroid_y = y_sum / (3.0 * area2)
let centroid_z = if z_complete && z_count > 0 {
Some(z_sum / z_count.to_double())
} else {
None
}
Some((area, centroid_x, centroid_y, centroid_z, z_complete))
}
///|
fn centroid_data_from_polygon(
rings : Array[Array[Coordinate]],
) -> CentroidData? {
if rings.length() == 0 {
return None
}
let shell = match centroid_ring(rings[0]) {
Some(value) => value
None => return None
}
let (shell_area, shell_x, shell_y, shell_z, shell_z_complete) = shell
if shell_area == 0.0 {
return None
}
let mut weight = shell_area
let mut x_sum = shell_area * shell_x
let mut y_sum = shell_area * shell_y
let mut z_sum = match shell_z {
Some(value) => shell_area * value
None => 0.0
}
let mut z_complete = shell_z_complete
for index in 1.. value
None => continue
}
let (area, x, y, z, hole_z_complete) = hole
weight -= area
x_sum -= area * x
y_sum -= area * y
if z_complete && hole_z_complete {
match z {
Some(value) => z_sum -= area * value
None => z_complete = false
}
} else {
z_complete = false
}
}
if weight <= 0.0 {
return None
}
Some({ kind: AreaKind, weight, x_sum, y_sum, z_sum, z_complete, })
}
///|
fn centroid_data_from_polygons(
polygons : Array[Array[Array[Coordinate]]],
) -> CentroidData? {
let mut result : CentroidData? = None
for polygon in polygons {
result = merge_centroid_data(result, centroid_data_from_polygon(polygon))
}
result
}
///|
fn centroid_data(geometry : Geometry) -> CentroidData? {
match geometry {
Point(coordinate) => centroid_data_from_points([coordinate])
EmptyPoint => None
LineString(coordinates) => centroid_data_from_line(coordinates)
Polygon(rings) => centroid_data_from_polygon(rings)
MultiPoint(coordinates) => centroid_data_from_points(coordinates)
MultiLineString(lines) => {
let mut result : CentroidData? = None
for line in lines {
result = merge_centroid_data(result, centroid_data_from_line(line))
}
result
}
MultiPolygon(polygons) => centroid_data_from_polygons(polygons)
GeometryCollection(geometries) => {
let mut result : CentroidData? = None
for child in geometries {
result = merge_centroid_data(result, centroid_data(child))
}
result
}
}
}
///|
/// Computes a dimension-weighted centroid.
pub fn Geometry::centroid(self : Geometry) -> Coordinate? {
let data = match centroid_data(self) {
Some(value) => value
None => return None
}
if data.weight <= 0.0 {
return None
}
let z = if data.z_complete { Some(data.z_sum / data.weight) } else { None }
Some({ x: data.x_sum / data.weight, y: data.y_sum / data.weight, z, m: None, })
}