Repository navigation
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 204 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
4aaf255 to
7a4e43f
Compare
7a4e43f to
3ef7c11
Compare
|
@Nayor master moved a lot since this branch started: Python 3.15, SQLAlchemy 2.1, geoalchemy2 0.20, stricter ruff. The test classes are also no longer To save you the merge, #2400 targets this branch. It merges master in, resolves the conflicts, converts the tests to plain For new code on this branch: plain |
|
LGTM. @lbesson can you merge your merge? 😋 |
|
Hello, |
7a2f89e to
16ae5f2
Compare
|
Thanks for this, the two-stage design (ES filters → PostGIS geometry check) is sound. Some AI-suggested improvements to address, roughly by priority. 1. Reduce the tracks payload (main issue)
At the 100-track limit this is 6+ MB per pan, per overlay. Real GPX tracks recorded every second are often longer than 1,500 points. Please do it in SQL and skip the Python conversion: tol = (xmax - xmin) / 1500 # ~1 px for a typical viewport; or take `zoom` as a param
geojson = func.ST_AsGeoJSON(
func.ST_Simplify(func.ST_Force2D(DocumentGeometry.geom_detail), tol), 0
)2. Candidate selection in ES: false "truncated" answers and missed tracks
Suggested fix: index the track envelope in ES (4 float fields with range queries, or a 3. Heatmap cell size is controlled by the clientThe comment says 4. Smaller points
FYI: indexing is fineThe existing GIST index |
Two new endpoints back the outings search map's density feature: GET /outings/map/heatmap aggregates outings into a PostGIS grid (ST_SnapToGrid) for low/medium zoom levels, and GET /outings/map/tracks returns individual outing tracks for a bbox once zoomed in enough for the result count to stay bounded. Both query PostGIS directly (not Elasticsearch) since geom_detail tracks only exist there, and reuse the existing dogpile/Redis caching idiom. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The views become plain functions called through web.handlers, the routes are declared in web/document_map.py and listed in routes.txt, and the tests use the client and session fixtures. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…first The tracks come with the activities, the dates of an outing and the ratings that are set, as the listings have them, and the outings are sorted from the most recent to the oldest. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The cells of the grid were up to 25 pixels wide on the screen, which made blobs once blurred: they are now a few pixels wide, the answer has whole meters and at most 5000 cells. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With the variable set to false, 0, no or off in the environment of the container, the four map endpoints answer 503 before touching the database or the search index. For the servers, if the overlays load them too much. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…a heatmap grid from the box Review of the first version: - the tracks that cross the bbox are looked for first, in the database (its GIST index): exactly, by their track and not by their point, without the documents that have none, and without a box four times the screen. The search index then only filters their ids, and is not asked at all when there are no filters; - the track is simplified in SQL (about a pixel of the map, from the size of the bbox), in 2D, written as GeoJSON by the database with the decimals it needs: much lighter answers, and no conversion in Python. The track in the database is not changed; - the size of the heatmap cells comes from the bbox, not from the client (the zoom parameter is no longer needed, it is ignored); - the bbox is cut to the world of the projection; - the titles are read without the other texts of the locales. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
16ae5f2 to
bc96683
Compare
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…have Above this number the tracks endpoints answer "too many" and the map shows the density. 100 by default, 500 at most: once the answers of a real server say how many a view can afford, it is changed in the environment of the container, without a new release. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Validate ids, ignore sort/offset of the listing in the heatmap, keep tracks that collapse when simplified, accept a bbox with a 0 coordinate.
|
Thanks for the rework. The PG-first approach does fix both earlier issues (long tracks with a far midpoint, trackless outings filling the cap). Two concerns that follow from it: 1. The "too many" path is slow
That slow path is the common one in busy areas, and all it returns is Suggested fixes:
2. Selective filters are truncated because of unfiltered densityThe 5000 cap applies before the ES filters. So on a route page ( Suggestion: when filters are given, first ask ES for the matching ids with the filters only (no bbox). Selective filters like def _matching_ids(doc_type, params, cap):
"""The ids matching the filters (anywhere), or None if there are more than `cap`."""
search = build_query(params, {'limit': cap, 'offset': 0}, doc_type).sort()
response = search.execute()
if response.hits.total > cap:
return None
return [int(doc.meta.id) for doc in response]If it returns a list, pass it to |
…en they are selective The exact ST_Intersects on every candidate made the dense views cost 300-400 ms to only answer 'truncated'. The index alone (&&) finds the candidates, the exact test only runs on the few tracks that are sent. Without filters, only limit + 1 ids are needed. With filters that match few documents (a route, a user), the search index is asked first so that the number of tracks in the view does not matter. Also describe the four routes in the OpenAPI document.
|
Both points are fixed, and the tests cover each new path. One remaining thing: with a broad filter (e.g. def _matching_ids(doc_type, params, cap):
search = build_query(params, {'limit': 0, 'offset': 0}, doc_type).sort()
if search.execute().hits.total > cap:
return None
return [int(doc.meta.id) for doc in search.extra(size=cap).execute()]Nits:
|
Summary
Four endpoints feeding the "Outings" and "Routes" map overlays of c2corg/c2c_ui#4574:
GET /outings/map/heatmapandGET /routes/map/heatmap: density buckets ({x, y, count}, EPSG:3857) of a geohash grid aggregation on the search index. The size of the cells comes from thebboxon the server (a few pixels once on the screen), never from the client: a fine grid cannot be asked over a large area. The oldzoomparameter is no longer needed (it is ignored).GET /outings/map/tracksandGET /routes/map/tracks: the tracks crossing thebbox, with a title in the preferred language and what tells them apart (activities, dates of an outing, the ratings that are set). With more than 100 tracks the answer is empty withtruncated: true, and the UI asks the heatmap of the same box instead.act,date,r,w,a,u, ...), described in the OpenAPI document like the listing's, so the overlays can match a search made from the listing. They also acceptids=1,2,3to restrict the answer to given documents (an empty list matches nothing).bboxis cut to the world of the projection.TRACKS_OVERLAY_ENABLEDin the environment of the container: set tofalse,0,nooroff, the four endpoints answer503before touching the database or the search index (read at each request). The UI reads the same variable, and turns the overlays off when it gets a 503.How the tracks are found and sent
Following the review of the first version:
idsfilter plus the filters of the request, unsorted), and is not asked at all when there are no filters (the Routes overlay of a route page, for instance). Up to 5000 ids are looked at.ST_Simplify(about one pixel of the map, from the size of the box), 2D,ST_AsGeoJSONwith the decimals the tolerance needs, no conversion in Python. The track in the database is not changed, and the document pages still give it as it is (the UI downloads that one for the GPX). On the development database, 44 tracks of a zone went from about 480 KB (3D, 15 digits) to 190 KB.I chose this over indexing the envelope of the tracks in the search index: it has the same benefits (exact candidates,
has_track) without changing the mapping and reindexing, which is a heavy operation on the current search index.Not done: snapping the box to a tile grid so that a CDN can cache the answers.
Related: c2corg/c2c_ui#4573 (frontend counterpart and community context).
Test plan
c2corg_api/tests/views/test_document_map.pyand the wholec2corg_api/tests/contractfolder pass in docker (330 tests): validation, heatmap counts and filters, the cells following the size of the box, the box cut to the world, tracks found by their track (not their point), only the documents with a track counted, no search without filters, simplification for the size of the box and 2D output, truncation,ids, ratings and dates, the503when turned off (with contract snapshots).validation.pygained a validator.EXPLAIN (ANALYZE, BUFFERS)), as suggested in the review, would still be worth it.🤖 Generated with Claude Code