diff --git a/python/core/python/geoarrow/rust/core/_rust.pyi b/python/core/python/geoarrow/rust/core/_rust.pyi index a28bab866..9bea2ef50 100644 --- a/python/core/python/geoarrow/rust/core/_rust.pyi +++ b/python/core/python/geoarrow/rust/core/_rust.pyi @@ -1239,20 +1239,20 @@ def read_csv( batch_size: int = 65536, ) -> GeoTable: ... def read_flatgeobuf( - file: Union[str, Path, BinaryIO], batch_size: int = 65536 + file: Union[str, Path, BinaryIO], *, batch_size: int = 65536 ) -> GeoTable: ... async def read_flatgeobuf_async( url: str, *, batch_size: int = 65536, options: Dict[str, str] | None = None ) -> GeoTable: ... def read_geojson( - file: Union[str, Path, BinaryIO], batch_size: int = 65536 + file: Union[str, Path, BinaryIO], *, batch_size: int = 65536 ) -> GeoTable: ... def read_geojson_lines( - file: Union[str, Path, BinaryIO], batch_size: int = 65536 + file: Union[str, Path, BinaryIO], *, batch_size: int = 65536 ) -> GeoTable: ... def read_ipc(file: Union[str, Path, BinaryIO]) -> GeoTable: ... def read_ipc_stream(file: Union[str, Path, BinaryIO]) -> GeoTable: ... -def read_parquet(path: str, batch_size: int = 65536) -> GeoTable: ... +def read_parquet(path: str, *, batch_size: int = 65536) -> GeoTable: ... def read_postgis(connection_url: str, sql: str) -> Optional[GeoTable]: ... async def read_postgis_async(connection_url: str, sql: str) -> Optional[GeoTable]: ... def read_pyogrio( @@ -1284,6 +1284,9 @@ def write_flatgeobuf( def write_geojson( table: ArrowStreamExportable, file: Union[str, Path, BinaryIO] ) -> None: ... +def write_geojson_lines( + table: ArrowStreamExportable, file: Union[str, Path, BinaryIO] +) -> None: ... def write_ipc( table: ArrowStreamExportable, file: Union[str, Path, BinaryIO] ) -> None: ... diff --git a/python/core/src/io/geojson_lines.rs b/python/core/src/io/geojson_lines.rs index efa8621b6..7a69307d5 100644 --- a/python/core/src/io/geojson_lines.rs +++ b/python/core/src/io/geojson_lines.rs @@ -1,10 +1,11 @@ use crate::error::PyGeoArrowResult; -use crate::io::file::BinaryFileReader; +use crate::io::file::{BinaryFileReader, BinaryFileWriter}; use crate::table::GeoTable; use geoarrow::io::geojson_lines::read_geojson_lines as _read_geojson_lines; +use geoarrow::io::geojson_lines::write_geojson_lines as _write_geojson_lines; use pyo3::prelude::*; -/// Read a GeoJSON Lines file from a path on disk into a GeoTable. +/// Read a newline-delimited GeoJSON file from a path on disk into a GeoTable. /// /// This expects a GeoJSON Feature on each line of a text file, with a newline character separating /// each Feature. @@ -25,3 +26,25 @@ pub fn read_geojson_lines( let table = _read_geojson_lines(&mut reader, Some(batch_size))?; Ok(GeoTable(table)) } + +/// Write a GeoTable to a newline-delimited GeoJSON file on disk. +/// +/// Note that the GeoJSON specification mandates coordinates to be in the WGS84 (EPSG:4326) +/// coordinate system, but this function will not automatically reproject into WGS84 for you. +/// +/// Args: +/// table: the table to write. +/// file: the path to the file or a Python file object in binary write mode. +/// +/// Returns: +/// None +#[pyfunction] +pub fn write_geojson_lines( + py: Python, + mut table: GeoTable, + file: PyObject, +) -> PyGeoArrowResult<()> { + let writer = file.extract::(py)?; + _write_geojson_lines(&mut table.0, writer)?; + Ok(()) +} diff --git a/python/core/src/lib.rs b/python/core/src/lib.rs index a3a61bde0..96c730dfd 100644 --- a/python/core/src/lib.rs +++ b/python/core/src/lib.rs @@ -184,6 +184,10 @@ fn _rust(_py: Python, m: &PyModule) -> PyResult<()> { m )?)?; m.add_function(wrap_pyfunction!(crate::io::geojson::write_geojson, m)?)?; + m.add_function(wrap_pyfunction!( + crate::io::geojson_lines::write_geojson_lines, + m + )?)?; // Interop m.add_function(wrap_pyfunction!( diff --git a/python/docs/source/api/core/io.md b/python/docs/source/api/core/io.md index e46e5db02..9a128889f 100644 --- a/python/docs/source/api/core/io.md +++ b/python/docs/source/api/core/io.md @@ -21,6 +21,7 @@ Read and write to files on disk and databases like PostGIS. - write_csv - write_flatgeobuf - write_geojson + - write_geojson_lines - write_ipc - write_ipc_stream - write_parquet diff --git a/src/io/geojson_lines/mod.rs b/src/io/geojson_lines/mod.rs index 561c0b844..7edfdcf01 100644 --- a/src/io/geojson_lines/mod.rs +++ b/src/io/geojson_lines/mod.rs @@ -1,5 +1,7 @@ -//! Read from [newline-delimited GeoJSON](https://stevage.github.io/ndgeojson/) files. +//! Read from and write to [newline-delimited GeoJSON](https://stevage.github.io/ndgeojson/) files. mod reader; +mod writer; pub use reader::read_geojson_lines; +pub use writer::write_geojson_lines; diff --git a/src/io/geojson_lines/writer.rs b/src/io/geojson_lines/writer.rs new file mode 100644 index 000000000..ac6067538 --- /dev/null +++ b/src/io/geojson_lines/writer.rs @@ -0,0 +1,13 @@ +use geozero::geojson::GeoJsonLineWriter; +use geozero::GeozeroDatasource; +use std::io::Write; + +use crate::error::Result; +use crate::table::GeoTable; + +/// Write a table to newline-delimited GeoJSON +pub fn write_geojson_lines(table: &mut GeoTable, writer: W) -> Result<()> { + let mut geojson_writer = GeoJsonLineWriter::new(writer); + table.process(&mut geojson_writer)?; + Ok(()) +}