#![deny(missing_docs)]
mod crs;
pub mod error;
pub mod feature;
pub mod flatgeobuf;
pub mod geojson;
pub mod geometry;
pub mod geopackage;
#[cfg(feature = "geoparquet")]
pub mod geoparquet;
pub mod gml;
pub mod gpx;
pub mod kml;
#[cfg(feature = "kmz")]
pub mod kmz;
pub mod mapinfo;
pub mod memory_store;
#[cfg(feature = "osmpbf")]
pub mod osmpbf;
pub mod reproject;
pub mod shapefile;
pub mod topojson;
pub use error::{GeoError, Result};
pub use feature::{Crs, Feature, FieldDef, FieldType, FieldValue, Layer, Schema};
pub use geometry::{BBox, Coord, Geometry, GeometryType, Ring};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorFormat {
FlatGeobuf,
GeoJson,
TopoJson,
GeoPackage,
#[cfg(feature = "geoparquet")]
GeoParquet,
Gml,
Gpx,
Kml,
#[cfg(feature = "kmz")]
Kmz,
MapInfoMif,
#[cfg(feature = "osmpbf")]
OsmPbf,
Shapefile,
}
impl VectorFormat {
pub fn detect<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let file_name_lc = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_ascii_lowercase();
let ext_lc = path
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_ascii_lowercase();
if file_name_lc.ends_with(".osm.pbf") {
#[cfg(feature = "osmpbf")]
{
return Ok(Self::OsmPbf);
}
#[cfg(not(feature = "osmpbf"))]
{
return Err(GeoError::NotImplemented(
"OSM PBF support requires enabling the `osmpbf` feature".into(),
));
}
}
match ext_lc.as_str() {
"fgb" => return Ok(Self::FlatGeobuf),
"geojson" => return Ok(Self::GeoJson),
"topojson" => return Ok(Self::TopoJson),
"gpkg" => return Ok(Self::GeoPackage),
"gml" => return Ok(Self::Gml),
"gpx" => return Ok(Self::Gpx),
"kml" => return Ok(Self::Kml),
"mif" => return Ok(Self::MapInfoMif),
"shp" => return Ok(Self::Shapefile),
"parquet" => {
#[cfg(feature = "geoparquet")]
{
return Ok(Self::GeoParquet);
}
#[cfg(not(feature = "geoparquet"))]
{
return Err(GeoError::NotImplemented(
"GeoParquet support requires enabling the `geoparquet` feature".into(),
));
}
}
"kmz" => {
#[cfg(feature = "kmz")]
{
return Ok(Self::Kmz);
}
#[cfg(not(feature = "kmz"))]
{
return Err(GeoError::NotImplemented(
"KMZ support requires enabling the `kmz` feature".into(),
));
}
}
"json" => {
if let Some(kind) = sniff_json(path)? {
return Ok(kind);
}
return Ok(Self::GeoJson);
}
"xml" => {
if let Some(kind) = sniff_xml(path)? {
return Ok(kind);
}
}
_ => {}
}
if path.extension().is_none() {
let shp = path.with_extension("shp");
if shp.exists() {
return Ok(Self::Shapefile);
}
}
if path.is_file() {
let sig = read_signature(path, 16)?;
if sig.starts_with(&flatgeobuf::MAGIC) {
return Ok(Self::FlatGeobuf);
}
if sig.starts_with(b"SQLite format 3\0") {
return Ok(Self::GeoPackage);
}
if sig.starts_with(b"PAR1") {
#[cfg(feature = "geoparquet")]
{
return Ok(Self::GeoParquet);
}
}
if sig.starts_with(b"PK\x03\x04") {
#[cfg(feature = "kmz")]
{
return Ok(Self::Kmz);
}
}
if let Some(kind) = sniff_xml(path)? {
return Ok(kind);
}
}
Err(GeoError::UnknownFormat(path.display().to_string()))
}
pub fn read<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Layer> {
match self {
Self::FlatGeobuf => flatgeobuf::read(path),
Self::GeoJson => geojson::read(path),
Self::TopoJson => topojson::read(path),
Self::GeoPackage => geopackage::read(path),
#[cfg(feature = "geoparquet")]
Self::GeoParquet => geoparquet::read(path),
Self::Gml => gml::read(path),
Self::Gpx => gpx::read(path),
Self::Kml => kml::read(path),
#[cfg(feature = "kmz")]
Self::Kmz => kmz::read(path),
Self::MapInfoMif => mapinfo::read(path),
#[cfg(feature = "osmpbf")]
Self::OsmPbf => osmpbf::read(path),
Self::Shapefile => shapefile::read(path),
}
}
pub fn write<P: AsRef<std::path::Path>>(&self, layer: &Layer, path: P) -> Result<()> {
match self {
Self::FlatGeobuf => flatgeobuf::write(layer, path),
Self::GeoJson => geojson::write(layer, path),
Self::TopoJson => topojson::write(layer, path),
Self::GeoPackage => geopackage::write(layer, path),
#[cfg(feature = "geoparquet")]
Self::GeoParquet => geoparquet::write(layer, path),
Self::Gml => gml::write(layer, path),
Self::Gpx => gpx::write(layer, path),
Self::Kml => kml::write(layer, path),
#[cfg(feature = "kmz")]
Self::Kmz => kmz::write(layer, path),
Self::MapInfoMif => mapinfo::write(layer, path),
#[cfg(feature = "osmpbf")]
Self::OsmPbf => Err(GeoError::NotImplemented(
"OSM PBF writer is not implemented".into(),
)),
Self::Shapefile => shapefile::write(layer, path),
}
}
}
pub fn read<P: AsRef<std::path::Path>>(path: P) -> Result<Layer> {
let fmt = VectorFormat::detect(path.as_ref())?;
fmt.read(path)
}
pub fn write<P: AsRef<std::path::Path>>(
layer: &Layer,
path: P,
format: VectorFormat,
) -> Result<()> {
format.write(layer, path)
}
pub use geopackage::GpkgStreamWriter;
pub use geojson::GeoJsonStreamWriter;
pub use flatgeobuf::FgbStreamWriter;
pub enum VectorStreamWriter {
GeoPackage(GpkgStreamWriter),
GeoJson(GeoJsonStreamWriter),
FlatGeobuf(FgbStreamWriter),
Layer(Layer, String),
}
impl VectorStreamWriter {
pub fn open(path: &str, schema: &Layer) -> Result<Self> {
if memory_store::vector_is_memory_path(path) {
return Ok(Self::Layer(schema_layer_empty(schema), path.to_string()));
}
match VectorFormat::detect(path).ok() {
Some(VectorFormat::GeoPackage) => {
Ok(Self::GeoPackage(GpkgStreamWriter::create(path, schema)?))
}
Some(VectorFormat::GeoJson) => {
Ok(Self::GeoJson(GeoJsonStreamWriter::create(path, schema)?))
}
Some(VectorFormat::FlatGeobuf) => {
Ok(Self::FlatGeobuf(FgbStreamWriter::create(path, schema)?))
}
_ => Ok(Self::Layer(schema_layer_empty(schema), path.to_string())),
}
}
pub fn push_feature(
&mut self,
geom: Option<&Geometry>,
attrs: &[FieldValue],
) -> Result<()> {
match self {
Self::GeoPackage(w) => w.push_feature(geom, attrs),
Self::GeoJson(w) => w.push_feature(geom, attrs),
Self::FlatGeobuf(w) => w.push_feature(geom, attrs),
Self::Layer(layer, _) => {
layer.push(Feature {
fid: layer.features.len() as u64,
geometry: geom.cloned(),
attributes: attrs.to_vec(),
});
Ok(())
}
}
}
pub fn finish(self) -> Result<String> {
match self {
Self::GeoPackage(w) => { let p = w.path.clone(); w.finish()?; Ok(p) }
Self::GeoJson(w) => { let p = w.path.clone(); w.finish()?; Ok(p) }
Self::FlatGeobuf(w) => { let p = w.path.clone(); w.finish()?; Ok(p) }
Self::Layer(layer, path) => {
if memory_store::vector_is_memory_path(&path) {
let id = memory_store::put_vector(layer);
Ok(memory_store::make_vector_memory_path(&id))
} else {
let fmt = VectorFormat::detect(&path).map_err(|_| {
GeoError::UnknownFormat(path.clone())
})?;
fmt.write(&layer, &path)?;
Ok(path)
}
}
}
}
}
fn schema_layer_empty(schema: &Layer) -> Layer {
let mut layer = Layer::new(schema.name.clone());
layer.geom_type = schema.geom_type;
layer.crs = schema.crs.clone();
for fd in schema.schema.fields() {
layer.add_field(fd.clone());
}
layer
}
fn read_signature(path: &std::path::Path, n: usize) -> Result<Vec<u8>> {
use std::io::Read;
let mut f = std::fs::File::open(path)?;
let mut buf = vec![0u8; n];
let read_n = f.read(&mut buf)?;
buf.truncate(read_n);
Ok(buf)
}
fn sniff_xml(path: &std::path::Path) -> Result<Option<VectorFormat>> {
use std::io::Read;
let mut f = match std::fs::File::open(path) {
Ok(f) => f,
Err(_) => return Ok(None),
};
let mut buf = vec![0u8; 4096];
let n = f.read(&mut buf)?;
buf.truncate(n);
let txt = String::from_utf8_lossy(&buf).to_ascii_lowercase();
if txt.contains("<kml") {
return Ok(Some(VectorFormat::Kml));
}
if txt.contains("<gpx") {
return Ok(Some(VectorFormat::Gpx));
}
if txt.contains("<gml") || txt.contains("opengis.net/gml") {
return Ok(Some(VectorFormat::Gml));
}
Ok(None)
}
fn sniff_json(path: &std::path::Path) -> Result<Option<VectorFormat>> {
use std::io::Read;
let mut f = match std::fs::File::open(path) {
Ok(f) => f,
Err(_) => return Ok(None),
};
let mut buf = vec![0u8; 4096];
let n = f.read(&mut buf)?;
buf.truncate(n);
let txt = String::from_utf8_lossy(&buf).to_ascii_lowercase();
if txt.contains("\"type\"") && txt.contains("\"topology\"") {
return Ok(Some(VectorFormat::TopoJson));
}
if txt.contains("\"type\"")
&& (txt.contains("\"featurecollection\"") || txt.contains("\"feature\""))
{
return Ok(Some(VectorFormat::GeoJson));
}
Ok(None)
}