Repository navigation
feat(geo): vector tiles of features for maps - #470
Merged
Merged
Conversation
GET /v1/geo/tiles/{z}/{x}/{y}.mvt serves GeoJSONFeatures as Mapbox Vector Tiles built by
PostGIS (ST_AsMVT), so maps can draw every feature instead of the 100 per page of the
geoFeatures connection. The package's first REST surface, wired through
GeoPlugin.v1_urlpatterns.
- From zoom 13 up, a `features` layer with every feature (points and polygons) and `id` /
`feature_type` attributes. Below it, a `clusters` layer: a 16x16 grid per tile, one point
per non-empty cell with a `point_count`; half-open tile bounds count each feature once.
- Same query parameters as geoFeatures (GeoJSONFeatureFilter). The filters' django
ValidationErrors, raised while the queryset is evaluated, return 400, not 500.
- Access through has_perm("view_geojsonfeature") (rest_framework/permissions.py), so an
overridden backend applies to tiles too. 204 for empty tiles, Cache-Control max-age=300.
- Geography prefilter on the padded tile envelope (GiST index) from zoom 4, then an exact
test in EPSG:3857; huge low-zoom tiles skip the prefilter (great-circle edges).
- Extension points: get_queryset, filter_queryset, get_feature_properties (ORM expressions
as MVT attributes), cluster_max_zoom, cluster_grid_size. baseapp.geo.tiles exposes the
builders for custom endpoints.
- Tests decode tiles with a small protobuf-free MVT decoder: mapbox-vector-tile would
downgrade protobuf (it requires < 7) for every consumer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019P1oyApi4BLS8iLfMEGm8b
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
nossila
added a commit
to queplanta/baseapp-backend
that referenced
this pull request
Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019P1oyApi4BLS8iLfMEGm8b
Review of #470: - `id` is the feature's DocumentId public_id (as text): the ID the GraphQL API and the target_object_id filter use, and sequential pks would let anyone count features. The `document` join is one row per feature (DocumentId is unique per content type and object); a test pins both. - Drop the unused logger from the tile view. - README: document that zooms 0-3 skip the spatial index and scan the filtered table. - Test decoder: decode uint64 values as unsigned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019P1oyApi4BLS8iLfMEGm8b
nossila
added a commit
to queplanta/baseapp-backend
that referenced
this pull request
Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019P1oyApi4BLS8iLfMEGm8b
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Vector tiles for
baseapp.geo, to fold into #445. Maps can now draw every feature at every zoom; thegeoFeaturesconnection returns at most 100 per page.GET /v1/geo/tiles/{z}/{x}/{y}.mvtservesGeoJSONFeatures as Mapbox Vector Tiles built by PostGIS (ST_TileEnvelope,ST_AsMVTGeom,ST_AsMVT). No new runtime dependency.baseapp/geo/rest_framework/(views.py,urls.py,permissions.py), wired throughGeoPlugin.v1_urlpatternsin the staticmethod form.Behavior
cluster_max_zoom(13): afeatureslayer with every feature in the tile. Points stay points and polygons stay polygons, clipped to the tile plus a 64px buffer. Attributes areidandfeature_type.clusterslayer. Each tile is split into acluster_grid_size² (16×16) grid; each non-empty cell becomes a point at the centroid of its features, withpoint_count.ST_PointOnSurface.geoFeatures, by reusingGeoJSONFeatureFilteras-is (feature_type,target_object_id,bbox,near). No new filter API.ValidationErrors while the queryset is evaluated, afteris_valid(). The view catches them and returns400with field errors instead of a500.test_filterset_raises_validation_error_outside_grapheneis untouched.CanViewGeoJSONFeaturescallsrequest.user.has_perm("<app>.view_geojsonfeature"), so a project that overridesGeoPermissionsBackendgets the same rule on tiles as on GraphQL.204for empty tiles and404for out-of-range tiles.Cache-Control: public, max-age=300, so a CDN can absorb map pans.SQL
ST_Transform(…, 3857).__intersectsprefilter runs on the tile envelope padded by 10%. It uses the GiST index, which I verified exists:sqlmigrate geo 0001createsgeo_geojsonfeature_geometry_…_id USING GIST ("geometry"). Then an exact planar test in EPSG:3857._mvt_property_N) and renamed in the outer query, since Django rejects annotations named like model fields (e.g.id).Extension points
get_queryset,filter_queryset,get_feature_properties(ORM expressions become MVT attributes).cluster_max_zoom,cluster_grid_size, layer names andcache_max_age.baseapp.geo.tiles.build_features_tile/build_clusters_tilebuild tiles from any queryset, for custom endpoints.Tests
baseapp/geo/tests/test_rest_tiles.py, 15 tests on real PostGIS, no mocks. They cover tile math, points and polygons, the properties hook, cluster aggregation, the polygon cluster, counts across a tile edge, 204/404, cache headers, filter pass-through,400for malformed filters, and permission viahas_perm.baseapp/geo/tests/mvt.py, a small protobuf-free MVT decoder.mapbox-vector-tilewas the first choice, but it requiresprotobuf<7. Locking it downgraded protobuf from 7.36 to 6.33 for every consumer of the library, which is too much for a test helper.Test plan
pytest baseapp/geo/tests/: 105 passed.baseapp/geocoverage is 99% (tiles.py100%,rest_framework/views.py95%).black,isort,flake8andast-grepare clean on the changed files.🤖 Generated with Claude Code
https://claude.ai/code/session_019P1oyApi4BLS8iLfMEGm8b