Convert KML to GeoJSON in your browser or NodeJS app
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:
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?).Then, we pass all what we did with the DOMParser to the
toGeoJson.kml()
function which will convert our KML to a GeoJSON object.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:
Get the HTML element of the dropzone using
document.querySelector()
Bind the
drop
event listener to the functiononDrop()
Bind the
dragover
event listener to the functiondragOverHandler()
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!
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.
Subscribe to my newsletter
Read articles from Tarek Jellali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Tarek Jellali
Tarek Jellali
Software Engineer โข TypeScript โข JavaScript โข Angular โข VueJS โข WordPress โข Ex-founder of ng-enious