# Spatial filters

Spatial filters allows you to filter [**CARTO charts and widgets**](/carto-for-developers/key-concepts/charts-and-widgets.md) by a geospatial polygon. This is typically used to synchronize widgets with a map (so that the widget is filtered as the user pans or zooms in) but it can also be used alongside other geospatial inputs, such as a draw a polygon mechanism.

## Adding a spatial filter

To filter a widget by a geospatial polygon, simply use the`spatialFilter` property. This property is available in all [models](/carto-for-developers/reference/carto-widgets-reference/models.md), and expects a valid GeoJSON polygon or multipolygon.

**Example**

```typescript
const categories = await dataSource.widgetSource.getCategories({
  column: 'group_name',
  operation: 'count',
  spatialFilter: polygon // GeoJSON.Polygon | GeoJSON.MultiPolygon; 
});
```

## Synchronizing widgets with a deck.gl map

The most common use case is to create a [CARTO + deck.gl](https://github.com/CartoDB/gitbook-documentation/blob/master/carto-for-developers/key-concepts/carto-for-deck.gl) map and then have the widgets be filtered. In this case, the question is: *how do I pass the current viewport to the widgets?*

1. **Use the onViewStateChange property in deck.gl to obtain the latest viewState**

Your code probably already looks similar to this, where you're using `map.jumpTo` to sync layers with the `viewState` in deck.gl. In any case, you want to do the same for your widgets. Our recommendation is to send that viewState to a function that will create the `spatialFilter` out of the `viewState`.

```typescript
import {Deck} from '@deck.gl/core';

deck.setProps({
  onViewStateChange: ({viewState}) => {
    const {longitude, latitude, ...rest} = viewState;
    map.jumpTo({center: [longitude, latitude], ...rest});
    debouncedUpdateSpatialFilter(viewState);
  }
});
```

{% hint style="danger" %}
We strongly recommend **debouncing** all actions downstream, at least for 100ms. From updating the spatialFilter to rendering new widgets, it all should probably wait until the user has finished interacting with the map. This will improve the performance of your application and reduce the amount of queries to your data warehouse.
{% endhint %}

2. **Create a function that turns a viewState into a spatialFilter, using createViewportSpatialFilter**

Turning a deck.gl `viewState` into a valid GeoJSON polygon is not a trivial task. It involves unprojecting the viewState and making sure the resulting polygon is a valid GeoJSON, even when crossing the antimeridian for low zoom levels.

That's why we provide the [createViewportSpatialFilter](#createviewportspatialfilter), where all those operations are already available and ready to use.

```typescript
import {WebMercatorViewport, Deck} from '@deck.gl/core';
import {createViewportSpatialFilter} from '@carto/api-client';
// import {debounce} from './utils'; — Your favorite debounce method

let viewportSpatialFilter;

const debouncedUpdateSpatialFilter = debounce(viewState => {
  const viewport = new WebMercatorViewport(viewState);
  viewportSpatialFilter = createViewportSpatialFilter(viewport.getBounds());
  renderWidgets();
}, 300);
```

3. **Use your updated spatial filter in your CARTO widgets**

```typescript
// import and set up dataSource...

const categories = await dataSource.widgetSource.getCategories({
    column: 'group_name',
    operation: 'count',
    spatialFilter: viewportSpatialFilter // this is now in-sync with the map!
});
```

### createViewportSpatialFilter

Returns a valid `SpatialFilter` for a given viewport, typically obtained from deck.gl's `viewport.getBounds()` method (\[west, south, east, north]).

* If the viewport covers the entire world (to some margin of error in Web Mercator space), `undefined` is returned instead.
* If the viewport extends beyond longitude range \[-180, +180], the polygon may be reformatted for compatibility with CARTO APIs.

**Usage**

```typescript
import {createViewportSpatialFilter} from '@carto/api-client';

createViewportSpatialFilter(viewport.getBounds());
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.carto.com/carto-for-developers/reference/filters/spatial-filters.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
