Skip to content

feat(KMLExport): implement KML export and metadata mapping for geo questions DEV-1592 DEV-1593#348

Merged
RuthTurk merged 3 commits into
mainfrom
dev-1592-add-kml-export-type-and-to_kml-generator
Jul 7, 2026
Merged

feat(KMLExport): implement KML export and metadata mapping for geo questions DEV-1592 DEV-1593#348
RuthTurk merged 3 commits into
mainfrom
dev-1592-add-kml-export-type-and-to_kml-generator

Conversation

@RuthTurk

@RuthTurk RuthTurk commented Jul 6, 2026

Copy link
Copy Markdown
Member

📣 Summary

Formpack now supports KML export for geo questions, with Google Earth compatible output.

📖 Description

Projects with geo questions can now be exported to KML from Formpack, including points, lines, and polygons in one export.
The generated KML uses a Google Earth-compatible namespace and displays each feature with a simple Survey Instance: <uuid> description to match KPI behavior. All GeoJSON feature properties are also preserved in KML ExtendedData for structured metadata access.

👀 Preview steps

  1. ℹ️ In the repo root, use the project virtualenv Python:
    python
  2. Run this in shell to generate KML via the implemented Export.to_kml() method:
python- <<'PY'
from pathlib import Path

from formpack import FormPack
from tests.fixtures.build_fixture import build_fixture

# Load fixture with geopoint + geotrace + geoshape
title, schemas, submissions = build_fixture('all_geo_types')

# Build exporter
fp = FormPack(schemas, title)
export = fp.export(versions=fp.versions.keys())

# Generate KML using the actual to_kml() implementation
kml_text = ''.join(export.to_kml(submissions, flatten=True))

out_file = Path('preview_to_kml_all_geo_types.kml')
out_file.write_text(kml_text, encoding='utf-8')
print(f'Wrote {out_file.resolve()}')
print(f'bytes={out_file.stat().st_size}')
PY
  1. 🟢 Confirm file exists: preview_to_kml_all_geo_types.kml
  2. Open the file in Google Earth
  3. 🟢 Confirm:
  • point, line, and polygon placemarks are present
  • description shows Survey Instance: <uuid>
  • point style uses red-circle icon
  • ExtendedData includes GeoJSON properties

@coveralls

coveralls commented Jul 6, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 87.548% (+0.7%) from 86.847% — dev-1592-add-kml-export-type-and-to_kml-generator into main

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

Adds first-class KML export support for geo questions by converting existing GeoJSON exports into Google Earth-compatible KML, enriching placemarks with survey instance descriptions and GeoJSON properties in ExtendedData.

Changes:

  • Implement Export.to_kml() using geojson2kml, including namespace normalization and post-processing of the produced KML.
  • Add KML placemark metadata mapping (description + ExtendedData) and point icon styling.
  • Add test coverage for KML output structure and metadata mapping; register kml as a valid export type and add geojson2kml dependency.

Reviewed changes

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

File Description
tests/test_exports.py Adds tests asserting KML geometry presence, description formatting, and ExtendedData property mapping.
src/formpack/reporting/export.py Implements KML export pipeline and KML XML post-processing (metadata + styling).
src/formpack/constants.py Adds EXPORT_TYPE_KML and includes it in VALID_EXPORT_TYPES.
setup.py Adds geojson2kml as an install requirement.

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

Comment thread src/formpack/reporting/export.py
Comment thread src/formpack/reporting/export.py Outdated
Comment thread src/formpack/reporting/export.py
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds KML export support for surveys with geo questions (geopoint, geotrace, geoshape), generating Google Earth-compatible output via the new to_kml() method on Export. The export pipeline converts GeoJSON to KML using the geojson2kml library, then post-processes the KML to add ExtendedData elements with survey metadata.

  • to_kml() normalizes the GeoJSON into a flat FeatureCollection, converts it via geojson2kml, patches the OGC namespace to Google Earth's KML 2.2 namespace, and injects ExtendedData and Survey Instance descriptions into each <Placemark>.
  • _collect_survey_instances() re-parses all submissions to build a parallel list of UUIDs (with fallback to meta/rootUuid / meta/instanceID) that maps to features; counts are verified to match before zipping with placemarks.
  • 15+ tests cover geo-type mapping, iterator inputs, ExtendedData structure, UUID fallback chains, and mismatch error handling.

Confidence Score: 5/5

Safe to merge. The KML export pipeline is logically correct, all submissions are properly materialized before the two-pass parse, count mismatches between placemarks and features raise a RuntimeError, and the test suite covers all geo types, fallback UUID chains, and error paths.

No functional defects were found in the changed code. The count-parity check (RuntimeError when placemark/feature/uuid lengths diverge) prevents silent data misalignment. All previously flagged issues from earlier review rounds have been addressed in the final commits. The two remaining observations (unused GX_NAMESPACE constant and the double-parse of submissions) are non-blocking quality notes that do not affect correctness.

No files require special attention, though export.py has the double-submission-parse design worth revisiting for large exports.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant Export
    participant toGeoJSON as to_geojson()
    participant collectUUIDs as _collect_survey_instances()
    participant normalize as _normalize_feature_collection()
    participant geojson2kml as convert_geojson_file()
    participant addMetadata as _add_extended_data_to_kml()

    Caller->>Export: to_kml(submissions, flatten)
    Export->>Export: list(submissions)
    Export->>toGeoJSON: to_geojson(submissions, flatten)
    toGeoJSON-->>Export: GeoJSON text (generator)
    Export->>Export: json.loads(geojson)
    Export->>collectUUIDs: _collect_survey_instances(submissions)
    note over collectUUIDs: self.reset() + re-parse submissions<br/>builds [uuid, uuid, ...]
    collectUUIDs-->>Export: survey_instances[]
    Export->>normalize: _normalize_feature_collection(geojson)
    normalize-->>Export: flat FeatureCollection dict
    Export->>Export: write normalized GeoJSON to tmp file
    Export->>geojson2kml: convert_geojson_file(geojson_path, tmp_dir)
    geojson2kml-->>Export: kml_path
    Export->>Export: "read KML text + regex-replace OGC->Google namespace"
    Export->>addMetadata: _add_extended_data_to_kml(kml_text, geojson, survey_instances)
    note over addMetadata: count-match check<br/>inject ExtendedData + description<br/>style icon, ET.tostring
    addMetadata-->>Export: patched KML string
    Export-->>Caller: yield kml_text
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant Export
    participant toGeoJSON as to_geojson()
    participant collectUUIDs as _collect_survey_instances()
    participant normalize as _normalize_feature_collection()
    participant geojson2kml as convert_geojson_file()
    participant addMetadata as _add_extended_data_to_kml()

    Caller->>Export: to_kml(submissions, flatten)
    Export->>Export: list(submissions)
    Export->>toGeoJSON: to_geojson(submissions, flatten)
    toGeoJSON-->>Export: GeoJSON text (generator)
    Export->>Export: json.loads(geojson)
    Export->>collectUUIDs: _collect_survey_instances(submissions)
    note over collectUUIDs: self.reset() + re-parse submissions<br/>builds [uuid, uuid, ...]
    collectUUIDs-->>Export: survey_instances[]
    Export->>normalize: _normalize_feature_collection(geojson)
    normalize-->>Export: flat FeatureCollection dict
    Export->>Export: write normalized GeoJSON to tmp file
    Export->>geojson2kml: convert_geojson_file(geojson_path, tmp_dir)
    geojson2kml-->>Export: kml_path
    Export->>Export: "read KML text + regex-replace OGC->Google namespace"
    Export->>addMetadata: _add_extended_data_to_kml(kml_text, geojson, survey_instances)
    note over addMetadata: count-match check<br/>inject ExtendedData + description<br/>style icon, ET.tostring
    addMetadata-->>Export: patched KML string
    Export-->>Caller: yield kml_text
Loading

Reviews (3): Last reviewed commit: "address ai reviewer comments" | Re-trigger Greptile

Comment thread src/formpack/reporting/export.py
Comment thread src/formpack/reporting/export.py Outdated
Comment thread src/formpack/reporting/export.py Outdated
Comment thread src/formpack/reporting/export.py

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

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

Comment thread src/formpack/reporting/export.py
Comment thread src/formpack/reporting/export.py
Comment thread src/formpack/reporting/export.py
Comment thread src/formpack/reporting/export.py
@RuthTurk RuthTurk requested a review from Guitlle July 6, 2026 23:42

@Guitlle Guitlle left a comment

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.

LGTM

@RuthTurk RuthTurk merged commit 3bf65b7 into main Jul 7, 2026
3 checks passed
@RuthTurk RuthTurk deleted the dev-1592-add-kml-export-type-and-to_kml-generator branch July 7, 2026 20:18
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.

4 participants