I will first give a little bit of context to my problem.
I have obtained a .kml file of the territorial seas around the world on this site, and I would like to display it not on Google Earth but on a matplotlib.pyplot plot (with a cartopy map if possible too). The .kml file looks like this:
<?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">
<NetworkLink>
<name>Territorial Seas (12NM) v3</name>
<description><![CDATA[Flanders Marine Institute (2019). Maritime Boundaries Geodatabase: Territorial Seas (12NM), version 3. Available online at <a href="http://www.marineregions.org">http://www.marineregions.org</a> https://doi.org/10.14284/387. Consulted on YYYY-MM-DD.]]></description>
<Link>
<href>http://geo.vliz.be/geoserver/gwc/service/kml/MarineRegions:eez_12nm.png.kml</href>
</Link>
</NetworkLink>
</kml>
For that I saws on this other StackOverflow question that using fastkml to read the file was possible.
So this is the test.py code I am trying to run (it comes from the usage guide):
from fastkml import kml
filename = "C:\\Users\\dumasal\\Documents\\GOOGLE_EARTH\\MarineRegions-eez_12nm.kml"
with open(filename, 'rt', encoding="utf-8") as myfile:
doc=myfile.read()
print(doc)
# Create the KML object to store the parsed result
k = kml.KML()
# Read in the KML string
k.from_string(doc)
print('k = ', k)
### Next we perform some simple sanity checks ###
# Check that the number of features is correct
# This corresponds to the single ``Document``
features = list(k.features())
print(len(features))
# Check that we can access the features as a generator
# (The two Placemarks of the Document)
print(features[0].features())
f2 = list(features[0].features())
print(len(f2))
# Check specifics of the first Placemark in the Document
print(f2[0])
print(f2[0].description)
print(f2[0].name)
# Check specifics of the second Placemark in the Document
print(f2[1].name)
f2[1].name = "ANOTHER NAME"
# Verify that we can print back out the KML object as a string
print(k.to_string(prettyprint=True))
When I ran it I got the error: ValueError: Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration.
.
I looked the error up on google and found this GitHub page where they were saying that the "from_string()" function only takes bytes so the beginning of my code could be changed to:
from fastkml import kml
filename = "C:\\Users\\dumasal\\Documents\\GOOGLE_EARTH\\MarineRegions-eez_12nm.kml"
with open(filename, 'r') as myfile:
doc=myfile.read().encode('UTF-8')
print(doc)
# Create the KML object to store the parsed result
k = kml.KML()
# Read in the KML string
k.from_string(doc)
print('k = ', k)
### Next we perform some simple sanity checks ###
# Check that the number of features is correct
# This corresponds to the single ``Document``
features = list(k.features())
print(len(features))
And strangely enough the ValueError stopped appearing. However now I get the error:
print(features[0].features())
IndexError: list index out of range
this is because my variables features = []
, and I don't know why.
So could you either explain to me why the features variable is empty, or a more direct method to plot a .kml file with python and matplotlib?
Thank you very much!
One issue is the KML file you have is a super-overlay, which is auto-generated as multiple KML "files" referenced as NetworkLinks in sub regions and few KML python packages support recursive NetworkLinks directly. The fastkml module you're using does not implement NetworkLinks so the content is skipped.
The pyKML package can parse the KML file and iterate over the KML layers referenced in the Network Links.
You can do something like this to iterate over the NetworkLinks and the KML content.
import requests
import re
from pykml import parser
count = 0
def walk(elt):
global count
for elt in elt.getchildren():
tag = re.sub(r'^.*\}', '', elt.tag)
if tag in ['Folder', 'Document']:
walk(elt)
elif tag == 'NetworkLink':
print(tag)
print(elt.Link.href)
if count == 10:
# for debugging stop after 10 links
raise Exception("too many links")
count += 1
response = requests.get(elt.Link.href)
walk(parser.fromstring(response.content))
elif tag == 'GroundOverlay':
print(tag)
# do something with the ground overlay
else:
# other tag; e.g. Region, comment, etc
print(">>", tag)
with open("MarineRegions-eez_12nm.kml", 'rb') as myfile:
root = parser.parse(myfile).getroot()
walk(root)
The MarineRegions KML is a super-overlay that recursively sub-divides into smaller regions so trying to plot the GroundOverlay at all levels will be overwrite the larger low-res overlays with smaller hi-res overlays. You need to decide if you only want the hi-res ground overlays or the low-res overlays. For example, if the KML content at a given level doesn't have any NetworkLinks then it's at the lowest level.
Note GeoServer has two different types of super-overlays: raster and vector. The KML you're using is a raster super-overlay. You may want to check if there is a vector overlay available in the case dealing with vectors might be easier than dealing with ground overlay images.