Skip to content

Add kml-to-mif converter (KML/KMZ → MapInfo) - #2

Closed
fedyunin wants to merge 1 commit into
feat/converter-registryfrom
feat/kml-to-mif
Closed

Add kml-to-mif converter (KML/KMZ → MapInfo)#2
fedyunin wants to merge 1 commit into
feat/converter-registryfrom
feat/kml-to-mif

Conversation

@fedyunin

Copy link
Copy Markdown
Owner

Summary

Adds the second converter to the registry: KML/KMZ → MapInfo MIF/MID. Validates the Converter contract from #1 with a real second implementation. Produces byte-identical output to the reference Python script on a real 1854-feature dataset.

Stacked on top of #1 — review #1 first.

What it does

  • Colors preserved. Each Placemark's styleUrl is followed through StyleMap(normal) → Style. KML AABBGGRR is converted to MapInfo decimal RGB. Polygons emit matching Pen + Brush (pattern 1 when <fill>0</fill>, pattern 2 otherwise). Lines emit Pen with line width from <LineStyle><width>. Points emit Symbol with IconStyle color.
  • Labels and metadata preserved. Four columns: Name (from <name>), Description, resolved StyleId, and Folder (full slash-joined KML folder path — usable for MapInfo SQL filtering).
  • Folder hierarchy preserved. Default: nested directories on disk matching the KML Folder tree. With flat: true: one directory, parent folders joined as parent__child prefix to avoid TAB name collisions when merging.
  • Charsets. WindowsCyrillic (cp1251, default — works in any MapInfo Pro version), or Neutral (UTF-8, requires MapInfo Pro 15.2+).
  • KMZ supported via an in-process pure-JS ZIP reader (stored + deflate). No native deps.

New modules

  • src/core/common/color.js — KML AABBGGRR ↔ MapInfo int ↔ #RRGGBB
  • src/core/common/zip.js — minimal ZIP reader (zlib only, ~150 LOC)
  • src/core/converters/kml-to-mif/
    • parseKml.jsfast-xml-parser + Style/StyleMap resolution
    • writeMif.js — MIF/MID writer with CRLF normalization and Unicode-safe filename sanitization (preserves Cyrillic via \p{L})
    • index.js — Converter definition + run
  • test/fixtures/sample.kml — small synthetic file with polygons, line, point, and a StyleMap

New dependency: fast-xml-parser (zero runtime deps, MIT, widely used).

Tests

75 passing (62 from #1 + 13 new):

  • color (5): AABBGGRR / BBGGRR / fallback / hex round-trip
  • zip (3): deflate + stored extraction, rejects non-ZIP
  • kml-to-mif integration (5): schema shape, nested folder dispatch with verified Pen/Brush/Symbol values per feature, flat layout filename pattern, KMZ end-to-end via a generated in-memory archive, Neutral/UTF-8 charset path
npm test

Byte-for-byte parity check

node -e "/* run kml-to-mif on the real Karaganda KMZ */"
diff -r /Users/cranfan/work/kmz/mif_out/  /tmp/mifkit-parity/
# => no output (18 files all identical)

Test plan

  • npm test is green
  • byte-identical to the Python reference on 1854-feature Karaganda 108 KMZ
  • manual smoke: open one of the produced .mif files in MapInfo Pro and confirm colors match the source KML

Follow-ups

  • feat/dispatch-ui — switch the renderer to render its options panel from the converter schema, switch the worker to dispatch by converterId. Two converters now exist, so we can validate that the UI generalizes.
  • feat/clibin/mifkit convert kml-to-mif <input> <output> --flat.
  • feat/mif-to-kml — reverse direction (export MapInfo to Google Earth).

🤖 Generated with Claude Code

Adds the second converter through the registry contract introduced in PR1.
Produces byte-identical output to the reference Python script
(kml_to_mif.py) on a real 1854-feature dataset (Karaganda 108 KMZ).

What it does
- Resolves per-feature colors by following styleUrl -> StyleMap(normal)
  -> Style chains. KML AABBGGRR -> MapInfo decimal RGB int.
- Preserves <name> (label), <description>, and the resolved StyleId as
  MID attribute columns. Adds a Folder column with the full slash-joined
  KML folder path so the hierarchy is queryable in MapInfo SQL.
- Lays files out either as nested directories matching the KML Folder
  tree (default) or one flat directory with `parent__child` prefixes
  (`flat: true`) so they can be merged without TAB name collisions.
- Charset option: WindowsCyrillic (cp1251, default) for Russian/Kazakh
  data, or Neutral (UTF-8) for MapInfo Pro 15.2+.
- Supports KML and KMZ. KMZ is unzipped in-process via a minimal pure-JS
  ZIP reader (stored + deflate) — no native deps.
- Skips placemarks without geometry; reports per-file errors in stats.

New modules
- src/core/common/color.js       KML AABBGGRR <-> MapInfo int <-> #RRGGBB
- src/core/common/zip.js         minimal ZIP reader (zlib only)
- src/core/converters/kml-to-mif/
    parseKml.js                  KML parsing (fast-xml-parser) +
                                 Style/StyleMap collection + resolution
    writeMif.js                  MIF/MID writer with CRLF normalization
                                 and Unicode-safe filename sanitization
    index.js                     Converter definition + run()
- test/fixtures/sample.kml       polygons + line + point + StyleMap

New dependency: fast-xml-parser (zero runtime deps, MIT, popular)

Tests: 75 passing (62 from PR1 + 13 new)
- color: AABBGGRR / BBGGRR / fallback / hex round-trip
- zip: deflate + stored extraction, rejects non-ZIP buffers
- kml-to-mif: schema, full folder dispatch, flat layout, KMZ archive,
  Neutral charset, resolved Pen/Brush/Symbol values per feature

Verified end-to-end: `diff -r` of the original Python output against
the JS output on the 1854-feature Karaganda 108 KMZ shows no
differences across all 18 produced files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant