Skip to content

Query a source

A source query reports the loaded features in a source, including features that no layer draws. To find features at a screen position, use Query a map.

The query belongs to the render session and runs on the session’s owner thread. Source queries become available after the first render update. An earlier query reports an invalid-state status.

A vector source needs source-layer IDs, because one vector tile holds several layers and MapLibre reads the layers that you name. A vector query with no source-layer ID returns zero features. A GeoJSON source holds a single layer and ignores the field.

query-source-features.c
const mln_buffer_view source_layers[] = {view("poi")};
mln_source_feature_query_options options =
mln_source_feature_query_options_default();
options.fields = MLN_SOURCE_FEATURE_QUERY_OPTION_SOURCE_LAYER_IDS;
options.source_layer_ids = source_layers;
options.source_layer_id_count = 1;

Name the source itself by its style source ID. That ID is the query’s second argument, and it differs from the source-layer IDs in the options.

query-source-features.c
mln_queried_feature_list result = MLN_HANDLE_NULL;
const mln_status queried = mln_render_session_query_source_features(
session, view("places"), &options, &result
);
if (queried != MLN_STATUS_OK) return;
read_features(result);
mln_queried_feature_list_destroy(result);

A successful query can return no hits for an unknown source ID. Copy every value that the host keeps.

query-source-features.c
size_t count = 0;
if (mln_queried_feature_list_count(result, &count) != MLN_STATUS_OK) return;
if (count == 0) return;
mln_queried_feature hit = mln_queried_feature_default();
if (mln_queried_feature_list_get(result, 0, &hit) != MLN_STATUS_OK) return;
// Copy hit.feature and any identifier or state view you keep.

A query covers the tiles that the source loaded for the current camera. Its result therefore changes as the camera moves and more tiles arrive. A source loads tiles only while a layer uses it.

An extension query runs a named extension against one source feature. The supercluster extension reads a clustered GeoJSON source. Its leaves field returns the features under one cluster as a feature collection.

supercluster matches numbers by exact native JSON type. Encode limit and offset as nonnegative integer literals so MapLibre reads them as unsigned values.

cluster-leaves.c
const mln_buffer_view arguments = view("{\"limit\":10,\"offset\":0}");

The call names the source, the cluster feature, the extension, and the field within that extension.

cluster-leaves.c
mln_buffer result = MLN_HANDLE_NULL;
const mln_status queried = mln_render_session_query_feature_extensions(
session, view("places"), cluster, view("supercluster"), view("leaves"),
&arguments, &result
);
if (queried == MLN_STATUS_OK) read_leaves(result);
mln_buffer_destroy(result);

An extension result is one owned buffer. For leaves, parse it as a GeoJSON FeatureCollection; scalar extension fields return their JSON value.

cluster-leaves.c
mln_buffer_view json = {0};
if (mln_buffer_get(result, &json) != MLN_STATUS_OK) return;
// Parse json.data[0..json.size] as a GeoJSON FeatureCollection. Each feature
// is one point that the cluster contains.

Pass the Feature bytes from a queried feature to the extension query. supercluster also matches cluster_id by exact JSON type. Preserve its nonnegative integer literal; rewriting every number as a double produces a successful query with an empty result.