6

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.)
4
  • 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
    Commented May 7, 2018 at 20:54
  • See also stackoverflow.com/questions/12037297/…
    – user30184
    Commented May 7, 2018 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... :) Commented May 7, 2018 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
    Commented May 7, 2018 at 21:48

2 Answers 2

5

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.

0

I have just encountered the same issue with a KML file, and have resolved it as per @Spacedman's answer by wrapping each hole LinearRing in innerBoundaryIs tags using three REGEX substitutions in Notepad++. This approach assumes that the external ring is already correctly tagged with outerBoundaryIs:

  1. Remove newlines and indentation to allow the lookbehind in step 2 to work:

Search for: \t+|\R+

Replace with: nothing

  1. Find all instances of <LinearRing> without a preceding <innerBoundaryIs> or <outerBoundaryIs> tag:

Search for: (?<!BoundaryIs>)<LinearRing>

Replace with: <innerBoundaryIs><LinearRing>

  1. Find all instances of <\LinearRing> without a succeeding <\innerBoundaryIs> or <\outerBoundaryIs> tag:

Search for: <\/LinearRing>(?!<\/.{5}BoundaryIs>)

Replace with: <\/LinearRing><\/innerBoundaryIs>

The resulting file correctly loads in QGIS (using GDAL). Interestingly loading the modified KML file into Google Earth Pro and then saving it out results in the extra innerBoundaryIs tags being dropped again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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