Skip to content

Query

This guide shows how to find the features the map draws, or the features a source has loaded.

Queries run through the map’s render session. A rendered query reads the last frame, so after a camera or style change, render a frame before you query.

To find what’s drawn at a screen point or in a box, use mln_render_session_query_rendered_featuresRenderSessionHandle.queryRenderedFeaturesRenderSessionHandle.QueryRenderedFeaturesAsyncRenderSessionHandle.QueryRenderedFeaturesmaplibre_native_ffi_render_session_query_rendered_features_asyncRenderSessionHandle.queryRenderedFeaturesRenderSessionHandle.queryRenderedFeaturesRenderSessionHandle.query_rendered_featuresRenderSessionHandle::query_rendered_featuresRenderSessionHandle.queryRenderedFeaturesRenderSessionHandle.queryRenderedFeaturesRenderSession.queryRenderedFeatures. Name the layers to query, or leave them out to query every layer.

main.c
static void on_rendered(
void* user_data, const mln_completion_result* result
) {
if (result->status == MLN_STATUS_OK) {
for (size_t i = 0; i < result->value_count; i++) {
const mln_rendered_feature* hit = MLN_STRIDED_AT(
mln_rendered_feature, result->value, result->value_size, i
);
// The views are valid only during this callback.
copy_feature(hit->feature);
}
}
on_done(user_data, result);
}
const mln_rendered_query_geometry box = {
.size = sizeof(mln_rendered_query_geometry),
.type = MLN_RENDERED_QUERY_GEOMETRY_TYPE_BOX,
.data.box = {
.min = {.x = point.x - 5, .y = point.y - 5},
.max = {.x = point.x + 5, .y = point.y + 5},
},
};
const mln_buffer_view layers[] = {mln_buffer_view_from_cstr("poi")};
const mln_rendered_feature_query_options options = {
.size = sizeof(mln_rendered_feature_query_options),
.fields = MLN_RENDERED_FEATURE_QUERY_OPTION_LAYER_IDS,
.layer_ids = layers,
.layer_id_count = 1,
};
const mln_completion rendered = {
.size = sizeof(mln_completion), .callback = on_rendered
};
mln_render_session_query_rendered_features(
session, &box, &options, &rendered, NULL
);
main.dart
final hits = await session.queryRenderedFeatures(
RenderedQueryGeometry.box(
ScreenBox(
min: ScreenPoint(x: point.x - 5, y: point.y - 5),
max: ScreenPoint(x: point.x + 5, y: point.y + 5),
),
),
options: RenderedFeatureQueryOptions(layerIds: ['poi']),
);
for (final hit in hits) {
// hit.feature is the decoded GeoJSON feature.
useFeature(hit.feature);
}
Program.cs
var box = new ScreenBox(
new ScreenPoint(point.X - 5, point.Y - 5),
new ScreenPoint(point.X + 5, point.Y + 5)
);
var hits = await session.QueryRenderedFeaturesAsync(
new RenderedQueryGeometry.Box(box),
new RenderedFeatureQueryOptions { LayerIds = ["poi"] }
);
foreach (var hit in hits)
{
// hit.Feature is GeoJSON text.
UseFeature(hit.Feature);
}
main.go
box := maplibre.RenderedQueryGeometryBoxVariant{
Value: maplibre.ScreenBox{
Min: maplibre.ScreenPoint{X: point.X - 5, Y: point.Y - 5},
Max: maplibre.ScreenPoint{X: point.X + 5, Y: point.Y + 5},
},
}
rendered, err := session.QueryRenderedFeatures(
box,
&maplibre.RenderedFeatureQueryOptions{
LayerIDs: []string{"poi"},
},
)
if err != nil {
return err
}
hits, err := rendered.Await(ctx)
if err != nil {
return err
}
for _, hit := range hits {
// hit.Feature is the feature's GeoJSON.
useFeature(hit.Feature)
}
main.vala
ScreenBox box = {
{ point.x - 5, point.y - 5 }, { point.x + 5, point.y + 5 }
};
var options = new RenderedFeatureQueryOptions () {
layer_ids = { "poi" },
};
var hits = yield session.query_rendered_features_async (
new RenderedQueryGeometry.box (box), options
);
foreach (var hit in hits) {
// The feature is GeoJSON text.
use_feature (hit.get_feature ());
}
Main.java
List<RenderedFeature> hits =
session
.queryRenderedFeatures(
new RenderedQueryGeometry.Box(
new ScreenBox(
new ScreenPoint(
point.getX() - 5, point.getY() - 5),
new ScreenPoint(
point.getX() + 5, point.getY() + 5))),
RenderedFeatureQueryOptions.of()
.withLayerIds(List.of("poi")))
.join();
for (RenderedFeature hit : hits) {
// hit.getFeature() is the GeoJSON feature, as JSON.
useFeature(hit.getFeature());
}
Main.kt
val hits =
session
.queryRenderedFeatures(
RenderedQueryGeometry.Box(
ScreenBox(
min = ScreenPoint(x = point.x - 5, y = point.y - 5),
max = ScreenPoint(x = point.x + 5, y = point.y + 5),
),
),
RenderedFeatureQueryOptions(layerIds = listOf("poi")),
)
.await()
for (hit in hits) {
// hit.feature is the GeoJSON feature, as JSON.
useFeature(hit.feature)
}
main.py
hits = session.query_rendered_features(
mln.RenderedQueryGeometryBoxVariant(
mln.ScreenBox(
min=mln.ScreenPoint(x=point.x - 5, y=point.y - 5),
max=mln.ScreenPoint(x=point.x + 5, y=point.y + 5),
)
),
mln.RenderedFeatureQueryOptions(layer_ids=["poi"]),
).result()
for hit in hits:
# hit.feature is the parsed GeoJSON feature.
use_feature(hit.feature)
main.rs
let area = RenderedQueryGeometry::Box(ScreenBox {
min: ScreenPoint::new(point.x - 5.0, point.y - 5.0),
max: ScreenPoint::new(point.x + 5.0, point.y + 5.0),
});
let options = RenderedFeatureQueryOptions {
layer_ids: Some(&["poi"]),
..Default::default()
};
let hits = session
.query_rendered_features(&area, Some(&options))?
.await?;
for hit in hits {
// hit.feature is the GeoJSON feature, as JSON.
use_feature(&hit.feature);
}
main.swift
let hits = try await session.queryRenderedFeatures(
geometry: .box(ScreenBox(
min: ScreenPoint(x: point.x - 5, y: point.y - 5),
max: ScreenPoint(x: point.x + 5, y: point.y + 5)
)),
options: RenderedFeatureQueryOptions(layerIds: ["poi"])
)
for hit in hits {
// hit.feature is the GeoJSON feature, as JSON.
useFeature(hit.feature)
}
main.ts
const hits = await session.queryRenderedFeatures(
{
kind: "box",
value: {
min: { x: point.x - 5, y: point.y - 5 },
max: { x: point.x + 5, y: point.y + 5 },
},
},
{ layerIds: ["poi"] },
);
for (const hit of hits) {
// hit.feature is the parsed GeoJSON feature.
useFeature(hit.feature);
}
main.zig
const rendered = try session.queryRenderedFeatures(
allocator,
.{ .box = .{
.min = .{ .x = point.x - 5, .y = point.y - 5 },
.max = .{ .x = point.x + 5, .y = point.y + 5 },
} },
.{ .layer_ids = &.{"poi"} },
null,
);
var hits = try rendered.await(null);
defer hits.deinit();
for (hits.value) |hit| {
// hit.feature is the GeoJSON feature, as JSON.
useFeature(hit.feature);
}

To search a source’s data instead of what’s drawn, query the source by its ID with mln_render_session_query_source_featuresRenderSessionHandle.querySourceFeaturesRenderSessionHandle.QuerySourceFeaturesAsyncRenderSessionHandle.QuerySourceFeaturesmaplibre_native_ffi_render_session_query_source_features_asyncRenderSessionHandle.querySourceFeaturesRenderSessionHandle.querySourceFeaturesRenderSessionHandle.query_source_featuresRenderSessionHandle::query_source_featuresRenderSessionHandle.querySourceFeaturesRenderSessionHandle.querySourceFeaturesRenderSession.querySourceFeatures, and for a vector source, name the source layers. To keep only some features, pass a filter.

A source query only sees the tiles that the source has loaded for the current view, and a source loads tiles only while a visible layer uses it.

main.c
static void on_source(
void* user_data, const mln_completion_result* result
) {
if (result->status == MLN_STATUS_OK) {
for (size_t i = 0; i < result->value_count; i++) {
const mln_source_feature* hit = MLN_STRIDED_AT(
mln_source_feature, result->value, result->value_size, i
);
copy_feature(hit->feature);
}
}
on_done(user_data, result);
}
const mln_buffer_view source_layers[] = {
mln_buffer_view_from_cstr("transportation")
};
const mln_source_feature_query_options source_options = {
.size = sizeof(mln_source_feature_query_options),
.fields = MLN_SOURCE_FEATURE_QUERY_OPTION_SOURCE_LAYER_IDS,
.source_layer_ids = source_layers,
.source_layer_id_count = 1,
// A filter from the MapLibre style spec, as JSON.
.filter = mln_buffer_view_from_cstr(
"[\"==\", [\"get\", \"class\"], \"motorway\"]"
),
};
const mln_completion source = {
.size = sizeof(mln_completion), .callback = on_source
};
mln_render_session_query_source_features(
session, mln_buffer_view_from_cstr("openmaptiles"),
&source_options, &source, NULL
);
main.dart
final features = await session.querySourceFeatures(
'openmaptiles',
options: SourceFeatureQueryOptions(
sourceLayerIds: ['transportation'],
// A filter from the MapLibre style spec.
filter: [
'==',
['get', 'class'],
'motorway',
],
),
);
for (final feature in features) {
useFeature(feature.feature);
}
Program.cs
var features = await session.QuerySourceFeaturesAsync(
"openmaptiles",
new SourceFeatureQueryOptions
{
SourceLayerIds = ["transportation"],
// A filter from the MapLibre style spec, as JSON.
Filter = """["==", ["get", "class"], "motorway"]""",
}
);
foreach (var feature in features)
{
UseFeature(feature.Feature);
}
main.go
source, err := session.QuerySourceFeatures(
"openmaptiles",
&maplibre.SourceFeatureQueryOptions{
SourceLayerIDs: []string{"transportation"},
// A filter from the MapLibre style spec, as JSON.
Filter: `["==", ["get", "class"], "motorway"]`,
},
)
if err != nil {
return err
}
features, err := source.Await(ctx)
if err != nil {
return err
}
for _, feature := range features {
useFeature(feature.Feature)
}
main.vala
var filter = new SourceFeatureQueryOptions () {
source_layer_ids = { "transportation" },
// A filter from the MapLibre style spec.
filter = "[\"==\", [\"get\", \"class\"], \"motorway\"]",
};
var features = yield session.query_source_features_async (
"openmaptiles", filter
);
foreach (var feature in features) {
use_feature (feature.get_feature ());
}
Main.java
List<SourceFeature> features =
session
.querySourceFeatures(
"openmaptiles",
SourceFeatureQueryOptions.of()
.withSourceLayerIds(List.of("transportation"))
// A filter from the MapLibre style spec.
.withFilter(
"""
["==", ["get", "class"], "motorway"]
"""))
.join();
for (SourceFeature feature : features) {
useFeature(feature.getFeature());
}
Main.kt
val features =
session
.querySourceFeatures(
"openmaptiles",
SourceFeatureQueryOptions(
sourceLayerIds = listOf("transportation"),
// A filter from the MapLibre style spec.
filter = """["==", ["get", "class"], "motorway"]""",
),
)
.await()
for (feature in features) {
useFeature(feature.feature)
}
main.py
features = session.query_source_features(
"openmaptiles",
mln.SourceFeatureQueryOptions(
source_layer_ids=["transportation"],
# A filter from the MapLibre style spec.
filter=["==", ["get", "class"], "motorway"],
),
).result()
for feature in features:
use_feature(feature.feature)
main.rs
let options = SourceFeatureQueryOptions {
source_layer_ids: Some(&["transportation"]),
// A filter from the MapLibre style spec, as JSON.
filter: r#"["==", ["get", "class"], "motorway"]"#,
};
let features = session
.query_source_features("openmaptiles", Some(&options))?
.await?;
for feature in features {
use_feature(&feature.feature);
}
main.swift
let features = try await session.querySourceFeatures(
"openmaptiles",
options: SourceFeatureQueryOptions(
sourceLayerIds: ["transportation"],
// A filter from the MapLibre style spec, as JSON.
filter: #"["==", ["get", "class"], "motorway"]"#
)
)
for feature in features {
useFeature(feature.feature)
}
main.ts
const features = await session.querySourceFeatures("openmaptiles", {
sourceLayerIds: ["transportation"],
// A filter from the MapLibre style spec.
filter: ["==", ["get", "class"], "motorway"],
});
for (const feature of features) {
useFeature(feature.feature);
}
main.zig
const source = try session.querySourceFeatures(
allocator,
"openmaptiles",
.{
.source_layer_ids = &.{"transportation"},
// A filter from the MapLibre style spec, as JSON.
.filter = "[\"==\", [\"get\", \"class\"], \"motorway\"]",
},
null,
);
var features = try source.await(null);
defer features.deinit();
for (features.value) |feature| {
useFeature(feature.feature);
}