4

I have a KML file that contains a polygon with holes in it. It looks similar to this KML:

<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Folder><name>polygon_with_holes</name>
    <Placemark>
        <Style><LineStyle><color>ff0000ff</color></LineStyle><PolyStyle><fill>0</fill></PolyStyle></Style>
        <Polygon>
            <outerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -95,45 -95.0,45.007 -94.99,45.007 -94.99,45.0 -95,45
                    </coordinates>
                </LinearRing>
            </outerBoundaryIs>
            <innerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -94.999,45.003 -94.999,45.005 -94.997,45.005 -94.997,45.003 -94.999,45.003
                    </coordinates>
                </LinearRing>
                <LinearRing>
                    <coordinates>
                        -94.996,45.001 -94.996,45.003 -94.994,45.003 -94.994,45.001 -94.996,45.001
                    </coordinates>
                </LinearRing>
                <LinearRing>
                    <coordinates>
                        -94.993,45.004 -94.993,45.006 -94.991,45.006 -94.991,45.004 -94.993,45.004
                    </coordinates>
                </LinearRing>
            </innerBoundaryIs>
        </Polygon>
    </Placemark>
</Folder>
</Document></kml>

This KML renders fine within Google Earth:

google earth

However, when I read this KML into R using st_read from the sf package, only the first hole gets read in:

> polygon1 <- st_read(dsn = '/path/to/file.kml', layer = 'polygon_with_holes')
> st_geometry(polygon1)[[1]]
# POLYGON ((-95 45, -95 45.007, -94.99 45.007, -94.99 45, -95 45), (-94.999 45.003, -94.999 45.005, -94.997 45.005, -94.997 45.003, -94.999 45.003))
> plot(st_geometry(polygon1), col = 'red')

polygon1

Note that the same behavior occurs with readOGR from the sp package, which leads me to believe this behavior is specific to GDAL. I've determined that wrapping each inner LinearRing with innerBoundaryIs tags solves the problem. In other words, the following KML is properly parsed by GDAL:

<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Folder><name>polygon_with_holes</name>
    <Placemark>
        <Style><LineStyle><color>ff0000ff</color></LineStyle><PolyStyle><fill>0</fill></PolyStyle></Style>
        <Polygon>
            <outerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -95,45 -95.0,45.007 -94.99,45.007 -94.99,45.0 -95,45
                    </coordinates>
                </LinearRing>
            </outerBoundaryIs>
            <innerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -94.999,45.003 -94.999,45.005 -94.997,45.005 -94.997,45.003 -94.999,45.003
                    </coordinates>
                </LinearRing>
            </innerBoundaryIs>
            <innerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -94.996,45.001 -94.996,45.003 -94.994,45.003 -94.994,45.001 -94.996,45.001
                    </coordinates>
                </LinearRing>
            </innerBoundaryIs>
            <innerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -94.993,45.004 -94.993,45.006 -94.991,45.006 -94.991,45.004 -94.993,45.004
                    </coordinates>
                </LinearRing>
            </innerBoundaryIs>
        </Polygon>
    </Placemark>
</Folder>
</Document></kml>

When this new KML is read into R:

> polygon2 <- st_read(dsn = '/path/to/newfile.kml', layer = 'polygon_with_holes')
> st_geometry(polygon2)[[1]]
# POLYGON ((-95 45, -95 45.007, -94.99 45.007, -94.99 45, -95 45), (-94.999 45.003, -94.999 45.005, -94.997 45.005, -94.997 45.003, -94.999 45.003), (-94.996 45.001, -94.996 45.003, -94.994 45.003, -94.994 45.001, -94.996 45.001), (-94.993 45.004, -94.993 45.006, -94.991 45.006, -94.991 45.004, -94.993 45.004))
> plot(st_geometry(polygon2), col = 'red')

polygon2

Unfortunately, the original offending file is part of an automated process, so fixing that file (and others like it) by hand is not a feasible option. So my questions are twofold:

  • Is this the expected behavior of GDAL when parsing KML polygons with holes?
  • Is there a GDAL option that can be invoked so that the original KML file is correctly parsed? (Preferrably something that can be passed in via the options argument to st_read.)
  • I believe that you should fix the automatic process. From developers.google.com/kml/documentation/… I can read <innerBoundaryIs> Contains a <LinearRing> element. A Polygon can contain multiple <innerBoundaryIs> elements, which create multiple cut-outs inside the Polygon. Unfortunately the specification is not very exact about inner boundaries and linearrings. – user30184 May 7 '18 at 20:54
  • See also stackoverflow.com/questions/12037297/… – user30184 May 7 '18 at 21:02
  • Perhaps I should elaborate. Many parts of the process are automated, but I think the source of the KML is typically a shapefile converted to KML within ArcGIS. Please don't ask me why we convert shapefiles to KML... :) – Cameron Bieganek May 7 '18 at 21:05
  • 1
    Contact your ESRI support and ask if they have a bug in their product. Or the bug can be in GDAL and LIBKML even the latter is the reference implementation by Google gdal.org/drv_libkml.html. – user30184 May 7 '18 at 21:48
3

Here's the part of the KML XSD that defines what an innerBoundaryIs can be:

<element name="innerBoundaryIs" type="kml:BoundaryType"/>

From http://schemas.opengis.net/kml/2.3/ogckml23.xsd

So its a BoundaryType which is this:

<complexType name="BoundaryType" final="#all">
<all>
<element ref="kml:LinearRing" minOccurs="0"/>
<element ref="kml:BoundarySimpleExtensionGroup" minOccurs="0" maxOccurs="unbounded"/>
<element ref="kml:BoundaryObjectExtensionGroup" minOccurs="0" maxOccurs="unbounded"/>
</all>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>

The <element ref="kml:LinearRing" minOccurs="0"/> says how many LinearRing elements are allowed in a BoundaryType, and the default for maxOccurs is 1, so this element can have zero or one LinearRing.

You'll have to read the XML in using one of R's XML readers, and reform it with each LinearRing in its own innerBoundaryIs to make the XML you give.

I just created a new polygon layer in QGIS with one feature with three holes, saved it to a shapefile and then converted to KML using ogr2ogr, and it correctly wrapped each inner boundary. I would say its a bug in whatever is writing your KML files, they are not compliant to the published spec.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.