4

I've been trying to display a KML file with Openlayers, which I created with Google Earth. It is a simple polygon with white outline and 50% alpha black filling.

In Openlayers it displays as a white, filled polygon, no matter what kind of styles I apply to it in Google Earth.

I have no errors or warnings, the KML just seems to miss out the styles. I use Openlayers 6. What am I missing here?


My Implementation using OL:

const file = functionThatReturnsUploadedFileFromFileInput();
const reader = new FileReader();
const vectorLayer = functionThatReturnsVectorLayerFromMap();

reader.onload = function () {
    var 
        source = new VectorSource({
            url: reader.result,
            format: new KML({extractStyles: true})
        });

    vectorLayer.setSource(source);

}
reader.readAsDataURL(file);

The KML generated by Google Earth:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
    <name>Test2</name>
    <gx:CascadingStyle kml:id="__managed_style_25EBAAC82614827EFCCB">
        <Style>
            <IconStyle>
                <scale>1.2</scale>
                <Icon>
                    <href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
                </Icon>
                <hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
            </IconStyle>
            <LabelStyle>
            </LabelStyle>
            <LineStyle>
                <width>24</width>
            </LineStyle>
            <PolyStyle>
                <color>80000000</color>
            </PolyStyle>
            <BalloonStyle>
                <displayMode>hide</displayMode>
            </BalloonStyle>
        </Style>
    </gx:CascadingStyle>
    <gx:CascadingStyle kml:id="__managed_style_14CDD4276C14827EFCCB">
        <Style>
            <IconStyle>
                <Icon>
                    <href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
                </Icon>
                <hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
            </IconStyle>
            <LabelStyle>
            </LabelStyle>
            <LineStyle>
                <width>16</width>
            </LineStyle>
            <PolyStyle>
                <color>80000000</color>
            </PolyStyle>
            <BalloonStyle>
                <displayMode>hide</displayMode>
            </BalloonStyle>
        </Style>
    </gx:CascadingStyle>
    <StyleMap id="__managed_style_0D301BCC0014827EFCCB">
        <Pair>
            <key>normal</key>
            <styleUrl>#__managed_style_14CDD4276C14827EFCCB</styleUrl>
        </Pair>
        <Pair>
            <key>highlight</key>
            <styleUrl>#__managed_style_25EBAAC82614827EFCCB</styleUrl>
        </Pair>
    </StyleMap>
    <Placemark id="04AFE6060F147CE66FBD">
        <name>Ort1</name>
        <LookAt>
            <longitude>10.06256752902339</longitude>
            <latitude>53.57036326842834</latitude>
            <altitude>13.96486261382906</altitude>
            <heading>0</heading>
            <tilt>0</tilt>
            <gx:fovy>35</gx:fovy>
            <range>632.584179697442</range>
            <altitudeMode>absolute</altitudeMode>
        </LookAt>
        <styleUrl>#__managed_style_0D301BCC0014827EFCCB</styleUrl>
        <Polygon>
            <outerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        10.05998904317019,53.57172202479447,10.32521244530025 10.06072970043745,53.57050957507556,13.60797686155092 10.06170365480513,53.57072597737833,13.60026817081542 10.06094034058923,53.57192922042453,10.47620396741323 10.05998904317019,53.57172202479447,10.32521244530025 
                    </coordinates>
                </LinearRing>
            </outerBoundaryIs>
        </Polygon>
    </Placemark>
</Document>
</kml>

What it should look like:

enter image description here


What it looks like in OL:

enter image description here

2

3 Answers 3

5

The CascadingStyle tag isn't supported, but this works

<Style id="__managed_style_25EBAAC82614827EFCCB">
        <IconStyle>
            <scale>1.2</scale>
            <Icon>
                <href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
            </Icon>
            <hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
        </IconStyle>
        <LabelStyle>
        </LabelStyle>
        <LineStyle>
            <width>24</width>
        </LineStyle>
        <PolyStyle>
            <color>80000000</color>
        </PolyStyle>
        <BalloonStyle>
            <displayMode>hide</displayMode>
        </BalloonStyle>
</Style>
<Style id="__managed_style_14CDD4276C14827EFCCB">
        <IconStyle>
            <Icon>
                <href>https://earth.google.com/earth/rpc/cc/icon?color=1976d2&amp;id=2000&amp;scale=4</href>
            </Icon>
            <hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/>
        </IconStyle>
        <LabelStyle>
        </LabelStyle>
        <LineStyle>
            <width>16</width>
        </LineStyle>
        <PolyStyle>
            <color>80000000</color>
        </PolyStyle>
        <BalloonStyle>
            <displayMode>hide</displayMode>
        </BalloonStyle>
</Style>
1
  • 1
    Thank you very much, that worked! Is there any info about supported/non-supported tags for the OL KML parser?
    – Kuga
    Jul 14, 2020 at 14:38
3

So, finally I came up with the following solution as the OL Parser does not support cascadingStyle Tag:

function removeBadTags (rawSource) {
    let result = rawSource;

    // remove "cascadingStyle" Tags
    result = rawSource.replace(/<.*?cascadingstyle.*?kml:id="(.+)">\s*<style>/gmi, (a, b) => {
        return "<Style id=\"" + b + "\">";
    });
    result = result.replace(/<\/Style>\s*<\/.*?cascadingstyle>/gmi, "</Style>");

    // ... remove more tags eventually

    return result;
}

Most likely not the most solid and elegant solution, however, it seems to work.

2
  • I've seen in the wild that <gx:CascadingStyle /> tags might also be followed by <styleUrl /> tags, and other <Style /> tags after that (for example when using info popovers). Your solution breaks such KMLs (It still removes the closing tag) Dec 16, 2021 at 12:58
  • The issue could be addressed by another replacement before the two you suggested: result = rawString.replace(/<.*?cascadingstyle.*?>\s*(<styleurl>.*<\/styleurl>)/gim, (a, b) => {return a.slice(0, a.indexOf(b));}); Dec 16, 2021 at 13:24
0

If you want to deal with Google Earth Web KMLs automatically, you can subclass the openlayers KML class. Then pass this one to the format option of the source.

The class shown below also takes care of fixing the dataURIs exported for custom icons you uploaded to Google Earth Web and makes them resolvable by the browser.

The example below incorporates @Kuga's answer.

class GoogleEarthWebKML extends KML {
  constructor(options) {
    super(options)

    this.base64Regex = /data:image\/(?:png|jpeg|webp|svg\+xml)(?:;charset=utf-8)?;base64,((?:[A-Za-z0-9_-]|[+/])+={0,2})/gim
  } 

  getType() {
    return "text";
  }

  readFeature(source, options) {
    return super.readFeature(this.handleBadTags(source), options);
  }

  readFeatures(source, options) {
    return super.readFeatures(this.handleBadTags(source), options);
  }

  handleBadTags(rawString) {
    let result = rawString;

    // remove "styleUrl" Tags that are direct descendants of "cascadingStyle" Tags
    result = rawString.replace(
      /<.*?cascadingstyle.*?>\s*(<styleurl>.*<\/styleurl>)/gim,
      (a, b) => {
        return a.slice(0, a.indexOf(b));
      }
    );

    // remove "cascadingStyle" Tags
    result = result.replace(
      /<.*?cascadingstyle.*?kml:id="(.+)">\s*<style>/gim,
      (a, b) => {
        return `<Style id="${b}">`;
      }
    );
    result = result.replace(/<\/Style>\s*<\/.*?cascadingstyle>/gim, "</Style>");

    // fix dataURIs (replace -_ with +/ and add padding if necessary)
    result = result.replace(this.base64Regex, (a, b) => {
      const withPadding =
        b.length % 4 !== 0
          ? (a += Array.from({ length: 4 - (b.length % 4) }, () => "=").join(
              ""
            ))
          : a;
      return withPadding.replaceAll("-", "+").replaceAll("_", "/");
    });

    return result;
  }
}

Your Answer

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

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