use super::*;
pub(crate) fn parse_geojson(name: &str, value: &DataValue) -> Result<MapData, GeoError> {
let kind: String = value
.get(GEO_FIELD_TYPE)
.and_then(|kind: &DataValue| kind.as_text())
.unwrap_or_default()
.to_string();
match kind.as_str() {
GEO_KIND_FEATURE_COLLECTION => {
let features: &[DataValue] = value
.get(GEO_FIELD_FEATURES)
.and_then(|features: &DataValue| features.as_list())
.unwrap_or(&[]);
let mut regions: Vec<MapRegion> = Vec::with_capacity(features.len());
for feature in features {
regions.push(parse_feature(feature)?);
}
Ok(MapData::from_regions(name, regions))
}
GEO_KIND_FEATURE => {
let region: MapRegion = parse_feature(value)?;
Ok(MapData::from_regions(name, vec![region]))
}
"" => Err(GeoError::MissingType),
other => Err(GeoError::UnsupportedType(other.to_string())),
}
}
pub(crate) fn parse_feature(value: &DataValue) -> Result<MapRegion, GeoError> {
let name: String = feature_name(value);
let geometry: &DataValue = value
.get(GEO_FIELD_GEOMETRY)
.ok_or(GeoError::MissingGeometry)?;
let polygons: Vec<RegionPolygon> = parse_geometry(geometry)?;
Ok(MapRegion { name, polygons })
}
pub(crate) fn feature_name(value: &DataValue) -> String {
if let Some(properties) = value.get(GEO_FIELD_PROPERTIES)
&& let Some(name) = properties
.get(GEO_FIELD_NAME)
.and_then(|name: &DataValue| name.as_text())
&& !name.is_empty()
{
return name.to_string();
}
for field in [GEO_FIELD_NAME, GEO_FIELD_NAME_UPPER, GEO_FIELD_ID] {
if let Some(name) = value.get(field).and_then(|name: &DataValue| name.as_text())
&& !name.is_empty()
{
return name.to_string();
}
}
String::new()
}
pub(crate) fn parse_geometry(value: &DataValue) -> Result<Vec<RegionPolygon>, GeoError> {
let kind: String = value
.get(GEO_FIELD_TYPE)
.and_then(|kind: &DataValue| kind.as_text())
.unwrap_or_default()
.to_string();
let coordinates: &DataValue = value
.get(GEO_FIELD_COORDINATES)
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_NO_COORDINATES.to_string()))?;
match kind.as_str() {
GEO_KIND_POLYGON => Ok(vec![parse_polygon(coordinates)?]),
GEO_KIND_MULTI_POLYGON => {
let list: &[DataValue] = coordinates.as_list().ok_or_else(|| {
GeoError::MalformedGeometry(GEO_ERROR_MULTI_POLYGON_NOT_A_LIST.to_string())
})?;
let mut polygons: Vec<RegionPolygon> = Vec::with_capacity(list.len());
for item in list {
polygons.push(parse_polygon(item)?);
}
Ok(polygons)
}
GEO_KIND_POINT
| GEO_KIND_MULTI_POINT
| GEO_KIND_LINE_STRING
| GEO_KIND_MULTI_LINE_STRING => Err(GeoError::UnsupportedType(kind)),
"" => Err(GeoError::MissingType),
other => Err(GeoError::UnsupportedType(other.to_string())),
}
}
pub(crate) fn parse_polygon(value: &DataValue) -> Result<RegionPolygon, GeoError> {
let rings: &[DataValue] = value
.as_list()
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_POLYGON_NOT_A_LIST.to_string()))?;
let mut outer: Vec<GeoPoint> = Vec::new();
let mut holes: Vec<Vec<GeoPoint>> = Vec::new();
for (index, ring) in rings.iter().enumerate() {
let points: Vec<GeoPoint> = parse_ring(ring)?;
if points.len() < 3 {
continue;
}
if index == 0 {
outer = points;
} else {
holes.push(points);
}
}
if outer.is_empty() {
return Err(GeoError::MalformedGeometry(
GEO_ERROR_NO_OUTER_RING.to_string(),
));
}
Ok(RegionPolygon { outer, holes })
}
pub(crate) fn parse_ring(value: &DataValue) -> Result<Vec<GeoPoint>, GeoError> {
let list: &[DataValue] = value
.as_list()
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_RING_NOT_A_LIST.to_string()))?;
let mut points: Vec<GeoPoint> = Vec::with_capacity(list.len());
for item in list {
let pair: &[DataValue] = item
.as_list()
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_POINT_NOT_A_PAIR.to_string()))?;
let lng: f64 = pair
.first()
.and_then(|value: &DataValue| value.to_number())
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_POINT_NOT_A_PAIR.to_string()))?;
let lat: f64 = pair
.get(1)
.and_then(|value: &DataValue| value.to_number())
.ok_or_else(|| GeoError::MalformedGeometry(GEO_ERROR_POINT_NOT_A_PAIR.to_string()))?;
points.push(GeoPoint { lng, lat });
}
Ok(points)
}
pub(crate) fn ring_center(ring: &[GeoPoint]) -> Option<GeoPoint> {
if ring.is_empty() {
return None;
}
let mut area: f64 = 0.0;
let mut lng_sum: f64 = 0.0;
let mut lat_sum: f64 = 0.0;
for index in 0..ring.len() {
let current: GeoPoint = ring[index];
let next: GeoPoint = ring[(index + 1) % ring.len()];
let cross: f64 = current.lng * next.lat - next.lng * current.lat;
area += cross;
lng_sum += (current.lng + next.lng) * cross;
lat_sum += (current.lat + next.lat) * cross;
}
if area.abs() > f64::EPSILON {
return Some(GeoPoint {
lng: lng_sum / (3.0 * area),
lat: lat_sum / (3.0 * area),
});
}
let count: f64 = ring.len() as f64;
Some(GeoPoint {
lng: ring.iter().map(|point: &GeoPoint| point.lng).sum::<f64>() / count,
lat: ring.iter().map(|point: &GeoPoint| point.lat).sum::<f64>() / count,
})
}
pub(crate) fn fit_aspect(rect: BBox, aspect: f64) -> BBox {
let width: f64 = rect.width();
let height: f64 = rect.height();
if aspect <= 0.0 || width <= 0.0 || height <= 0.0 {
return rect;
}
let target_width: f64 = width * DEFAULT_MAP_FILL;
let target_height: f64 = height * DEFAULT_MAP_FILL;
let (map_width, map_height) = if aspect >= target_width / target_height {
(target_width, target_width / aspect)
} else {
(target_height * aspect, target_height)
};
BBox::from_corners(
rect.min_x + (width - map_width) * 0.5,
rect.min_y + (height - map_height) * 0.5,
rect.min_x + (width + map_width) * 0.5,
rect.min_y + (height + map_height) * 0.5,
)
}
pub(crate) fn region_path(region: &MapRegion, transform: &Transform2D) -> Path {
let mut builder: PathBuilder = Path::builder().fill_rule(FillRule::EvenOdd);
for polygon in ®ion.polygons {
for ring in once(&polygon.outer).chain(polygon.holes.iter()) {
if ring.len() < 3 {
continue;
}
let (x, y) = transform.apply(ring[0].lng, ring[0].lat);
builder = builder.move_to(x as f32, y as f32);
for point in &ring[1..] {
let (x, y) = transform.apply(point.lng, point.lat);
builder = builder.line_to(x as f32, y as f32);
}
builder = builder.close();
}
}
builder.build()
}
pub(crate) fn region_center(region: &MapRegion, transform: &Transform2D) -> Point {
let Some(center) = region.center() else {
return Point::new(0.0, 0.0);
};
let (x, y) = transform.apply(center.lng, center.lat);
Point::new(x as f32, y as f32)
}