use std::ffi::c_char;
use geopackage::core::tiles::{TileCoord, TileMatrixSet, ZoomLadder};
use geopackage::{BoundingBox, TilePyramidBuilder};
use crate::error::{Status, gpkg_error_t, set_error, set_library_error};
use crate::handle::{Container, CursorScan, TileCursorHandle, TilesHandle};
use crate::util::{borrow_str, out_string};
#[expect(
non_camel_case_types,
reason = "the C name is the type's name; cbindgen emits it verbatim"
)]
pub type gpkg_tiles_t = TilesHandle;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_names_count(
gpkg: *const Container,
out: *mut usize,
error: *mut gpkg_error_t,
) -> Status {
if gpkg.is_null() || out.is_null() {
unsafe { set_error(error, Status::BadArgument, "gpkg handle or out is NULL") };
return Status::BadArgument;
}
match unsafe { (*gpkg).gpkg().tile_pyramids() } {
Ok(pyramids) => {
unsafe { *out = pyramids.len() };
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_name_at(
gpkg: *const Container,
index: usize,
error: *mut gpkg_error_t,
) -> *mut c_char {
if gpkg.is_null() {
unsafe { set_error(error, Status::BadArgument, "gpkg handle is NULL") };
return std::ptr::null_mut();
}
let pyramids = match unsafe { (*gpkg).gpkg().tile_pyramids() } {
Ok(pyramids) => pyramids,
Err(err) => {
unsafe { set_library_error(error, &err) };
return std::ptr::null_mut();
}
};
let Some(pyramid) = pyramids.get(index) else {
unsafe { set_error(error, Status::NotFound, "pyramid index out of range") };
return std::ptr::null_mut();
};
let name = pyramid.table_name().to_owned();
unsafe { out_string(&name, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_open(
gpkg: *const Container,
name: *const c_char,
error: *mut gpkg_error_t,
) -> *mut gpkg_tiles_t {
if gpkg.is_null() {
unsafe { set_error(error, Status::BadArgument, "gpkg handle is NULL") };
return std::ptr::null_mut();
}
let Some(name) = (unsafe { borrow_str(name, error, "pyramid name") }) else {
return std::ptr::null_mut();
};
match unsafe { (*gpkg).tiles(name) } {
Ok(handle) => Box::into_raw(Box::new(handle)),
Err(err) => {
unsafe { set_library_error(error, &err) };
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_free(tiles: *mut gpkg_tiles_t) {
if tiles.is_null() {
return;
}
drop(unsafe { Box::from_raw(tiles) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_name(
tiles: *const gpkg_tiles_t,
error: *mut gpkg_error_t,
) -> *mut c_char {
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_name") }) else {
return std::ptr::null_mut();
};
let name = handle.pyramid().table_name().to_owned();
unsafe { out_string(&name, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_count(
tiles: *const gpkg_tiles_t,
out: *mut i64,
error: *mut gpkg_error_t,
) -> Status {
if out.is_null() {
unsafe { set_error(error, Status::BadArgument, "out is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_count") }) else {
return Status::BadArgument;
};
match handle.pyramid().tile_count() {
Ok(count) => {
unsafe { *out = count };
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_count_at(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
out: *mut i64,
error: *mut gpkg_error_t,
) -> Status {
if out.is_null() {
unsafe { set_error(error, Status::BadArgument, "out is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_count_at") }) else {
return Status::BadArgument;
};
match handle.pyramid().tile_count_at(zoom_level) {
Ok(count) => {
unsafe { *out = count };
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_zoom_level_count(
tiles: *const gpkg_tiles_t,
out: *mut usize,
error: *mut gpkg_error_t,
) -> Status {
if out.is_null() {
unsafe { set_error(error, Status::BadArgument, "out is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_zoom_level_count") }) else {
return Status::BadArgument;
};
let count = handle.pyramid().matrices().len();
unsafe { *out = count };
Status::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_matrix_at(
tiles: *const gpkg_tiles_t,
index: usize,
zoom_level: *mut i64,
matrix_width: *mut i64,
matrix_height: *mut i64,
tile_width: *mut i64,
tile_height: *mut i64,
error: *mut gpkg_error_t,
) -> Status {
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_matrix_at") }) else {
return Status::BadArgument;
};
let Some(matrix) = handle.pyramid().matrices().get(index) else {
unsafe { set_error(error, Status::NotFound, "zoom level index out of range") };
return Status::NotFound;
};
for (slot, value) in [
(zoom_level, matrix.zoom_level),
(matrix_width, matrix.matrix_width),
(matrix_height, matrix.matrix_height),
(tile_width, matrix.tile_width),
(tile_height, matrix.tile_height),
] {
if !slot.is_null() {
unsafe { *slot = value };
}
}
Status::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_extent(
tiles: *const gpkg_tiles_t,
min_x: *mut f64,
min_y: *mut f64,
max_x: *mut f64,
max_y: *mut f64,
srs_id: *mut i32,
error: *mut gpkg_error_t,
) -> Status {
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_extent") }) else {
return Status::BadArgument;
};
let set = handle.pyramid().matrix_set();
for (slot, value) in [
(min_x, set.min_x),
(min_y, set.min_y),
(max_x, set.max_x),
(max_y, set.max_y),
] {
if !slot.is_null() {
unsafe { *slot = value };
}
}
if !srs_id.is_null() {
unsafe { *srs_id = set.srs_id };
}
Status::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_has(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
column: i64,
row: i64,
out: *mut bool,
error: *mut gpkg_error_t,
) -> Status {
if out.is_null() {
unsafe { set_error(error, Status::BadArgument, "out is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_has") }) else {
return Status::BadArgument;
};
match handle
.pyramid()
.has_tile(TileCoord::new(zoom_level, column, row))
{
Ok(has) => {
unsafe { *out = has };
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_get(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
column: i64,
row: i64,
out_data: *mut *mut u8,
out_len: *mut usize,
error: *mut gpkg_error_t,
) -> Status {
if out_data.is_null() || out_len.is_null() {
unsafe { set_error(error, Status::BadArgument, "out_data or out_len is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_get") }) else {
return Status::BadArgument;
};
let found = match handle
.pyramid()
.get_tile(TileCoord::new(zoom_level, column, row))
{
Ok(found) => found,
Err(err) => {
unsafe { set_library_error(error, &err) };
return Status::from(&err);
}
};
let Some(bytes) = found else {
unsafe { *out_data = std::ptr::null_mut() };
unsafe { *out_len = 0 };
return Status::NotFound;
};
let boxed: Box<[u8]> = bytes.into_boxed_slice();
let len = boxed.len();
let raw = Box::into_raw(boxed).cast::<u8>();
unsafe { *out_data = raw };
unsafe { *out_len = len };
Status::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_put(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
column: i64,
row: i64,
data: *const u8,
len: usize,
error: *mut gpkg_error_t,
) -> Status {
if data.is_null() {
unsafe { set_error(error, Status::BadArgument, "data is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_put") }) else {
return Status::BadArgument;
};
let payload = unsafe { std::slice::from_raw_parts(data, len) };
let result = handle
.pyramid()
.put_tile(TileCoord::new(zoom_level, column, row), payload);
unsafe { report(result, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_delete(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
column: i64,
row: i64,
out_deleted: *mut bool,
error: *mut gpkg_error_t,
) -> Status {
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_delete") }) else {
return Status::BadArgument;
};
match handle
.pyramid()
.delete_tile(TileCoord::new(zoom_level, column, row))
{
Ok(deleted) => {
if !out_deleted.is_null() {
unsafe { *out_deleted = deleted };
}
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_bytes_free(data: *mut u8, len: usize) {
if data.is_null() {
return;
}
drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(data, len)) });
}
unsafe fn checked<'a>(
tiles: *const gpkg_tiles_t,
error: *mut gpkg_error_t,
what: &str,
) -> Option<&'a TilesHandle> {
if tiles.is_null() {
unsafe {
set_error(
error,
Status::BadArgument,
&format!("{what}: tiles handle is NULL"),
);
}
return None;
}
Some(unsafe { &*tiles })
}
unsafe fn report(result: geopackage::Result<()>, error: *mut gpkg_error_t) -> Status {
match result {
Ok(()) => Status::Ok,
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_get_into(
tiles: *const gpkg_tiles_t,
zoom_level: i64,
column: i64,
row: i64,
buffer: *mut u8,
capacity: usize,
out_len: *mut usize,
error: *mut gpkg_error_t,
) -> Status {
if out_len.is_null() || (buffer.is_null() && capacity != 0) {
unsafe { set_error(error, Status::BadArgument, "out_len or buffer is NULL") };
return Status::BadArgument;
}
let Some(handle) = (unsafe { checked(tiles, error, "gpkg_tiles_get_into") }) else {
return Status::BadArgument;
};
let mut scratch = Vec::new();
let found = match handle
.pyramid()
.get_tile_into(TileCoord::new(zoom_level, column, row), &mut scratch)
{
Ok(found) => found,
Err(err) => {
unsafe { set_library_error(error, &err) };
return Status::from(&err);
}
};
if !found {
unsafe { *out_len = 0 };
return Status::NotFound;
}
let len = scratch.len();
unsafe { *out_len = len };
if len > capacity {
unsafe {
set_error(
error,
Status::InvalidArgument,
"buffer too small; out_len contains the length required",
);
}
return Status::InvalidArgument;
}
unsafe { std::ptr::copy_nonoverlapping(scratch.as_ptr(), buffer, len) };
Status::Ok
}
#[expect(
non_camel_case_types,
reason = "the C name is the type's name; cbindgen emits it verbatim"
)]
pub type gpkg_tile_cursor_t = TileCursorHandle;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_cursor(
tiles: *const gpkg_tiles_t,
error: *mut gpkg_error_t,
) -> *mut gpkg_tile_cursor_t {
unsafe { open_cursor(tiles, &CursorScan::All, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_cursor_at(
tiles: *const gpkg_tiles_t,
zoom: i64,
error: *mut gpkg_error_t,
) -> *mut gpkg_tile_cursor_t {
unsafe { open_cursor(tiles, &CursorScan::At(zoom), error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_cursor_in(
tiles: *const gpkg_tiles_t,
zoom: i64,
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
error: *mut gpkg_error_t,
) -> *mut gpkg_tile_cursor_t {
let bbox = BoundingBox::new(min_x, min_y, max_x, max_y);
unsafe { open_cursor(tiles, &CursorScan::In(zoom, bbox), error) }
}
unsafe fn open_cursor(
tiles: *const gpkg_tiles_t,
scan: &CursorScan,
error: *mut gpkg_error_t,
) -> *mut gpkg_tile_cursor_t {
if tiles.is_null() {
unsafe { set_error(error, Status::BadArgument, "tiles handle is NULL") };
return std::ptr::null_mut();
}
match unsafe { (*tiles).cursor(scan) } {
Ok(handle) => Box::into_raw(Box::new(handle)),
Err(err) => {
unsafe { set_library_error(error, &err) };
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tile_cursor_next(
cursor: *mut gpkg_tile_cursor_t,
out_zoom: *mut i64,
out_column: *mut i64,
out_row: *mut i64,
out_data: *mut *const u8,
out_len: *mut usize,
error: *mut gpkg_error_t,
) -> Status {
if cursor.is_null() || out_data.is_null() || out_len.is_null() {
unsafe {
set_error(
error,
Status::BadArgument,
"cursor handle, out_data or out_len is NULL",
);
}
return Status::BadArgument;
}
let handle = unsafe { &mut *cursor };
match handle.next() {
Ok(Some(tile)) => {
let coord = tile.coord();
let data = tile.data();
if !out_zoom.is_null() {
unsafe { *out_zoom = coord.zoom_level };
}
if !out_column.is_null() {
unsafe { *out_column = coord.column };
}
if !out_row.is_null() {
unsafe { *out_row = coord.row };
}
unsafe { *out_data = data.as_ptr() };
unsafe { *out_len = data.len() };
Status::Ok
}
Ok(None) => {
unsafe { *out_data = std::ptr::null() };
unsafe { *out_len = 0 };
Status::Ok
}
Err(err) => {
unsafe { set_library_error(error, &err) };
Status::from(&err)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tile_cursor_free(cursor: *mut gpkg_tile_cursor_t) {
if cursor.is_null() {
return;
}
drop(unsafe { Box::from_raw(cursor) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_create(
gpkg: *const Container,
name: *const c_char,
srs_id: i32,
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
min_zoom: i64,
max_zoom: i64,
base_columns: i64,
base_rows: i64,
tile_width: i64,
tile_height: i64,
error: *mut gpkg_error_t,
) -> *mut gpkg_tiles_t {
let set = TileMatrixSet::new(srs_id, min_x, min_y, max_x, max_y);
unsafe {
create_pyramid(
gpkg,
name,
set,
min_zoom,
max_zoom,
(base_columns, base_rows),
(tile_width, tile_height),
error,
)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_tiles_create_web_mercator(
gpkg: *const Container,
name: *const c_char,
min_zoom: i64,
max_zoom: i64,
error: *mut gpkg_error_t,
) -> *mut gpkg_tiles_t {
let set = TileMatrixSet::web_mercator_quad();
unsafe { create_pyramid(gpkg, name, set, min_zoom, max_zoom, (0, 0), (0, 0), error) }
}
#[expect(
clippy::too_many_arguments,
reason = "a private body shared by two C entry points whose parameters are the C signature"
)]
unsafe fn create_pyramid(
gpkg: *const Container,
name: *const c_char,
set: TileMatrixSet,
min_zoom: i64,
max_zoom: i64,
base_grid: (i64, i64),
tile_size: (i64, i64),
error: *mut gpkg_error_t,
) -> *mut gpkg_tiles_t {
if gpkg.is_null() {
unsafe { set_error(error, Status::BadArgument, "gpkg handle is NULL") };
return std::ptr::null_mut();
}
let Some(name) = (unsafe { borrow_str(name, error, "pyramid name") }) else {
return std::ptr::null_mut();
};
let mut ladder = ZoomLadder::new(min_zoom, max_zoom);
if base_grid != (0, 0) {
ladder = ladder.base_grid(base_grid.0, base_grid.1);
}
if tile_size != (0, 0) {
ladder = ladder.tile_size(tile_size.0, tile_size.1);
}
let matrices = match set.ladder(ladder) {
Ok(matrices) => matrices,
Err(err) => {
unsafe { set_error(error, Status::InvalidArgument, &err.to_string()) };
return std::ptr::null_mut();
}
};
let builder = TilePyramidBuilder::new(name, set).matrices(matrices);
match unsafe { (*gpkg).create_tiles(&builder) } {
Ok(handle) => Box::into_raw(Box::new(handle)),
Err(err) => {
unsafe { set_library_error(error, &err) };
std::ptr::null_mut()
}
}
}