Convert KML to GeoJSON in your browser or NodeJS app

Convert KML to GeoJSON in your browser or NodeJS app

ยท

6 min read

Hello good people! In this short article, we're going to convert KML files to the GeoJSON format. The main takeaway aside from discovering something new is that I'll tell you about the popular solution, and then I'll show you the new solution I had to use due to some constraints.

What are KML and GeoJSON? ๐Ÿค”

KML (Keyhole Markup Language) is a specific XML format used for geographic annotation and representation. You can learn more about it in this Wikipedia article. What you need to know is that KML is commonly used in a lot of applications tied with geography and GPS tracking for example.

GeoJSON (Geographic JavaScript Object Notation) is an open standard also used for geographic representations but is more simple in its approach. Learn more about it over here in this Wikipedia article.

Both formats have pros and cons, but we're not here to discuss this. We want to convert a KML file to GeoJSON. GeoJSON is easy to display on a map for example geojson.io or directly on your web app that uses Mapbox, Leaflet or others.

How to convert a KML to a GeoJSON? โ™ป๏ธ

Let's suppose you have this KML file that represents the future metro line 15 in the Paris region in France (source). We can easily display this KML file using Google's My Maps app. Let's quickly test out the KML file in My Maps:

  • Open My Maps

  • Click the button "Create a new map" and you'll get redirected to a map interface

  • Once on the map, locate the left toolbox and click the "Import" button

  • In the new modal that will display, you can drag and drop your KML file or simply browse it

  • After it's uploaded, you'll see the metro line represented in red in the south of Paris

Below is a GIF of the whole process:

Now that we're sure our file is working as expected, let's try to parse it and convert it to GeoJSON. Here is a StackBlitz environment with everything already prepared, just head to the file index.ts. Let's go through the code:

import * as toGeoJson from '@mapbox/togeojson';
import './style.css';

// Handle the dropfile event
function onDrop(event: DragEvent) {
  event.preventDefault();
  const reader = new FileReader();
  const selectedFile = event.dataTransfer.files[0];
  if (selectedFile) {
    reader.onload = (ev) => {
      handleDroppedFileToMap(reader.result as string);
    };
    reader.readAsText(selectedFile, 'text/xml');
  }
}

// Suppress the default browser behavior when dragging a file in the dropzone
function dragOverHandler(ev) {
  ev.preventDefault();
}

// Convert the dragged kml file
function handleDroppedFileToMap(fileResult: string) {
  const geojsonFile = toGeoJson.kml(
    new DOMParser().parseFromString(fileResult, 'text/xml')
  );
  console.log(geojsonFile);
  // Display our dragged file in a fancy way
  const appDiv: HTMLElement = document.getElementById('app');
  appDiv.innerHTML = `<pre><code class="geojson">${JSON.stringify(
    geojsonFile,
    null,
    4
  )}</code></pre>`;
}

// Get our dropzone
const dropzone = document.querySelector('.dropzone');
// Bind the necessary event listeners to our dropzone
dropzone.addEventListener('drop', onDrop);
dropzone.addEventListener('dragover', dragOverHandler);

First, we're importing @mapbox/togeojson as a dependency. We can install it simply with npm install @mapbox/togeojson in our project.

Next, we're defining our onDrop(event: DragEvent) function. It will handle when the user drops a file. The function initializes a FileReader and gets the selected file from the drag event. Note we're assuming that the user is dropping a single file. We then prepare our FileReader instance to call a function named handleDroppedFileToMap() once the file is loaded. And we finally start reading the file as text with reader.readAsText(selectedFile, 'text/xml'); .

Next dragOverHandler, as you can see by the comment I left, this function will just suppress the browser's default behaviour where it will attempt to open your file in a new tab.

Now let's examine handleDroppedFileToMap(fileResult: string), this function takes our file in the form of a string and then passes it to this funky line of code:

const geojsonFile = toGeoJson.kml(
  new DOMParser().parseFromString(fileResult, 'text/xml')
);

We're doing 3 things here:

  1. First, we create a new DOMParser instance that will be used and then disposed of. DOMParser will help us parse our string to an XML file via the browser, this way we avoid validating and doing a lot of heavy lifting. As you can see parseFromString() takes our file in the form of a string as well as 'text/xml' which will help tell it we're trying to parse an XML file (our KML is just an XML file remember?).

  2. Then, we pass all what we did with the DOMParser to the toGeoJson.kml() function which will convert our KML to a GeoJSON object.

  3. Finally, we store our result in the constant geojsonFile, easy!

The final two instructions of our function are to log our GeoJSON file to view it in the console and insert it in our HTML document using a simpler innerHTML.

Finally, the last 3 lines of code are in order:

  1. Get the HTML element of the dropzone using document.querySelector()

  2. Bind the drop event listener to the function onDrop()

  3. Bind the dragover event listener to the function dragOverHandler()

That's all we need! Now here is a GIF of what happens when I drop the file in the dropzone.

Now it's all good, there is only one small problem; you should not use @mapbox/togeojson ! Let me explain.

Why you should not use @mapbox/togeojson ๐Ÿ›‘

Let's be clear, I'm a huge fan of Mapbox as a map solution and map provider. My issue is that the @mapbox/togeojson package has not been maintained since January 2018! Since we're using this library in our client's app and their security department takes things too seriously, we need to keep all our packages updated. togeojson in this case uses minimist@1.2.0 which suffers from a security exploit. And believe me, nothing is scarier for the cybersecurity department of a big corporate than an npm security vulnerability ๐Ÿ˜… .

Anyway, we need an alternative. And thanks to the amazing Github community, we have a great alternative called @tmcw/togeojson. Honestly, I'm surprised that I didn't know about it sooner. It features:

  • No dependency!

  • Extremely tiny at 299KB

  • Tested

  • Works on NodeJS and the browser

  • Has built-in TypeScript typings!

  • Bonus: Can also convert TCX and GPX files

Now, let's quickly change our code to use this package, head back to index.ts and do the following:

// Replace this at line 1
import * as toGeoJson from '@mapbox/togeojson';
// With this
import { kml } from "@tmcw/togeojson";

// Replace line 24
const geojsonFile = toGeoJson.kml(
    new DOMParser().parseFromString(fileResult, 'text/xml')
  );
// With this
const geojsonFile = kml(new DOMParser().parseFromString(fileResult, 'text/xml'));

And that's it! We've replaced two lines and our code still works as expected. You can check out the commit diff here. And the final solution at Stackblitz is here.

Wrap-up ๐Ÿ“ฆ

In this article, we've discovered KML, GeoJSON and their purpose. We've tried to parse and convert a KML file to GeoJSON using @mapbox/togeojson. Then we discovered an alternative library that's more up-to-date and well-maintained called @tmcw/togeojson.

I hope I helped you learn something new and of course, don't hesitate to chime in the comments. I would be delighted to read you too!

Cheers!

Banner photo from Pixabay.

Did you find this article valuable?

Support Tarek Jellali by becoming a sponsor. Any amount is appreciated!

ย