Converting multilayer KML files to GeoJSON using ogr2ogr

The trick is to get a list of layers in the KML that you can than iterate over individually.

A linux solution (bash) could look like...

for layer in "$(ogrinfo -ro -so -q file.kml | cut -d ' ' -f 2)"
do
    ogr2ogr -f "GeoJSON" "file_${layer}.json" file.kml "${layer}"
done

You might also have an issue of spaces ' ' in the kml layer name.

Those can be substituted (removed or replaced) on the fly with bash:

$ layer1="Site - 123"
$ echo ${layer1// /}
$ Site-123
$ echo ${layer1} | sed 's/ //g'
$ Site-123

Because of this, I highly recommend experimenting with the output of ogrinfo -ro -so -q file.kml | cut -d ' ' -f 2) to make sure it treats the layers in the list of that kml properly. Try to make adjustments to the code so that the spaces don't split up the list too much.

You can see that cut -d ' ' -f 2 will look for spaces (-d ' ') and separate the output into fields where they are found. Then it takes the second field (-f 2) and uses that as the layer name.

Here's an example of the output on a sample sqlite database:

% ogrinfo -ro -so -q ca_bs.sqlite 
1: bs_1250009_0 (Point)
2: bs_2060009_0 (Point)
3: bs_2010009_2 (Multi Polygon)
4: bs_2380009_2 (Multi Polygon)
5: bs_2240009_1 (Multi Line String)
6: bs_2310009_1 (Multi Line String)

% ogrinfo -ro -so -q ca_bs.sqlite | cut -d ' ' -f 2
bs_1250009_0
bs_2060009_0
bs_2010009_2
bs_2380009_2
bs_2240009_1
bs_2310009_1

So this should get pretty close with the sample you provided:

$ ogrinfo -ro -so -q test.kml | cut -d ':' -f2 | sed 's/^ //g'
Shapes
Set 1
Set 2

Mapbox's togeojson converter handles this very nicely. It flattens the contents of all layers/folders into sibling features.

A bonus: unlike the accepted answer using ogr2ogr to extract each layer/folder, togeojson preserves features in the top-level, and not in any directory.

Since it's JavaScript, it can run in your browser at http://mapbox.github.io/togeojson/—fully client-side code, it's not sending your KML to any server to convert. However, it's often important to have a local install for command-line fun.

So to install togeojson, first install node.js (it's small, ~13 MB for the entire runtime, and it's only going to get more useful for GIS). Then use node's package manager npm to install togeojson:

$ npm install -g togeojson

(might need sudo). Finally, convert your file:

$ togeojson test.kml > test.json

The result (as of now, version 0.13.0) has copious whitespace, four spaces per level of indentation. That can be annoying. Here's how to trim it, in the node.js command-line:

$ node
> var fs = require('fs')
> fs.writeFileSync('test-one-space.json', JSON.stringify(JSON.parse(fs.readFileSync('test.json', 'utf8')), null, 1))

This slurps the test.json file, converts it to a JavaScript object ("JSO"?), converts it back to JSON but with one space per indentation level, and writes the result back out to test-one-space.json.

For that unreadable look, eliminate all superfluous whitespace with:

> fs.writeFileSync('test-minified.json', JSON.stringify(JSON.parse(fs.readFileSync('test.json', 'utf8'))))