libkart C API
-------------
``libkart`` is a native C-ABI shared library (written in Rust) that lets an
external program read Kart repositories *in-process*, instead of shelling out to
the ``kart`` CLI. It is loaded directly into the host process (for example via a
`cffi `_ wrapper in Python) and exposes a small,
stable C ABI for opening a repo, listing and opening
:doc:`datasets `, reading feature/tile blobs, and
decoding GeoPackage geometries.
The library ships inside the Kart bundle, alongside the ``kart`` executable:
- shared library: ``_internal/libkart.so`` (Linux), ``_internal/libkart.dylib``
(macOS), or ``_internal/libkart.dll`` (Windows)
- C header: ``_internal/share/kart/libkart.h``
The ``_internal/`` directory lives inside the installed Kart application
directory (the same place the ``kart`` executable is shipped from); if you do not
know that path at build time, locate the bundle from the installed ``kart``
binary at runtime, or set your own environment variable pointing at the library.
The header is C and C++ compatible (wrapped in ``extern "C"``) and includes
```` and ````; the ``uint64_t`` / ``uint8_t`` / ``size_t``
types used below are the standard fixed-width/size types from those headers.
.. c:type:: uint64_t
.. c:type:: uint8_t
.. c:type:: size_t
What libkart does and does not do
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
libkart decodes Kart's storage formats, but it is **not** a git library: it does
not open arbitrary git objects, walk trees, or enumerate the features/tiles in a
dataset for you. The two functions that decode an individual feature or tile
(:c:func:`kart_feature_geometry`, :c:func:`kart_tile_summary_json`) take *raw
git blob bytes that you read yourself* using a separate git library (e.g.
`pygit2 `_ / libgit2). See
`Reading feature and tile blobs`_ for how to obtain those bytes.
Terminology
~~~~~~~~~~~
A few Kart-internal terms appear below. You normally do not need to understand
the storage format in detail — libkart decodes it for you — but the glosses help
make sense of the error messages and the feature/tile inputs:
- **refish** — any git ref or commit-ish: a branch or tag name, ``HEAD``, or a
commit hash. (The special values ``""`` and ``"[EMPTY]"`` resolve to the empty
tree, and ``"HEAD"`` on an unborn HEAD also resolves to the empty tree; see
:c:func:`kart_repo_list_datasets`.)
- **dataset's internal tree** — each dataset is stored under a hidden child
directory (``.table-dataset`` for tables, ``.point-cloud-dataset.v1`` for point
clouds). It contains a ``meta/`` subtree (schema, CRS, etc.) and a ``feature/``
(table) or ``tile/`` (point-cloud) subtree of blobs. See
:doc:`/pages/development/table_v3` and :doc:`/pages/development/pointcloud_v1`.
- **legend** — a small per-revision blob in ``meta/legend/`` that maps a stored
feature's value slots (which omit the primary-key columns) back to schema
column ids. libkart loads it for you to find which slot holds the geometry; you
never construct one.
- **WKB** — the OGC Well-Known Binary geometry encoding. **GPKG /
StandardGeoPackageBinary** — Kart's name for the standard
`GeoPackage binary geometry encoding `_ (a small
header followed by WKB); see `GPKG geometry byte format`_.
Typical call sequence
~~~~~~~~~~~~~~~~~~~~~~
#. :c:func:`kart_repo_open` — open the repo, get a repo handle.
#. :c:func:`kart_repo_list_datasets` — list dataset paths at a refish.
#. :c:func:`kart_dataset_open` — open one dataset, get a dataset handle.
#. :c:func:`kart_dataset_type` / :c:func:`kart_dataset_schema_json` /
:c:func:`kart_dataset_crs_wkt` — inspect the dataset. The type tells you which
decoder applies: ``table`` => :c:func:`kart_feature_geometry`;
``point-cloud`` => :c:func:`kart_tile_summary_json`; ``raster`` /
``unsupported`` => no per-blob reader is provided. (Raster is not yet
implemented rather than fundamentally different: raster tiles are LFS
pointers like point-cloud tiles, but their summaries also involve the PAM
sidecar files, and no consumer needs them yet.)
#. *Read blobs yourself* (see `Reading feature and tile blobs`_): use your own
git library to walk the dataset's ``feature/`` or ``tile/`` subtree and read
each blob's raw bytes.
#. :c:func:`kart_feature_geometry` / :c:func:`kart_tile_summary_json` — decode a
blob; then optionally :c:func:`kart_gpkg_to_wkb` etc. on the geometry.
#. Free every out-buffer with :c:func:`kart_free`, and every handle with
:c:func:`kart_repo_free` / :c:func:`kart_dataset_free`.
A complete worked example is in `Usage example (Python / cffi)`_.
ABI conventions
~~~~~~~~~~~~~~~
Return codes
^^^^^^^^^^^^
Every fallible function returns an ``int`` return code: ``0`` means success and
``-1`` means error. The two free functions (:c:func:`kart_repo_free`,
:c:func:`kart_dataset_free`) and :c:func:`kart_free` return ``void``;
:c:func:`kart_last_error` returns ``const char *``.
On error (rc ``-1``) the failing function sets a thread-local message
retrievable via :c:func:`kart_last_error`. Messages are prefixed by category,
e.g. ``not implemented:``, ``not found:``, ``format error:``, ``git error:``,
``msgpack error:``, ``json error:``, ``utf-8 error:``.
Handles
^^^^^^^
Repos and datasets are referred to by opaque ``uint64_t`` handles; ``0`` is
never a valid handle.
- Free a repo handle with :c:func:`kart_repo_free` and a dataset handle with
:c:func:`kart_dataset_free`.
- A dataset is an independent in-memory snapshot: at open time it eagerly copies
the dataset's ``meta/`` subtree, and it does **not** keep the repo alive. You
may free the repo and keep using datasets opened from it.
- Passing an unknown/already-freed handle to a fallible function returns rc
``-1`` with ``not found: repo handle`` or ``not found: dataset handle``.
Passing an unknown handle to a free function is a silent no-op.
Returned buffers and memory ownership
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Functions that return data through ``char **`` / ``uint8_t **`` (plus a
``size_t *`` length) out-params allocate the buffer with ``malloc``. The caller
**must** release it with :c:func:`kart_free` (never with their own allocator's
``free``). ``kart_free(NULL)`` is a safe no-op.
Returned text/JSON buffers are **not** NUL-terminated. Always use the
accompanying ``*out_len``; never call ``strlen`` on them. JSON buffers are
UTF-8-encoded JSON bytes.
Absent vs. error
^^^^^^^^^^^^^^^^
A buffer-returning function whose result is logically absent or empty returns rc
``0`` with ``*out == NULL`` and ``*out_len == 0`` (no buffer to free). Callers
must treat a ``NULL`` out-pointer as "absent", which is distinct from an error
(rc ``-1``).
Input strings
^^^^^^^^^^^^^
C string in-arguments (``path``, ``refish``, ``name``) must be valid
NUL-terminated UTF-8. A ``NULL`` pointer yields rc ``-1``
``format error: unexpected NULL string argument``; invalid UTF-8 yields a utf-8
error. Byte ``(ptr, len)`` in-arguments are treated as an empty slice if the
pointer is ``NULL`` or ``len`` is ``0``.
Error message lifetime
^^^^^^^^^^^^^^^^^^^^^^^
:c:func:`kart_last_error` returns a pointer owned by libkart that is valid only
until the *next* libkart call on the same thread. Copy the string if you need to
keep it. Never free it.
Thread-safety
^^^^^^^^^^^^^
The handle registries are mutex-protected, so calls are thread-safe.
``last_error`` is thread-local, so each thread reads its own most-recent error.
Calls that touch the same registry serialize: each operation holds the lock for
its duration, so do not call another libkart function that touches the same
registry from a thread already blocked inside one (e.g. via a host-language
signal handler or reentrant wrapper). No explicit initialization is required —
functions are usable as soon as the library is loaded. Handles live in
process-global registries; freeing them is optional at process exit but required
to avoid leaks in a long-running host.
Function reference
~~~~~~~~~~~~~~~~~~
Repo functions
^^^^^^^^^^^^^^
.. c:function:: int kart_repo_open(const char *path, uint64_t *out_repo)
Open the Kart repository rooted at ``path``, returning an opaque repo handle.
Pass the path to the Kart repository directory (the folder created by ``kart
init`` / ``kart clone``).
.. note::
Implementation detail: it looks for the git dir under ``.kart/``
(preferred) or legacy ``.sno/`` beneath ``path``, falling back to treating
``path`` itself as a bare git dir if neither exists.
:param path: NUL-terminated UTF-8 filesystem path to the repo root.
:param out_repo: out-param receiving the new (non-zero) repo handle on success.
:returns:
- ``*out_repo`` — the new, non-zero repo handle. Free it with
:c:func:`kart_repo_free`.
:errors:
- ``path`` is not a Kart repository (``git error:``).
.. c:function:: void kart_repo_free(uint64_t repo)
Release a repo handle previously returned by :c:func:`kart_repo_open`. No-op
if the handle is unknown/already freed. Datasets opened from this repo remain
valid after freeing it (they hold their own snapshot).
:param repo: the handle to free.
.. c:function:: int kart_repo_table_dataset_version(uint64_t repo, int *out_version)
Return the Kart table-dataset repository-structure format version (e.g. ``3``
for current ``.kart`` repos, ``2`` for legacy ``.sno``).
.. note::
Implementation detail — resolution order: (1) read the version blob in the
``HEAD`` root tree (``.kart.repostructure.version`` => 3-style, or legacy
``.sno.repository.version`` => 2-style; the blob's own integer contents are
parsed); (2) fall back to git config keys ``kart.repostructure.version``
then ``sno.repository.version``; (3) default to ``3`` if none found.
:param repo: repo handle.
:param out_version: out-param receiving the integer version.
:returns:
- ``*out_version`` — the integer format version (e.g. ``3``).
:errors:
- unknown repo handle (``not found: repo handle``).
- the version blob is not UTF-8 (``utf-8 error:``) or not an integer
(``format error: invalid version blob contents: ...``).
- a git error.
.. c:function:: int kart_repo_list_datasets(uint64_t repo, const char *refish, uint8_t **out_json, size_t *out_len)
List the paths of all datasets present at a given refish, as a JSON array of
strings (sorted ascending). A tree is recognised as a dataset if it has a
direct child tree whose name matches the dataset-dir pattern
(``\.[^/]*-dataset[^/]*``); dot-prefixed (hidden) trees are skipped during the
walk.
:param repo: repo handle.
:param refish: NUL-terminated UTF-8 git refish (see refish under
`Terminology`_). An empty string or ``"[EMPTY]"`` resolves to the git empty
tree; ``"HEAD"`` on an unborn/missing HEAD also resolves to the empty tree
(=> empty list).
:param out_json: out-param receiving a malloc'd UTF-8 JSON buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out_json`` — a UTF-8 JSON array of dataset path strings, e.g.
``["census2016_sdhca_ot_ra_short","nz_pa_points_topo_150k"]``. An empty
repo yields ``"[]"`` (still a buffer, not ``NULL``). Free with
:c:func:`kart_free`.
:errors:
- unknown repo handle (``not found: repo handle``).
- a git error.
Dataset functions
^^^^^^^^^^^^^^^^^
.. c:function:: int kart_dataset_open(uint64_t repo, const char *refish, const char *path, uint64_t *out_ds)
Open the dataset at ``path`` as it exists at ``refish``, returning an opaque
dataset handle. Eagerly loads the dataset's entire ``meta/`` subtree
(``schema.json``, legends, CRS WKT, format, etc.) into memory so subsequent
calls need no further git access and the repo need not stay alive.
Opening succeeds for any recognised dataset directory, including ones whose
type libkart does not specifically support (their :c:func:`kart_dataset_type`
is ``unsupported``).
.. note::
Implementation detail — inner-dir-to-type mapping: ``.table-dataset`` /
``.sno-dataset`` => ``table``, ``.point-cloud-dataset.v1`` =>
``point-cloud``, ``.raster-dataset.v1`` => ``raster``, any other
``.*-dataset*`` => ``unsupported``.
:param repo: repo handle.
:param refish: NUL-terminated UTF-8 refish (see refish under `Terminology`_).
:param path: NUL-terminated UTF-8 dataset path within the repo (e.g.
``"nz_pa_points_topo_150k"``); must be non-empty.
:param out_ds: out-param receiving the new (non-zero) dataset handle.
:returns:
- ``*out_ds`` — the new, non-zero dataset handle. Free with
:c:func:`kart_dataset_free`.
:errors:
- unknown repo handle (``not found: repo handle``).
- empty ``path`` (``not found: empty dataset path``).
- ``path`` not found, or not a tree (``not found: dataset path not found:
...`` / ``... is not a tree: ...``).
- no dataset directory under ``path`` (``not found: no dataset dir under
path: ...``).
- a git or parse error.
.. c:function:: void kart_dataset_free(uint64_t ds)
Release a dataset handle previously returned by :c:func:`kart_dataset_open`.
No-op if the handle is unknown/already freed.
:param ds: the handle to free.
.. c:function:: int kart_dataset_type(uint64_t ds, uint8_t **out, size_t *out_len)
Return the dataset's Kart type string.
:param ds: dataset handle.
:param out: out-param for a malloc'd UTF-8 text buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — one of ``table``, ``point-cloud``, ``raster`` or
``unsupported``. Always present for an open dataset (never ``NULL``); not
NUL-terminated, so use ``out_len``. Free with :c:func:`kart_free`.
:errors:
- unknown dataset handle (``not found: dataset handle``).
.. c:function:: int kart_dataset_schema_json(uint64_t ds, uint8_t **out, size_t *out_len)
Return a JSON object summarising the dataset: its path, type, whether it has a
geometry column, primary key, geometry column name, and full column list. See
`kart_dataset_schema_json output`_ for the object shape.
:param ds: dataset handle.
:param out: out-param for a malloc'd UTF-8 JSON buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — a UTF-8 JSON object (never ``NULL``); see
`kart_dataset_schema_json output`_. With no ``schema.json``, ``columns``
is ``[]`` and the geometry/primary-key fields are ``null``. Free with
:c:func:`kart_free`.
:errors:
- unknown dataset handle (``not found: dataset handle``).
- a JSON serialization error.
.. c:function:: int kart_dataset_crs_wkt(uint64_t ds, uint8_t **out, size_t *out_len)
Return the WKT of the dataset's geometry CRS.
:param ds: dataset handle.
:param out: out-param for a malloc'd UTF-8 text buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — the CRS WKT string, when the dataset has a geometry column
whose ``geometryCRS`` resolves to a stored ``crs/.wkt`` meta
item. Free with :c:func:`kart_free`.
- ``*out == NULL`` (absent) when there is no such CRS: no ``schema.json``,
no geometry column, no ``geometryCRS``, or the ``crs/*.wkt`` is missing.
:errors:
- unknown dataset handle (``not found: dataset handle``).
- bad schema JSON, or non-UTF-8 WKT (``utf-8 error:``).
.. c:function:: int kart_dataset_meta_item(uint64_t ds, const char *name, uint8_t **out, size_t *out_len)
Return the raw bytes of a named meta item from the dataset's ``meta/`` subtree
(passthrough, no parsing). Common keys are ``schema.json``, ``format.json``,
``title``, ``description`` and ``crs/.wkt``; content-addressed
``legend/`` blobs also live here. Most callers should prefer the
higher-level :c:func:`kart_dataset_schema_json` / :c:func:`kart_dataset_crs_wkt`
rather than fetching meta items by name. There is no API to enumerate the
available meta keys; see :doc:`/pages/development/table_v3` for the layout.
:param ds: dataset handle.
:param name: NUL-terminated UTF-8 key relative to ``meta/`` (e.g.
``"schema.json"``, ``"format.json"``, ``"title"``, ``"crs/EPSG:4326.wkt"``,
``"legend/"``).
:param out: out-param for a malloc'd raw byte buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — the raw meta-item bytes when ``name`` exists, returned
verbatim (may be JSON, plain text, msgpack, ...) and **not**
NUL-terminated. Free with :c:func:`kart_free`.
- ``*out == NULL`` (absent) when ``name`` is not present.
:errors:
- ``NULL`` or invalid ``name``.
- unknown dataset handle (``not found: dataset handle``).
Feature and tile functions
^^^^^^^^^^^^^^^^^^^^^^^^^^
Reading feature and tile blobs
""""""""""""""""""""""""""""""
The two functions below decode an *individual* feature or tile from its raw git
blob bytes. **libkart does not read those blobs for you** — it provides no
tree-walking or blob-reading API. You must read them yourself with a git library
(e.g. `pygit2 `_ / libgit2):
#. Resolve the refish to a commit/tree with your git library.
#. Descend into the dataset's path, then into its hidden internal tree
(``.table-dataset`` for tables, ``.point-cloud-dataset.v1`` for point clouds).
#. Walk the ``feature/`` (table) or ``tile/`` (point-cloud) subtree and read each
leaf blob's raw bytes. The blob *paths* are an encoded layout; you do not need
to decode them, only to read the blob contents.
#. Pass each blob's bytes to :c:func:`kart_feature_geometry` (table) or
:c:func:`kart_tile_summary_json` (point-cloud).
For the on-disk subtree layout and feature/tile encoding, see
:doc:`/pages/development/table_v3` and :doc:`/pages/development/pointcloud_v1`.
Raster and ``unsupported`` datasets have no per-blob reader here; use
:c:func:`kart_dataset_meta_item` and external tooling instead.
.. c:function:: int kart_feature_geometry(uint64_t ds, const uint8_t *blob, size_t blob_len, uint8_t **out, size_t *out_len)
Decode a single table/vector feature's git blob and extract its
`GeoPackage geometry bytes `_ for the dataset's
geometry column. Given the raw feature blob, returns the GPKG geometry (or
absent if the feature has no geometry); the dataset must be a table dataset
with a geometry column.
.. note::
Internal encoding (not needed to call this function): the blob
msgpack-decodes to ``[legend, non-pk-values]``; libkart loads the named
legend from the dataset's ``meta/legend/`` (see legend under
`Terminology`_) to find the geometry value's slot, which must be a msgpack
ext code ``'G'`` (``0x47``) carrying GPKG bytes.
:param ds: dataset handle (must be a table dataset with a geometry column to
yield anything).
:param blob: the raw feature blob bytes you read from git; ``NULL``/``0`` is
treated as an empty slice.
:param blob_len: length of ``blob``.
:param out: out-param for a malloc'd GPKG geometry byte buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — GPKG geometry bytes (starting with magic ``GP``) when the
feature has a non-null geometry. Free with :c:func:`kart_free`.
- ``*out == NULL`` (absent) when there is no geometry: the dataset or the
feature's legend has no geometry column, or the value is null.
:errors:
- unknown dataset handle (``not found: dataset handle``).
- a malformed blob or legend (msgpack/format error), a missing legend
(``not found: legend not found in meta: ...``), or an unexpected
non-geometry value.
.. c:function:: int kart_tile_summary_json(uint64_t ds, const uint8_t *blob, size_t blob_len, uint8_t **out, size_t *out_len)
Decode a point-cloud tile's Git-LFS pointer blob into a JSON summary object.
See `kart_tile_summary_json output`_ for the object shape.
A point-cloud tile's git blob is a Git-LFS *pointer*: a small text file that
references the real ``.laz`` / ``.copc`` tile data (stored separately) by a
content hash (``oid``). This function summarises that pointer; it does not
read the tile data itself.
:param ds: dataset handle (point-cloud).
:param blob: the raw LFS pointer file text bytes you read from git;
``NULL``/``0`` => empty slice.
:param blob_len: length of ``blob``.
:param out: out-param for a malloc'd UTF-8 JSON buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — a UTF-8 JSON object summarising the tile (never ``NULL``); see
`kart_tile_summary_json output`_. Free with :c:func:`kart_free`.
:errors:
- unknown dataset handle (``not found: dataset handle``).
- a non-UTF-8 pointer blob, a bad size field, or bad base64/msgpack.
.. note::
The C ABI provides no way to pass the tile's git blob name, so the
``name`` key (the tile filename) is **absent** from the returned JSON.
Derive the filename yourself from the git blob's name (using the returned
``format`` for the extension).
GPKG geometry functions
^^^^^^^^^^^^^^^^^^^^^^^
These operate on raw `StandardGeoPackageBinary `_
geometry bytes (Kart's name for the standard GeoPackage binary geometry
encoding; see `GPKG geometry byte format`_) and require no dataset handle.
.. c:function:: int kart_gpkg_is_empty(const uint8_t *g, size_t n, int *out)
Test whether a StandardGeoPackageBinary geometry is flagged empty.
:param g: GPKG geometry bytes (``NULL``/``0`` => empty slice, which fails the
header parse).
:param n: length of ``g``.
:param out: out-param receiving ``0``/``1`` (``1`` = empty).
:returns:
- ``*out`` — ``1`` if the geometry's empty flag is set, else ``0``.
:errors:
- the GPKG header cannot be parsed: too short, missing ``GP`` magic
(``format error: Expected GeoPackage Binary Geometry``), unsupported
version, an extended header, or an invalid envelope indicator.
.. c:function:: int kart_gpkg_geometry_type(const uint8_t *g, size_t n, int *out)
Return the OGR/ISO WKB geometry type code of a GPKG geometry, read using the
WKB's own endianness byte. See `WKB type codes`_.
:param g: GPKG geometry bytes.
:param n: length of ``g``.
:param out: out-param receiving the WKB type code.
:returns:
- ``*out`` — the ISO/OGR WKB type integer (e.g. ``1`` Point, ``2``
LineString, ``3`` Polygon, ``1003`` Polygon Z); see `WKB type codes`_.
:errors:
- the GPKG header cannot be parsed, or the WKB is truncated
(``format error: GPKG geometry truncated WKB``).
.. c:function:: int kart_gpkg_envelope(const uint8_t *g, size_t n, int only_2d, int calc, double *out6, int *out_count)
Read the GPKG geometry's stored bounding-box envelope into a caller-supplied
array of doubles. See `Envelope tuple order`_.
:param g: GPKG geometry bytes.
:param n: length of ``g``.
:param only_2d: if non-zero, truncate the result to the first 4 doubles (drop Z/M).
:param calc: ``calculate_if_missing`` flag.
:param out6: caller-provided array of at least 6 doubles to fill.
:param out_count: out-param set to the number of valid doubles written
(``0``, ``4``, or ``6``).
:returns:
- ``*out_count`` ``4`` (XY) or ``6`` (XYZ), with ``out6[0..*out_count]``
filled. An 8-double XYM/ZM envelope is capped to the first 6.
- ``*out_count == 0`` and ``out6`` untouched when there is no usable
envelope: the geometry is flagged empty, none is stored (and
``calc == 0``), or a stored value is ``NaN``.
:errors:
- the GPKG header cannot be parsed, or the envelope is truncated.
- ``calc != 0`` but no envelope is stored (see the warning below).
.. warning::
``calculate_if_missing`` is **not yet implemented**, and only fires when
there is literally no stored envelope (envelope indicator ``0``) *and*
``calc != 0``: libkart cannot compute envelopes from WKB (it would need
GDAL) and returns rc ``-1`` with
``not implemented: gpkg.envelope calculate_if_missing (needs GDAL)``. A
*stored* envelope containing ``NaN`` is treated as missing and always
yields ``*out_count == 0`` (rc ``0``) regardless of ``calc``.
.. c:function:: int kart_gpkg_to_wkb(const uint8_t *g, size_t n, uint8_t **out, size_t *out_len)
Strip the GPKG header and return the plain WKB geometry, normalised to
little-endian byte order.
:param g: GPKG geometry bytes.
:param n: length of ``g``.
:param out: out-param for a malloc'd WKB byte buffer.
:param out_len: out-param receiving the buffer length.
:returns:
- ``*out`` — the plain WKB geometry, normalised to little-endian (first
byte ``1``; big-endian input is byte-swapped). Free with
:c:func:`kart_free`.
:errors:
- the GPKG header cannot be parsed, the WKB is truncated, its byte-order
marker is invalid (``format error: Invalid WKB byte-order marker: ...``),
or its geometry type is unsupported while byte-swapping big-endian WKB.
Misc functions
^^^^^^^^^^^^^^
.. c:function:: const char *kart_last_error(void)
Return the current thread's most recent libkart error message.
:returns: a pointer to a NUL-terminated UTF-8 C string owned by libkart
(thread-local). Never ``NULL`` (empty string ``""`` if no error yet). Valid
only until the next libkart call on the same thread; copy it if you need to
keep it. Do **not** free it. Messages are category-prefixed (e.g.
``not found: ...``, ``format error: ...``, ``git error: ...``,
``not implemented: ...``).
.. c:function:: void kart_free(void *ptr)
Free a buffer previously returned by libkart through a ``uint8_t **`` /
``char **`` out-param.
:param ptr: the buffer pointer (the value written to ``*out``). ``NULL`` is
allowed.
:returns: nothing. Calls libc ``free``; ``kart_free(NULL)`` is a safe no-op.
Use this (not your own allocator's ``free``, and never on the pointer
returned by :c:func:`kart_last_error`).
Returned data shapes
~~~~~~~~~~~~~~~~~~~~
kart_dataset_schema_json output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A UTF-8 JSON object with keys:
- ``path`` — string, the dataset path.
- ``type`` — string dataset type (e.g. ``"table"``).
- ``has_geometry`` — bool, ``true`` iff a geometry column id was found.
- ``primary_key`` — string column name, or ``null`` (only set when there is
exactly one primary-key column).
- ``geom_column_name`` — string name of the geometry column, or ``null``.
- ``columns`` — array: the verbatim contents of the dataset's ``meta/schema.json``
(the Kart column descriptors); an empty array if there is no ``schema.json``.
Each column descriptor in ``columns`` comes straight from ``schema.json`` and
includes at least ``id`` (uuid string), ``name`` (string) and ``dataType`` (e.g.
``"integer"``, ``"text"``, ``"geometry"``). The geometry column (``dataType ==
"geometry"``) additionally carries ``geometryType`` and ``geometryCRS`` (e.g.
``"EPSG:4326"``); primary-key columns carry ``primaryKeyIndex`` (integer). For
the authoritative schema and CRS definitions, see
:doc:`/pages/development/table_v3`.
kart_tile_summary_json output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A UTF-8 JSON object built from the LFS pointer's ``key value`` text lines plus
the decoded ``ext-0-kart-encoded.*`` map. Each line is parsed: ``size`` becomes
an integer; ``ext-0-kart-encoded.`` lines are base64-decoded (altchars
``.``/``-``, padding-tolerant) then msgpack-map-decoded and merged in; the
``version`` line is dropped. Typical keys: ``oid`` (string, e.g.
``"sha256:..."``), ``size`` (integer), ``format`` (string, e.g.
``"laz-1.4/copc-1.0"``), ``pointCount`` (integer), ``nativeExtent`` (string,
comma-separated ``minx,maxx,miny,maxy,minz,maxz``), ``crs84Extent`` (WKT
``POLYGON`` string), ``sourceOid`` (string).
As noted above, ``name`` is absent from C-ABI output; supply the tile filename
yourself (the ``oid`` is the LFS content hash of the real tile data).
GPKG geometry byte format
^^^^^^^^^^^^^^^^^^^^^^^^^
The geometry bytes accepted and produced by the ``kart_gpkg_*`` functions and
:c:func:`kart_feature_geometry` use the standard
`GeoPackage binary geometry encoding `_ (which Kart
calls *StandardGeoPackageBinary*):
- magic ``GP`` (``0x47 0x50``)
- version byte (must be ``0``)
- flags byte
- optional ``srs_id`` + envelope
- then the WKB geometry
You do not normally parse these bytes yourself — the ``kart_gpkg_*`` functions
do. For reference, the flags byte encodes: ``0x01`` = header fields
little-endian; ``0x0E`` (``>> 1``) = envelope-contents indicator (``0`` => 0
doubles, ``1`` => 4 XY, ``2`` => 6 XYZ, ``3`` => 6 XYM, ``4`` => 8 XYZM);
``0x10`` = empty; ``0x20`` = ExtendedGeoPackageBinary. The WKB starts at offset
``8 + envelope_doubles * 8``. ExtendedGeoPackageBinary and a version ``!= 0`` are
rejected.
Envelope tuple order
^^^^^^^^^^^^^^^^^^^^
The doubles written by :c:func:`kart_gpkg_envelope` are ordered
``(minx, maxx, miny, maxy[, minz, maxz][, minm, maxm])`` — note the per-axis
``(min, max)`` pairing, **not** ``minx, miny, maxx, maxy``. ``out_count`` is
``0`` (none/empty/NaN), ``4`` (XY, or when ``only_2d`` truncates), or ``6`` (XYZ;
an 8-double XYZM stored envelope is capped to the first 6 doubles). Any ``NaN`` in
the stored envelope is treated as missing (count ``0``).
WKB type codes
^^^^^^^^^^^^^^
:c:func:`kart_gpkg_geometry_type` returns the OGR/ISO WKB integer type: base
``1`` = Point, ``2`` = LineString, ``3`` = Polygon, ``4`` = MultiPoint, ``5`` =
MultiLineString, ``6`` = MultiPolygon, ``7`` = GeometryCollection. ISO
dimensionality is encoded as ``base + 1000*Z + 2000*M`` (e.g. ``1003`` = Polygon
Z, ``2002`` = LineString M, ``3001`` = Point ZM). :c:func:`kart_gpkg_to_wkb`
always emits little-endian WKB (first byte ``1``).
Memory ownership summary
~~~~~~~~~~~~~~~~~~~~~~~~
- **Handles** (``uint64_t``): owned by the caller; free repos with
:c:func:`kart_repo_free` and datasets with :c:func:`kart_dataset_free`.
Datasets outlive the repo they were opened from.
- **Out-buffers** (``uint8_t **`` / ``char **``): owned by the caller; free with
:c:func:`kart_free`. A ``NULL`` out-pointer means "absent" — nothing to free.
- **Caller-provided arrays** (``out6`` in :c:func:`kart_gpkg_envelope`): owned by
the caller; libkart only writes into them.
- **Error strings** (:c:func:`kart_last_error`): owned by libkart, thread-local,
valid only until the next call on that thread. Never free; copy if needed.
Usage example (Python / cffi)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following uses `cffi `_ to load the library
(from the ``LIBKART_PATH`` environment variable; see
`What libkart does and does not do`_ for where the library lives) and open a
repo, list its datasets, read a dataset's schema, then read one feature and
extract its geometry as WKB. The feature blob is read with
`pygit2 `_, since libkart does not walk git trees (see
`Reading feature and tile blobs`_). Note how each fallible call is checked, every
out-buffer is freed, and a ``NULL`` out-pointer is treated as absent.
.. code-block:: python
import json
import os
import pygit2
from cffi import FFI
ffi = FFI()
ffi.cdef("""
int kart_repo_open(const char* path, uint64_t* out_repo);
void kart_repo_free(uint64_t repo);
int kart_repo_list_datasets(uint64_t repo, const char* refish,
uint8_t** out_json, size_t* out_len);
int kart_dataset_open(uint64_t repo, const char* refish,
const char* path, uint64_t* out_ds);
void kart_dataset_free(uint64_t ds);
int kart_dataset_schema_json(uint64_t ds, uint8_t** out, size_t* out_len);
int kart_feature_geometry(uint64_t ds, const uint8_t* blob, size_t blob_len,
uint8_t** out, size_t* out_len);
int kart_gpkg_to_wkb(const uint8_t* g, size_t n,
uint8_t** out, size_t* out_len);
const char* kart_last_error(void);
void kart_free(void* ptr);
""")
lib = ffi.dlopen(os.environ["LIBKART_PATH"]) # e.g. .../_internal/libkart.so
def check(rc):
# rc 0 == ok, -1 == error; kart_last_error() holds the message.
if rc != 0:
err = lib.kart_last_error()
msg = ffi.string(err).decode("utf-8", "replace") if err != ffi.NULL else ""
raise RuntimeError(msg or f"libkart call failed (rc={rc})")
def take_buffer(out_ptr, out_len):
# Read a libkart-malloc'd out-buffer into bytes, then free it. NULL -> None.
ptr = out_ptr[0]
if ptr == ffi.NULL:
return None
try:
return bytes(ffi.buffer(ptr, out_len[0]))
finally:
lib.kart_free(ptr)
def gpkg_to_wkb(gpkg):
# Strip the GPKG header, returning plain little-endian WKB.
g = ffi.from_buffer(gpkg)
out, out_len = ffi.new("uint8_t**"), ffi.new("size_t*")
check(lib.kart_gpkg_to_wkb(g, len(gpkg), out, out_len))
return take_buffer(out, out_len)
def walk_blobs(git, tree):
# Recursively yield every leaf blob under a pygit2 tree.
for entry in tree:
obj = git[entry.id]
if isinstance(obj, pygit2.Tree):
yield from walk_blobs(git, obj)
elif isinstance(obj, pygit2.Blob):
yield obj
repo_path = "/path/to/repo"
# Open a repo (handle is a uint64_t; 0 is never valid).
repo_out = ffi.new("uint64_t*")
check(lib.kart_repo_open(repo_path.encode(), repo_out))
repo = repo_out[0]
try:
# List datasets at a refish; returned buffer is JSON, NOT NUL-terminated.
out, out_len = ffi.new("uint8_t**"), ffi.new("size_t*")
check(lib.kart_repo_list_datasets(repo, b"HEAD", out, out_len))
datasets = json.loads(take_buffer(out, out_len) or b"[]")
ds_path = datasets[0]
# Open one dataset and read its schema.
ds_out = ffi.new("uint64_t*")
check(lib.kart_dataset_open(repo, b"HEAD", ds_path.encode(), ds_out))
ds = ds_out[0]
try:
out, out_len = ffi.new("uint8_t**"), ffi.new("size_t*")
check(lib.kart_dataset_schema_json(ds, out, out_len))
schema = json.loads(take_buffer(out, out_len) or b"{}")
# Read one feature blob from git ourselves (libkart does not do this).
git = pygit2.Repository(repo_path)
tree = git.revparse_single("HEAD").peel(pygit2.Tree)
feature_tree = tree[f"{ds_path}/.table-dataset/feature"]
for blob in walk_blobs(git, feature_tree):
# Decode this feature's GPKG geometry (NULL == no geometry).
out, out_len = ffi.new("uint8_t**"), ffi.new("size_t*")
check(lib.kart_feature_geometry(ds, blob.data, blob.size, out, out_len))
gpkg = take_buffer(out, out_len)
if gpkg is not None:
wkb = gpkg_to_wkb(gpkg)
break # just the first feature, for the example
finally:
lib.kart_dataset_free(ds)
finally:
lib.kart_repo_free(repo)