Filter Features Within a Layer in Flutter
This tutorial shows how to filter features within a single GeoJSON layer using expressions — showing or hiding features based on their properties without removing and re-adding the layer.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Filter by Property Value
Show only features matching a specific property value using layer filters:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class FilterWithinLayerScreen extends StatefulWidget {
@override
_FilterWithinLayerScreenState createState() =>
_FilterWithinLayerScreenState();
}
class _FilterWithinLayerScreenState extends State<FilterWithinLayerScreen> {
MapMetricsController? mapController;
String selectedType = 'all';
final List<String> filterOptions = ['all', 'capital', 'city', 'town'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Filter Within Layer')),
body: Column(
children: [
// Filter chips
Container(
padding: EdgeInsets.all(12),
child: Wrap(
spacing: 8,
children: filterOptions.map((option) {
return ChoiceChip(
label: Text(option == 'all'
? 'Show All'
: option[0].toUpperCase() + option.substring(1)),
selected: selectedType == option,
onSelected: (selected) {
if (selected) {
setState(() => selectedType = option);
_applyFilter();
}
},
);
}).toList(),
),
),
Expanded(
child: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.0, 8.0),
zoom: 4.0,
),
onStyleLoaded: () {
_addCitiesLayer();
},
),
),
],
),
);
}
void _addCitiesLayer() {
final geoJson = {
'type': 'FeatureCollection',
'features': [
{'type': 'Feature', 'properties': {'name': 'Paris', 'type': 'capital', 'pop': 2161}, 'geometry': {'type': 'Point', 'coordinates': [2.3522, 48.8566]}},
{'type': 'Feature', 'properties': {'name': 'London', 'type': 'capital', 'pop': 8982}, 'geometry': {'type': 'Point', 'coordinates': [-0.1276, 51.5074]}},
{'type': 'Feature', 'properties': {'name': 'Berlin', 'type': 'capital', 'pop': 3645}, 'geometry': {'type': 'Point', 'coordinates': [13.405, 52.52]}},
{'type': 'Feature', 'properties': {'name': 'Rome', 'type': 'capital', 'pop': 2873}, 'geometry': {'type': 'Point', 'coordinates': [12.4964, 41.9028]}},
{'type': 'Feature', 'properties': {'name': 'Barcelona', 'type': 'city', 'pop': 1621}, 'geometry': {'type': 'Point', 'coordinates': [2.1734, 41.3851]}},
{'type': 'Feature', 'properties': {'name': 'Munich', 'type': 'city', 'pop': 1472}, 'geometry': {'type': 'Point', 'coordinates': [11.582, 48.1351]}},
{'type': 'Feature', 'properties': {'name': 'Milan', 'type': 'city', 'pop': 1352}, 'geometry': {'type': 'Point', 'coordinates': [9.19, 45.4642]}},
{'type': 'Feature', 'properties': {'name': 'Lyon', 'type': 'city', 'pop': 516}, 'geometry': {'type': 'Point', 'coordinates': [4.8357, 45.764]}},
{'type': 'Feature', 'properties': {'name': 'Bruges', 'type': 'town', 'pop': 118}, 'geometry': {'type': 'Point', 'coordinates': [3.2247, 51.2093]}},
{'type': 'Feature', 'properties': {'name': 'Salzburg', 'type': 'town', 'pop': 155}, 'geometry': {'type': 'Point', 'coordinates': [13.055, 47.8095]}},
{'type': 'Feature', 'properties': {'name': 'Siena', 'type': 'town', 'pop': 54}, 'geometry': {'type': 'Point', 'coordinates': [11.3308, 43.3188]}},
],
};
mapController?.addGeoJsonSource('cities', geoJson);
mapController?.addCircleLayer(
'cities-layer',
'cities',
circleRadius: 8.0,
circleColor: '#3b82f6',
circleStrokeColor: '#ffffff',
circleStrokeWidth: 2.0,
);
mapController?.addSymbolLayer(
'cities-labels',
'cities',
textField: '{name}',
textSize: 12.0,
textOffset: [0.0, 1.5],
textAnchor: 'top',
);
}
void _applyFilter() {
if (selectedType == 'all') {
// Remove filter — show all features
mapController?.setFilter('cities-layer', null);
mapController?.setFilter('cities-labels', null);
} else {
// Apply property filter
final filter = ['==', ['get', 'type'], selectedType];
mapController?.setFilter('cities-layer', filter);
mapController?.setFilter('cities-labels', filter);
}
}
}Filter by Numeric Range
Filter features by a numeric property like population:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class NumericFilterScreen extends StatefulWidget {
@override
_NumericFilterScreenState createState() => _NumericFilterScreenState();
}
class _NumericFilterScreenState extends State<NumericFilterScreen> {
MapMetricsController? mapController;
RangeValues populationRange = RangeValues(0, 10000);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Population Filter')),
body: Column(
children: [
// Range slider
Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text(
'Population: ${populationRange.start.toInt()}K - ${populationRange.end.toInt()}K',
style: TextStyle(fontWeight: FontWeight.bold),
),
RangeSlider(
values: populationRange,
min: 0,
max: 10000,
divisions: 100,
labels: RangeLabels(
'${populationRange.start.toInt()}K',
'${populationRange.end.toInt()}K',
),
onChanged: (values) {
setState(() => populationRange = values);
_applyPopulationFilter();
},
),
],
),
),
Expanded(
child: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.0, 8.0),
zoom: 4.0,
),
onStyleLoaded: () {
_addCitiesWithPopulation();
},
),
),
],
),
);
}
void _addCitiesWithPopulation() {
final geoJson = {
'type': 'FeatureCollection',
'features': [
{'type': 'Feature', 'properties': {'name': 'London', 'pop': 8982}, 'geometry': {'type': 'Point', 'coordinates': [-0.1276, 51.5074]}},
{'type': 'Feature', 'properties': {'name': 'Berlin', 'pop': 3645}, 'geometry': {'type': 'Point', 'coordinates': [13.405, 52.52]}},
{'type': 'Feature', 'properties': {'name': 'Madrid', 'pop': 3223}, 'geometry': {'type': 'Point', 'coordinates': [-3.7038, 40.4168]}},
{'type': 'Feature', 'properties': {'name': 'Rome', 'pop': 2873}, 'geometry': {'type': 'Point', 'coordinates': [12.4964, 41.9028]}},
{'type': 'Feature', 'properties': {'name': 'Paris', 'pop': 2161}, 'geometry': {'type': 'Point', 'coordinates': [2.3522, 48.8566]}},
{'type': 'Feature', 'properties': {'name': 'Vienna', 'pop': 1897}, 'geometry': {'type': 'Point', 'coordinates': [16.3738, 48.2082]}},
{'type': 'Feature', 'properties': {'name': 'Barcelona', 'pop': 1621}, 'geometry': {'type': 'Point', 'coordinates': [2.1734, 41.3851]}},
{'type': 'Feature', 'properties': {'name': 'Munich', 'pop': 1472}, 'geometry': {'type': 'Point', 'coordinates': [11.582, 48.1351]}},
{'type': 'Feature', 'properties': {'name': 'Amsterdam', 'pop': 873}, 'geometry': {'type': 'Point', 'coordinates': [4.9041, 52.3676]}},
{'type': 'Feature', 'properties': {'name': 'Bruges', 'pop': 118}, 'geometry': {'type': 'Point', 'coordinates': [3.2247, 51.2093]}},
],
};
mapController?.addGeoJsonSource('pop-cities', geoJson);
mapController?.addCircleLayer(
'pop-cities-layer',
'pop-cities',
circleRadius: 8.0,
circleColor: '#ef4444',
circleStrokeColor: '#ffffff',
circleStrokeWidth: 2.0,
);
mapController?.addSymbolLayer(
'pop-cities-labels',
'pop-cities',
textField: '{name}',
textSize: 11.0,
textOffset: [0.0, 1.5],
textAnchor: 'top',
);
}
void _applyPopulationFilter() {
final filter = [
'all',
['>=', ['get', 'pop'], populationRange.start.toInt()],
['<=', ['get', 'pop'], populationRange.end.toInt()],
];
mapController?.setFilter('pop-cities-layer', filter);
mapController?.setFilter('pop-cities-labels', filter);
}
}Common Filter Expressions
| Expression | Description |
|---|---|
['==', ['get', 'type'], 'capital'] | Equals |
['!=', ['get', 'type'], 'town'] | Not equals |
['>=', ['get', 'pop'], 1000] | Greater than or equal |
['in', 'capital', ['get', 'type']] | Value in list |
['all', filter1, filter2] | AND (both must match) |
['any', filter1, filter2] | OR (either matches) |
null | Remove filter (show all) |
Next Steps
- Filter by Toggle List — Category checkbox filters
- Filter by Text Input — Search-based filtering
- Draw GeoJSON Points — GeoJSON point rendering
Tip: Layer filters are more performant than removing/re-adding features because the data stays in memory and the map only needs to re-render — not re-parse — the GeoJSON.