Skip to content

Optimize municipality matching and GPX parsing for performance - #1

Merged
raphibolliger merged 3 commits into
mainfrom
perf/processing-spatial-index
Jul 13, 2026
Merged

Optimize municipality matching and GPX parsing for performance#1
raphibolliger merged 3 commits into
mainfrom
perf/processing-spatial-index

Conversation

@raphibolliger

Copy link
Copy Markdown
Owner

This pull request introduces significant performance and code quality improvements to the GPX processing pipeline and municipality matching logic, as well as updates to the frontend data sources. The main changes include a complete refactor of the GPX file reading logic for efficiency, the introduction of a spatial index to accelerate point-in-polygon queries, and frontend asset updates.

Backend: GPX Processing and Municipality Matching

Performance and Efficiency Improvements:

  • Refactored GpxFilesReader to use parallel processing for reading GPX files, replaced asynchronous code with parallel synchronous code, and switched from XML LINQ to a fast, streaming XML parser for GPX parsing. This greatly improves speed and reduces memory usage. (GeoQuest25.Processing/GeoQuest25.Processing/GpxFilesReader.cs) [1] [2]
  • Replaced the previous brute-force approach for checking if a GPX track visits a municipality with a spatial index (STRtree) and prepared geometries, drastically reducing the number of expensive geometry operations. (GeoQuest25.Processing/GeoQuest25.Processing/Program.cs)

Algorithmic and Structural Changes:

  • Municipality matching now sorts GPX files by date to ensure the earliest visit is recorded, and uses a new helper MunicipalityEntry to bundle geometry data for efficient lookup and locking. (GeoQuest25.Processing/GeoQuest25.Processing/Program.cs) [1] [2] [3]
  • The logic for handling special-case municipalities (shared areas) is preserved but now safely checks for removals before adding. (GeoQuest25.Processing/GeoQuest25.Processing/Program.cs)

Code Cleanup:

  • Removed unused or obsolete code, such as the old IsMunicipalityVisited function and unnecessary concurrent collections. (GeoQuest25.Processing/GeoQuest25.Processing/Program.cs) [1] [2]

Frontend: Data Source Updates

Data File Updates:

  • Updated the file paths for visitedData and todoData resources in AppComponent to point to new GeoJSON assets, ensuring the UI reflects the latest processed data. (GeoQuest25.Frontend/src/app/app.component.ts)

raphibolliger and others added 3 commits July 14, 2026 01:00
…arsing

- Invert the matching loop: instead of scanning all GPX points per
  municipality, query an STRtree once per GPX file and test each point
  only against the unvisited candidate municipalities (envelope check
  first, then prepared-geometry contains).
- Prepare municipality geometries once (PreparedGeometryFactory) so
  point-in-polygon tests use an internal index instead of rebuilding
  the topology graph per call.
- Parse GPX files with a streaming XmlReader instead of XDocument and
  dispose the readers; parallelism moved from per-trackpoint to
  per-file with bounded concurrency.
- Sort done activities by date so FirstVisit is deterministically the
  earliest visit (previously it depended on file enumeration order).
- List the GPX directory once instead of once per activity type.

Full pipeline run: 102s -> 9s on the same inputs; visited/todo/planned
results are identical to the previous implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same visited/todo/planned sets as before; 376 municipalities now carry
an earlier firstVisit date because activities are matched oldest first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 13, 2026 23:05
@raphibolliger
raphibolliger merged commit bdf2e49 into main Jul 13, 2026
1 check passed
@raphibolliger
raphibolliger deleted the perf/processing-spatial-index branch July 13, 2026 23:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the GPX processing pipeline and municipality matching to improve performance, primarily by switching to parallel GPX reading, using streaming XML parsing, and introducing a spatial index + prepared geometries to reduce point-in-polygon checks. It also updates the frontend to reference newly generated GeoJSON assets.

Changes:

  • Reworked municipality matching to use prepared geometries and an STRtree spatial index, and to record earliest visits by sorting GPX files by date.
  • Refactored GPX parsing to a streaming XmlReader approach and parallelized GPX file loading.
  • Updated Angular asset paths for the visited/todo GeoJSON resources.

Reviewed changes

Copilot reviewed 3 out of 6 changed files in this pull request and generated 3 comments.

File Description
GeoQuest25.Processing/GeoQuest25.Processing/Program.cs Adds spatial indexing + prepared-geometry matching and updates visited/todo generation flow.
GeoQuest25.Processing/GeoQuest25.Processing/GpxFilesReader.cs Switches to parallel GPX file reading and streaming XML parsing for trackpoints and dates.
GeoQuest25.Frontend/src/app/app.component.ts Updates visited/todo GeoJSON asset filenames referenced by the UI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +64 to +68
else if (date is null && firstTrkptSeen && reader.LocalName == "time")
{
// the <time> of the first trkpt determines the activity date
date = DateOnly.FromDateTime(DateTime.Parse(reader.ReadElementContentAsString()));
}
Comment on lines +164 to +168
var trackEnvelope = new Envelope();
foreach (var point in gpxFile.Points)
trackEnvelope.ExpandToInclude(point.Coordinate);

return index.Query(trackEnvelope).Where(c => c.Municipality.FirstVisit is null).ToArray();
Comment on lines +83 to +86
if (candidate.Municipality.IsPlanned) continue;
if (!candidate.Envelope.Contains(point.Coordinate)) continue;
if (candidate.Prepared.Contains(point))
candidate.Municipality.IsPlanned = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants