Skip to content

feat(map): QGIS import reads merged categories, dashes and QGIS 1/2 names, and notes what it drops - #876

Merged
deniial00 merged 1 commit into
opengeos:mainfrom
pick2510:feat/qgis-import-audit
Oct 25, 2026
Merged

deniial00 merged 1 commit into
opengeos:mainfrom
pick2510:feat/qgis-import-audit

Conversation

@pick2510

@pick2510 pick2510 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #825 and #844. I audited the QGIS importer against 149 QGIS files: projects and styles written by QGIS 4.2.3 that cover every symbol layer, renderer, label option, paint effect, provider, layout item and annotation item, the QGIS Training Manual's files (QGIS 1.8 to 3.10), and swisstopo's SMV25 project. The audit lists every setting in those files that the importer neither reads nor notes. This PR fixes what it read wrongly and notes what it dropped without a word.

Read wrongly before

  • Merged categories: QGIS 4 writes a category of several values as <val value="c"/><val value="d"/>. The importer looked only for a list <Option>, so it took the category as "all other values" and drew those features with the wrong symbol.
  • Files from QGIS 1 and 2:
    • Simple lines store their settings as color, width and penstyle, which weren't read, so those lines weren't drawn at all.
    • Fill outlines (color_border, width_border, style_border) were lost.
    • The scale range (minimumScale/maximumScale, swapped against today's names), the opacity (layerTransparency, transparencyLevelInt) and the project's coordinate system (in <mapcanvas><destinationsrs> or SpatialRefSys) weren't read.
    • The layer order wasn't read: these files have a <legend> but no layer tree. A symbol's outputUnit is now the unit for sizes that give none of their own.

Carried over now

  • Dashes: a simple line's custom dash pattern, in its unit (an odd pattern's trailing step of no length dropped, as QGIS draws it), and QGIS's named styles (dash, dot, dash dot, dash dot dot) as Qt's patterns in line widths. A dashed line is a stroke of its own, as an offset one is. Dashed polygon outlines are noted, since a fill's outline has no dash in RustGIS.

Noted now (the notes count each thing once per layer)

  • Project: print layouts, annotations, bookmarks, map themes, relations.
  • Layer: time settings, diagrams, its other styles, joins, virtual fields, a vector tile layer's style, labels in QGIS 2's format.
  • Symbols and renderers: draw effects (a shadow, glow or blur that's on, not only the plain drawing an effect stack starts with), order-by.
  • Labels: halos (RustGIS draws its own thin one), distances, offsets, repeats along lines, priorities, wrapping, callouts, data-defined settings.
  • Note text: a layer of no provider (QGIS's annotation layer) is noted by its kind. Before, the note read "RustGIS doesn't read QGIS's sources".

Results on the 149 files: none crashes. Files that come in without a note went from 41 to 22, and those 22 carry over in full. Settings dropped without a note that vary in the files went from 257 to 72, mostly small label options and project structure. SMV25's 157 dashed strokes now draw as QGIS draws them. Compared pixel by pixel with QGIS's own rendering, the share that differs went from 7.5% to 7.3% in a 1 m/px view of Winterthur's old town and from 11.7% to 11.6% in a 4 m/px overview.

Tests

  • merged_categories_and_dashes_carry_over
  • files_of_qgis_1_and_2_read_by_their_old_names
  • what_rustgis_has_no_place_for_is_noted
  • the engine's a_qgis_project_opens_as_a_map_and_a_qgis_style_styles_a_layer, which also checks the annotation layer's note

The QGIS section of docs/user-guide/workspace.md lists what's new.

Summary by CodeRabbit

  • Improvements
    • QGIS imports now better handle older project formats, layer visibility and ordering, opacity, and categorized styles with multiple values.
    • Supported line dash patterns and additional label and symbol settings are handled more accurately.
  • Import Notes
    • Import reports now identify unsupported layers and settings, and explain when features such as label callouts, drawing effects, or label styling are skipped or approximated.
  • Documentation
    • Updated the QGIS import guide with supported styling details and known limitations.

…ames, and notes what it drops

An audit of the importer against 149 QGIS files (QGIS 1.8 to 4.2) found settings it dropped
without a note, and some it read wrongly. This fixes those and adds the missing notes.

- Merged categories: QGIS 4 writes a category of several values as <val>s, which were missed,
  so the category was taken as "all other values".
- Dashes: custom dash patterns (in their unit) and QGIS's named pen styles (dash, dot, ...)
  become the stroke's dash pattern instead of a "drawn solid" note.
- Files from QGIS 1 and 2: layer order and visibility from <legend>, the project's coordinate
  system from the map canvas or SpatialRefSys, the scale range's old names, opacity as
  layerTransparency or transparencyLevelInt, the old names of simple line and fill settings
  (whose lines weren't drawn at all), and a symbol's outputUnit.
- New notes: print layouts, annotations, bookmarks, map themes and relations; a layer's time
  settings, diagrams, other styles, joins, virtual fields and vector tile style; draw effects,
  order-by, dashed polygon outlines; label halos, distances, offsets, repeats, priorities,
  wrapping, callouts, data-defined settings and QGIS 2's labels.
- A layer of no provider (QGIS's annotation layer) is noted by its kind.
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 7f18f79e-3ba9-40b6-8304-682b1e18f93d
Commits

Reviewing files that changed from the base of the PR and between 2aaa0f0 and 36c7bbf.

Files selected for processing: 8
  • crates/engine/src/cmd/qgis.rs
  • crates/engine/src/tests.rs
  • crates/map/src/qgis/labels.rs
  • crates/map/src/qgis/mod.rs
  • crates/map/src/qgis/renderer.rs
  • crates/map/src/qgis/symbol.rs
  • crates/map/src/qgis/tests.rs
  • docs/user-guide/workspace.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Walkthrough

QGIS import now reads additional legacy project settings and layer types, parses more renderer and symbol values, and records notes for unsupported or approximated settings. Tests cover these parsing paths, and the workspace guide describes the supported formats and import notes.

Changes: QGIS Import Compatibility

Layer / File(s) Summary
Project parsing and layer metadata
crates/map/src/qgis/mod.rs, crates/engine/src/cmd/qgis.rs, crates/engine/src/tests.rs, crates/map/src/qgis/tests.rs
Project reading retains layer types, supports legacy CRS, canvas, legend, opacity, and scale settings, and records notes for project and layer features. Providerless layers are identified by layer kind in import notes. Tests cover legacy project reading and annotation layers.
Renderer and symbol parsing
crates/map/src/qgis/renderer.rs, crates/map/src/qgis/symbol.rs, crates/map/src/qgis/tests.rs, docs/user-guide/workspace.md
Renderer parsing accepts category values from <val> elements. Symbol parsing normalizes legacy properties and handles supported dash patterns. Tests and documentation cover these parsing and rendering details.
Label settings and import notes
crates/map/src/qgis/labels.rs, crates/map/src/qgis/tests.rs, docs/user-guide/workspace.md
The label importer records approximations for selected label settings and skipped callouts and data-defined settings. Tests and documentation describe these import notes and other unsupported project and layer features.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Suggested reviewers: giswqs

Merge Risk: Minimal · up to 36c7b

The inspected QGIS import paths show no actionable issue blocking merge after normal checks.

Pre-merge checks: Passed 4 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Docstring Coverage Inconclusive Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 6 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the main QGIS importer changes, including merged categories, dash support, legacy names, and reporting unsupported settings.
Description check Passed The description provides a detailed summary, testing information, and a related issue reference. It does not use the template headings exactly, but it includes all required information and is complete…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides a detailed summary, testing information, and a related issue reference. It does not use the template headings exactly, but it includes all required information and is complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 6 files. (2 skipped: 1 unsupported, 1 too large.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

A rabbit checks the map at dawn,
And marks the layers one by one.
Dashes hop across the page,
Old legends find a newer stage.
“Skipped,” says the hare, then writes the note,
And bounds away with map in tow.

Comment @coderabbitai help to get the list of available commands.

}
let halo = child(text, "text-buffer").is_some_and(|b| b.attribute("bufferDraw") == Some("1"));
if halo {
report.approximated(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (low confidence): this note fires for every label with a buffer, even though a buffer is the case RustGIS does carry over as a halo. Most real projects will show it on nearly every labelled layer. Consider noting it only when the buffer's colour or size differs notably from what RustGIS draws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping it as is. The note is true for every halo: RustGIS draws its own halo (thin, white or dark by the text colour), so a QGIS buffer's colour and size are always dropped, not only when they differ a lot. Notes repeat per layer once, so a project with many labelled layers gets one line each, which is what the notes list is for. Guessing what counts as "close enough" would hide real differences.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

I read the full diff and the new tests. I could not build or run anything here, so the tests are unrun.

Bugs

  • Nothing definite found. I checked the following paths and they look sound:
    • the QGIS 1/2 legend walk (bounded depth and size);
    • the old-name settings mapping, including SYMBOL_UNIT;
    • the opacity and scale-limit fallbacks, which are clamped and finite-checked;
    • the dash parsing (bounded, with the odd trailing zero step dropped);
    • the merged-category <val> values.

Security

  • No issues. Input is parsed with roxmltree, and recursion and list sizes are bounded.

Performance

  • No issues. The extra per-layer scans are linear in the layer's XML.

Quality

  • Low confidence: the new "label halos … drawn as RustGIS's" note fires for every label that has a buffer. Most real projects would show it on nearly every labelled layer, which dilutes the notes list (inline comment posted).
  • Low confidence: layer_notes and project_notes each define a similar local count closure. This is a small duplication that a shared helper could remove.

AGENTS.md

  • No unwrap, expect or panic! in non-test code, and no unsafe. The new tests use unwrap and panic! only in test code.
  • Regression tests are added for the new behaviour. docs/user-guide/workspace.md is updated.
  • The new import notes are report.* strings, not UI text, so tl! does not apply.

Change size

Compared: the whole PR against main (merge base 2aaa0f0 to PR head 36c7bbf)

Metric Value
Files changed 8 (added 0, modified 8, removed 0, renamed 0)
Size before 1.04 MB
Size after 1.06 MB
Total size change +23.9 KB
File Before After Change
crates/map/src/qgis/tests.rs 15.6 KB 25.9 KB +10.3 KB
crates/map/src/qgis/mod.rs 18.0 KB 24.8 KB +6.8 KB
crates/map/src/qgis/symbol.rs 11.7 KB 15.3 KB +3.6 KB
crates/map/src/qgis/labels.rs 11.1 KB 12.9 KB +1.7 KB
crates/engine/src/tests.rs 958.7 KB 959.2 KB +473 B
docs/user-guide/workspace.md 14.5 KB 14.9 KB +402 B
crates/map/src/qgis/renderer.rs 14.5 KB 14.9 KB +358 B
crates/engine/src/cmd/qgis.rs 15.7 KB 15.9 KB +207 B

@github-actions

Copy link
Copy Markdown
Contributor

📖 Docs preview

Item Value
Preview https://preview.rustgis.app/pr-876/
Web app https://preview.rustgis.app/pr-876/app/
Commit 36c7bbf

GitHub Pages takes a minute or two to publish a new commit.

@deniial00

deniial00 commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

@pick2510 This is awesome work—thank you for such a thorough audit and for improving compatibility across QGIS versions!

Would you be able to share the QGIS projects you used for testing, along with any supporting data or download links? Having these available would help us reproduce the comparisons and catch regressions as the importer evolves. Even a representative subset would be really useful.

Could you also take a look at QGIS parity and share your thoughts on the remaining gaps and which ones we should prioritize?

@deniial00
deniial00 merged commit e24fbe9 into opengeos:main Oct 25, 2026
18 checks passed
@pick2510

Copy link
Copy Markdown
Contributor Author

@deniial00 Thanks! The corpus is up in #881 under crates/map/testdata/qgis/, along with a test that reads every file in it:

  • Generated (Apache-2.0): 90 styles and 21 projects that QGIS 4.2.3 wrote through its API. Each varies one thing, together covering every symbol layer, renderer, effect, label setting, raster renderer, provider, project structure, layout and annotation item. The PR includes their small sample data and the generator.
  • SMV25: swisstopo's QGIS project for Swiss Map Vector 25, included with attribution (swisstopo OGD). Its data is about 760 MB, so the README links swisstopo's download instead.
  • QGIS Training Manual: its files are GPL-2.0, so they're linked, not copied: qgis/QGIS-Training-Data exercise_data/. It has 36 files from QGIS 1.8 to 3.x.

On parity: the audit lists each setting these 149 files contain and whether the importer reads it, notes it, or ignores it as irrelevant. After #876 and #877, 72 settings that vary are still dropped without a note, mostly project-structure details. The bigger gaps are in what RustGIS can draw. Here they are in the order I'd take them, by how often real projects (SMV25 and the Training Manual) hit them:

  1. Map coordinate systems. 9 of the real projects use a coordinate system the map is drawn in Web Mercator instead of. This matters most for national grids such as LV95.
  2. Markers along lines and font markers. SMV25 uses them on 20 layers: railways, boundaries and point symbols drawn from the map font.
  3. Labels: halo colour and width (28 layers in 3 projects), backgrounds, letter spacing, offsets and wrapping.
  4. Data-defined properties and expressions: size, colour and rotation by field, and rule-based renderers beyond the SQL subset. This is the largest piece.
  5. Fills and symbols: line and point pattern fills, gradient fills, SVG markers and draw effects.
  6. Print layouts and sources: mapping QGIS layouts onto RustGIS layouts, and the remaining providers (SpatiaLite, SQL Server, WCS, ArcGIS, GPX, memory and virtual layers).
  7. Round trip: writing .qgs and .qml back, once the above settles.

The remaining open importer PR is #877 (rasters). After it, I'd start with 1 or 2: they make the biggest visible difference on real maps, and SMV25 makes it easy to measure. Its pixel difference from QGIS is now 7.2% (overview 11.0%).

@deniial00

Copy link
Copy Markdown
Member

@pick2510 Thanks for sharing the corpus in #881 and linking the supporting data and Training Manual files! Having the fixtures and generator available will really help with reproducing the comparisons and catching regressions.

Your plan and prioritization sound good to me. For 1 (map coordinate systems), please also see #769 and the discussion about non-Web-Mercator tile sources and reprojection. It doesn’t cover the whole map-CRS problem, but it’s related and worth considering when defining the scope.

Since these are substantial changes, please open separate proposal issues for the pieces you’d like to tackle before starting much implementation, or add proposals to existing issues where appropriate. A short outline of the scope, approach, affected crates and commands, and testing plan would help us agree on the direction and coordinate the work.

@pick2510

Copy link
Copy Markdown
Contributor Author

@deniial00 Thanks, will do: a proposal issue per piece before implementing.

A correction to point 1 in my last comment. I looked at the nine projects again. All of them use EPSG:4326, not a national grid: map.projection refuses a coordinate system in degrees ("pick a projected coordinate system"), and the importer then falls back to Web Mercator. Projected systems such as LV95 (SMV25) already open in their own projection. So the gap is small and only on the import side: QGIS draws EPSG:4326 as plate carrée (longitude and latitude as x and y), which RustGIS's Equirectangular projection already draws the same way. The importer can map a geographic coordinate system to it instead of falling back to Web Mercator.

#769 (tile matrix sets other than Web Mercator) is a separate, larger piece: it doesn't block opening QGIS projects, only WMTS and tile sources in such projects. I'll mention it in the proposal where it applies rather than fold it into this.

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