///|
priv enum DimensionSummary {
NoDimension
Uniform(CoordinateDimension)
Mixed
}
///|
fn merge_dimension_summary(
left : DimensionSummary,
right : DimensionSummary,
) -> DimensionSummary {
match (left, right) {
(NoDimension, value) => value
(value, NoDimension) => value
(Mixed, _) => Mixed
(_, Mixed) => Mixed
(Uniform(a), Uniform(b)) => if a == b { Uniform(a) } else { Mixed }
}
}
///|
fn coordinate_dimension_summary(
coordinates : Array[Coordinate],
) -> DimensionSummary {
let mut result = NoDimension
for coordinate in coordinates {
result = merge_dimension_summary(result, Uniform(coordinate.dimension()))
}
result
}
///|
fn ring_dimension_summary(rings : Array[Array[Coordinate]]) -> DimensionSummary {
let mut result = NoDimension
for ring in rings {
result = merge_dimension_summary(result, coordinate_dimension_summary(ring))
}
result
}
///|
fn polygon_dimension_summary(
polygons : Array[Array[Array[Coordinate]]],
) -> DimensionSummary {
let mut result = NoDimension
for polygon in polygons {
result = merge_dimension_summary(result, ring_dimension_summary(polygon))
}
result
}
///|
fn collection_dimension_summary(
geometries : Array[Geometry],
) -> DimensionSummary {
let mut result = NoDimension
for geometry in geometries {
result = merge_dimension_summary(
result,
geometry_dimension_summary(geometry),
)
}
result
}
///|
fn geometry_dimension_summary(geometry : Geometry) -> DimensionSummary {
match geometry {
Point(coordinate) => Uniform(coordinate.dimension())
EmptyPoint => NoDimension
LineString(coordinates) => coordinate_dimension_summary(coordinates)
Polygon(rings) => ring_dimension_summary(rings)
MultiPoint(coordinates) => coordinate_dimension_summary(coordinates)
MultiLineString(lines) => ring_dimension_summary(lines)
MultiPolygon(polygons) => polygon_dimension_summary(polygons)
GeometryCollection(geometries) => collection_dimension_summary(geometries)
}
}
///|
fn summarize_uniform_dimension(
summary : DimensionSummary,
) -> Result[CoordinateDimension, GeoError] {
match summary {
Uniform(dimension) => Ok(dimension)
Mixed => Err(InvalidCoordinate("inconsistent coordinate dimensions"))
NoDimension => Err(InvalidCoordinate("geometry has no coordinates"))
}
}
///|
fn write_dimension_suffix(
builder : StringBuilder,
dimension : CoordinateDimension,
) -> Unit {
match dimension {
Xy => ()
Xyz => builder.write_string(" Z")
Xym => builder.write_string(" M")
Xyzm => builder.write_string(" ZM")
}
}
///|
fn validate_coordinate(
coordinate : Coordinate,
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
if coordinate.dimension() != dimension {
return Err(
InvalidCoordinate("coordinate dimension does not match geometry"),
)
}
if coordinate.x.is_nan() || coordinate.x.is_inf() {
return Err(InvalidCoordinate("non-finite x ordinate"))
}
if coordinate.y.is_nan() || coordinate.y.is_inf() {
return Err(InvalidCoordinate("non-finite y ordinate"))
}
match coordinate.z {
Some(value) =>
if value.is_nan() || value.is_inf() {
return Err(InvalidCoordinate("non-finite z ordinate"))
}
None => ()
}
match coordinate.m {
Some(value) =>
if value.is_nan() || value.is_inf() {
return Err(InvalidCoordinate("non-finite m ordinate"))
}
None => ()
}
Ok(())
}
///|
fn write_coordinate_tuple(
builder : StringBuilder,
coordinate : Coordinate,
dimension : CoordinateDimension,
) -> Unit {
builder.write_string(coordinate.x.to_string())
builder.write_string(" ")
builder.write_string(coordinate.y.to_string())
match dimension {
Xyz | Xyzm =>
match coordinate.z {
Some(value) => {
builder.write_string(" ")
builder.write_string(value.to_string())
}
None => ()
}
_ => ()
}
match dimension {
Xym | Xyzm =>
match coordinate.m {
Some(value) => {
builder.write_string(" ")
builder.write_string(value.to_string())
}
None => ()
}
_ => ()
}
}
///|
fn write_coordinate_list(
builder : StringBuilder,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
let mut first = true
for coordinate in coordinates {
match validate_coordinate(coordinate, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
if !first {
builder.write_string(", ")
}
write_coordinate_tuple(builder, coordinate, dimension)
first = false
}
Ok(())
}
///|
fn write_sequence(
builder : StringBuilder,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
empty_message : String,
) -> Result[Unit, GeoError] {
if coordinates.length() == 0 {
return Err(InvalidCoordinate(empty_message))
}
builder.write_string("(")
match write_coordinate_list(builder, coordinates, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(")")
Ok(())
}
///|
fn write_geometry_header(
builder : StringBuilder,
name : String,
dimension : CoordinateDimension,
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
match inherited_dimension {
Some(parent) =>
if parent != dimension {
Err(InvalidCoordinate("geometry dimension does not match collection"))
} else {
builder.write_string(name)
Ok(())
}
None => {
builder.write_string(name)
write_dimension_suffix(builder, dimension)
Ok(())
}
}
}
///|
fn write_empty_geometry(builder : StringBuilder, name : String) -> Unit {
builder.write_string(name)
builder.write_string(" EMPTY")
}
///|
fn write_point_geometry(
builder : StringBuilder,
coordinate : Coordinate,
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
let dimension = coordinate.dimension()
match
write_geometry_header(builder, "POINT", dimension, inherited_dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_coordinate(coordinate, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" (")
write_coordinate_tuple(builder, coordinate, dimension)
builder.write_string(")")
Ok(())
}
///|
fn write_line_string_geometry(
builder : StringBuilder,
coordinates : Array[Coordinate],
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
if coordinates.length() == 0 {
write_empty_geometry(builder, "LINESTRING")
return Ok(())
}
let dimension = match
summarize_uniform_dimension(coordinate_dimension_summary(coordinates)) {
Ok(value) => value
Err(err) => return Err(err)
}
match
write_geometry_header(builder, "LINESTRING", dimension, inherited_dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" ")
write_sequence(builder, coordinates, dimension, "empty line string")
}
///|
fn write_polygon_rings(
builder : StringBuilder,
rings : Array[Array[Coordinate]],
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
let mut first = true
for ring in rings {
if !first {
builder.write_string(", ")
}
match write_sequence(builder, ring, dimension, "empty polygon ring") {
Ok(_) => ()
Err(err) => return Err(err)
}
first = false
}
Ok(())
}
///|
fn write_polygon_geometry(
builder : StringBuilder,
rings : Array[Array[Coordinate]],
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
if rings.length() == 0 {
write_empty_geometry(builder, "POLYGON")
return Ok(())
}
for ring in rings {
if ring.length() == 0 {
return Err(InvalidCoordinate("empty polygon ring"))
}
}
let dimension = match
summarize_uniform_dimension(ring_dimension_summary(rings)) {
Ok(value) => value
Err(err) => return Err(err)
}
match
write_geometry_header(builder, "POLYGON", dimension, inherited_dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" (")
match write_polygon_rings(builder, rings, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(")")
Ok(())
}
///|
fn write_multi_point_coordinates(
builder : StringBuilder,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
wrapped : Bool,
) -> Result[Unit, GeoError] {
let mut first = true
for coordinate in coordinates {
match validate_coordinate(coordinate, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
if !first {
builder.write_string(", ")
}
if wrapped {
builder.write_string("(")
}
write_coordinate_tuple(builder, coordinate, dimension)
if wrapped {
builder.write_string(")")
}
first = false
}
Ok(())
}
///|
fn write_multi_point_geometry(
builder : StringBuilder,
coordinates : Array[Coordinate],
inherited_dimension : CoordinateDimension?,
dialect : WktDialect,
) -> Result[Unit, GeoError] {
if coordinates.length() == 0 {
write_empty_geometry(builder, "MULTIPOINT")
return Ok(())
}
let dimension = match
summarize_uniform_dimension(coordinate_dimension_summary(coordinates)) {
Ok(value) => value
Err(err) => return Err(err)
}
match
write_geometry_header(builder, "MULTIPOINT", dimension, inherited_dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" (")
match
write_multi_point_coordinates(
builder,
coordinates,
dimension,
dialect == Wkt2,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(")")
Ok(())
}
///|
fn write_multi_line_string_geometry(
builder : StringBuilder,
lines : Array[Array[Coordinate]],
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
if lines.length() == 0 {
write_empty_geometry(builder, "MULTILINESTRING")
return Ok(())
}
for line in lines {
if line.length() == 0 {
return Err(InvalidCoordinate("empty line string component"))
}
}
let dimension = match
summarize_uniform_dimension(ring_dimension_summary(lines)) {
Ok(value) => value
Err(err) => return Err(err)
}
match
write_geometry_header(
builder, "MULTILINESTRING", dimension, inherited_dimension,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" (")
let mut first = true
for line in lines {
if !first {
builder.write_string(", ")
}
match
write_sequence(builder, line, dimension, "empty line string component") {
Ok(_) => ()
Err(err) => return Err(err)
}
first = false
}
builder.write_string(")")
Ok(())
}
///|
fn write_multi_polygon_geometry(
builder : StringBuilder,
polygons : Array[Array[Array[Coordinate]]],
inherited_dimension : CoordinateDimension?,
) -> Result[Unit, GeoError] {
if polygons.length() == 0 {
write_empty_geometry(builder, "MULTIPOLYGON")
return Ok(())
}
for polygon in polygons {
if polygon.length() == 0 {
return Err(InvalidCoordinate("empty polygon component"))
}
for ring in polygon {
if ring.length() == 0 {
return Err(InvalidCoordinate("empty polygon ring"))
}
}
}
let dimension = match
summarize_uniform_dimension(polygon_dimension_summary(polygons)) {
Ok(value) => value
Err(err) => return Err(err)
}
match
write_geometry_header(
builder, "MULTIPOLYGON", dimension, inherited_dimension,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(" (")
let mut first = true
for polygon in polygons {
if !first {
builder.write_string(", ")
}
builder.write_string("(")
match write_polygon_rings(builder, polygon, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
builder.write_string(")")
first = false
}
builder.write_string(")")
Ok(())
}
///|
fn write_geometry_collection_geometry(
builder : StringBuilder,
geometries : Array[Geometry],
inherited_dimension : CoordinateDimension?,
dialect : WktDialect,
) -> Result[Unit, GeoError] {
if geometries.length() == 0 {
write_empty_geometry(builder, "GEOMETRYCOLLECTION")
return Ok(())
}
let summary = collection_dimension_summary(geometries)
builder.write_string("GEOMETRYCOLLECTION")
let child_dimension : CoordinateDimension? = match inherited_dimension {
Some(parent) => {
match summary {
Uniform(dimension) =>
if dimension != parent {
return Err(
InvalidCoordinate("geometry dimension does not match collection"),
)
}
Mixed =>
return Err(
InvalidCoordinate("mixed dimensions in dimensioned collection"),
)
NoDimension => ()
}
Some(parent)
}
None =>
match summary {
Uniform(dimension) => {
write_dimension_suffix(builder, dimension)
Some(dimension)
}
Mixed | NoDimension => None
}
}
builder.write_string(" (")
let mut first = true
for geometry in geometries {
if !first {
builder.write_string(", ")
}
match write_geometry(builder, geometry, child_dimension, dialect) {
Ok(_) => ()
Err(err) => return Err(err)
}
first = false
}
builder.write_string(")")
Ok(())
}
///|
fn write_geometry(
builder : StringBuilder,
geometry : Geometry,
inherited_dimension : CoordinateDimension?,
dialect : WktDialect,
) -> Result[Unit, GeoError] {
match geometry {
Point(coordinate) =>
write_point_geometry(builder, coordinate, inherited_dimension)
EmptyPoint => {
write_empty_geometry(builder, "POINT")
Ok(())
}
LineString(coordinates) =>
write_line_string_geometry(builder, coordinates, inherited_dimension)
Polygon(rings) =>
write_polygon_geometry(builder, rings, inherited_dimension)
MultiPoint(coordinates) =>
write_multi_point_geometry(
builder, coordinates, inherited_dimension, dialect,
)
MultiLineString(lines) =>
write_multi_line_string_geometry(builder, lines, inherited_dimension)
MultiPolygon(polygons) =>
write_multi_polygon_geometry(builder, polygons, inherited_dimension)
GeometryCollection(geometries) =>
write_geometry_collection_geometry(
builder, geometries, inherited_dimension, dialect,
)
}
}
///|
fn format_wkt_dialect(
geometry : Geometry,
dialect : WktDialect,
) -> Result[String, GeoError] {
let builder = StringBuilder()
match write_geometry(builder, geometry, None, dialect) {
Ok(_) => Ok(builder.to_string())
Err(err) => Err(err)
}
}
///|
/// Serializes a geometry as canonical WKT 1.
pub fn format_wkt(geometry : Geometry) -> Result[String, GeoError] {
format_wkt_dialect(geometry, Wkt1)
}
///|
/// Serializes a geometry as canonical WKT 2.
pub fn format_wkt2(geometry : Geometry) -> Result[String, GeoError] {
format_wkt_dialect(geometry, Wkt2)
}
///|
/// Serializes a geometry with an optional EWKT SRID prefix.
pub fn format_ewkt(value : SpatialGeometry) -> Result[String, GeoError] {
let text = match format_wkt(value.geometry) {
Ok(value) => value
Err(err) => return Err(err)
}
match value.srid {
Some(srid) =>
if srid < 0 {
Err(InvalidSrid(srid))
} else {
Ok("SRID=" + srid.to_string() + ";" + text)
}
None => Ok(text)
}
}