I am reading .kml files saved out of Google Earth Pro.
Here is the code which I got from a post here on SO. Works great when the xDoc.Descendants have consistent elements.
XDocument xDoc = XDocument.Load(openFileDialog1.FileName);
XNamespace ns = "http://www.opengis.net/kml/2.2";
var placemarks = xDoc.Descendants(ns + "Placemark").Select(p => new
{
Name = (string)p.Element(ns + "name").Value.ToString(),
Coords = p.Element(ns + "Point").Element(ns + "coordinates").Value,
})
.ToArray();
This will produce this with the below kml data
image of form displaying the read KML elements
<?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 id="1Y-yAHoHb_KrjsRT70KM5qLdZQjN_xcKW">
<name>Test kml</name>
<Placemark id="0B2FCD3E802CFEB8E9AD">
<name>0+00 cabinet location 12’ BOC</name>
<LookAt>
<longitude>-93.28687708665021</longitude>
<latitude>44.84264135896755</latitude>
<altitude>255.3402528075096</altitude>
<heading>0</heading>
<tilt>0</tilt>
<range>36.48488765818456</range>
<altitudeMode>absolute</altitudeMode>
<gx:fovy>35</gx:fovy>
</LookAt>
<styleUrl>#msn_parking_lot</styleUrl>
<Point>
<coordinates>-93.28687708665021,44.84264135896755,255.3402528075096</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Place HH</name>
<LookAt>
<longitude>-93.28692035131073</longitude>
<latitude>44.84255178413457</latitude>
<altitude>0</altitude>
<heading>1.868100177527735e-05</heading>
<tilt>15.19185305855737</tilt>
<range>106.8343470069538</range>
<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
</LookAt>
<styleUrl>#msn_square0</styleUrl>
<Point>
<gx:drawOrder>1</gx:drawOrder>
<coordinates>-93.28690200178055,44.84263978936539,0</coordinates>
</Point>
</Placemark>
<Placemark id="0C6BEBC5352CFEBCA94D">
<name>0+45 sewer</name>
<LookAt>
<longitude>-93.28845935397412</longitude>
<latitude>44.84257694352023</latitude>
<altitude>251.8505380949421</altitude>
<heading>0</heading>
<tilt>0</tilt>
<range>63.54447705992244</range>
<altitudeMode>absolute</altitudeMode>
<gx:fovy>35</gx:fovy>
</LookAt>
<styleUrl>#__managed_style_397C1C0EAA2D00EDBB6D</styleUrl>
<Point>
<coordinates>-93.28845935397412,44.84257694352023,251.8505380949421</coordinates>
</Point>
</Placemark>
</Document>
</kml>
But when the KML xDoc.Descendants have inconsistent elements such as the below KML, I get the System.Xml.Linq.XContainer.Elelement(…) returned null error because the first placemark does not have the point element.
image of error in visual studio
<?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 id="1Y-yAHoHb_KrjsRT70KM5qLdZQjN_xcKW">
<name>Test KML</name>
<Placemark>
<name>Running line</name>
<styleUrl>#m_ylw-pushpin</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
-93.28762387862635,44.84811886228031,0 -93.28753369589279,44.84814969351044,0 -93.28751831971846,44.84825957026099,0 -93.28749752003094,44.84895772192719,0
</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Place HH</name>
<LookAt>
<longitude>-93.28692035131073</longitude>
<latitude>44.84255178413457</latitude>
<altitude>0</altitude>
<heading>1.868100177527735e-05</heading>
<tilt>15.19185305855737</tilt>
<range>106.8343470069538</range>
<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
</LookAt>
<styleUrl>#msn_square0</styleUrl>
<Point>
<gx:drawOrder>1</gx:drawOrder>
<coordinates>-93.28690200178055,44.84263978936539,0</coordinates>
</Point>
</Placemark>
<Placemark id="0C6BEBC5352CFEBCA94D">
<name>0+45 sewer</name>
<LookAt>
<longitude>-93.28845935397412</longitude>
<latitude>44.84257694352023</latitude>
<altitude>251.8505380949421</altitude>
<heading>0</heading>
<tilt>0</tilt>
<range>63.54447705992244</range>
<altitudeMode>absolute</altitudeMode>
<gx:fovy>35</gx:fovy>
</LookAt>
<styleUrl>#__managed_style_397C1C0EAA2D00EDBB6D</styleUrl>
<Point>
<coordinates>-93.28845935397412,44.84257694352023,251.8505380949421</coordinates>
</Point>
</Placemark>
</Document>
</kml>
Is there are way to use this style of linq select and handle inconsistent elements?
Ultimately, I want it to get the coords out of the linestring /coordinates element when the placemark has them there, and the coords out of the Point/coordinate element when they are there. Can I have multiple array values populated with the value or null based on the "child" elements without null errors? I can then deal with looking at nulls vs values. If there are no coords in either point or linestring, both will just be null and I will deal with that.
Is there are way to use this style of linq select and handle inconsistent elements?
Absolutely - you just need to stop unconditionally dereferencing something that can be null. Using the null-conditional operator and the null-coalescing operator, this is quite straightforward:
var placemarks = xDoc
.Descendants(ns + "Placemark")
.Select(p => new
{
Name = p.Element(ns + "name").Value,
Coords = (p.Element(ns + "LineString")?.Element(ns + "coordinates").Value)
?? (p.Element(ns + "Point")?.Element(ns + "coordinates").Value)
});
The brackets there aren't strictly necessary, but I think they make it easier to see what's going on. Note that this will still fail if there's a LineString
or Point
element without a coordinates
child. You can use more null-conditional operators (before Value
) to avoid that:
var placemarks = xDoc
.Descendants(ns + "Placemark")
.Select(p => new
{
Name = p.Element(ns + "name").Value,
Coords = (p.Element(ns + "LineString")?.Element(ns + "coordinates")?.Value)
?? (p.Element(ns + "Point")?.Element(ns + "coordinates")?.Value)
});
(I've removed the cast and the ToString
call for Name
, as they weren't necessary - XElement.Value
already returns a string.)