Search code examples
c#linqkml

System.NullReferenceException: Object reference not set to an instance of an object. System.Xml.Linq.XContainer.Elelement(…) returned null


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 elementsenter image description here

<?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 studioenter image description here

<?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.


Solution

  • 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.)