Search code examples
pythonlxmlkml

How to update a KML to include a new Element with a value


I have a KML that does not have the Element "name" under Element Placemark. As such, when opening the KML using Google Earth, there is no name appearing next to each polygon under the tree.

In the original kml below, there are 2 Placemark. Each has an Element simpleData name="ID". The 2 values associated with them are FM2 and FM3 respectively.

<?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>Test.kml</name>
    <open>1</open>
    <Schema name="test" id="S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
        <SimpleField type="string" name="ID"> <displayName>&lt;b&gt;ID&lt;/b&gt;</displayName>
    </SimpleField>
    <SimpleField type="string" name="cname"><displayName>&lt;b&gt;cname&lt;/b&gt;</displayName>
    </SimpleField>
    </Schema>
    <Style id="falseColor01">
        <BalloonStyle>
            <text><![CDATA[<table border="0"><tr> 
            <td>b>ID</b>/td>td>$[test/ID]</td></tr>
            <tr><td><b>cname</b></td><td>$[test/cname]</td></tr>
            </table>]]></text>
        </BalloonStyle>
        <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
        </LineStyle>
        <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
        </PolyStyle>
    </Style>
    <StyleMap id="falseColor0">
        <Pair>
            <key>normal</key>
            <styleUrl>#falseColor00</styleUrl>
        </Pair>
        <Pair>
            <key>highlight</key>
            <styleUrl>#falseColor01</styleUrl>
       </Pair>
    </StyleMap>
    <Style id="falseColor00">
      <BalloonStyle>   
      </BalloonStyle>
        <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
        </LineStyle>
        <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
        </PolyStyle>
    </Style>
    <Folder id="layer 0">
        <name>Test_1</name>
        <open>1</open>
        <Placemark>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name="ID">FM2</SimpleData>
                    <SimpleData name="cname">FM2</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.889999,-32.17281600000001,0 
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
        <Placemark>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name="ID">FM3</SimpleData>
                    <SimpleData name="cname">FM3</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.90104,-32.15662800000001,0
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
    </Folder>
</Document>
</kml>

With the help of @DanielHaley, I was able to read the Element values FM2 and FM3.

How to obtain Element values from a KML by using lmxl

I then tried to used lxml etree tutorial to add an Element "name" under each , with values "FM2" and "FM3" respectively. I didn't have any luck.

This is what I expect to get:

    <Folder id="layer 0">
        <name>Test_1</name>
        <open>1</open>
        <Placemark>
            <name>FM2</name>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name="ID">FM2</SimpleData>
                    <SimpleData name="cname">FM2</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.889999,-32.17281600000001,0 
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
        <Placemark>
            <name>FM3</name>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name="ID">FM3</SimpleData>
                    <SimpleData name="cname">FM3</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.90104,-32.15662800000001,0
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
    </Folder>

Could anyone please give me a hint?

If possible, could you also please explain a little bit about the concept of namespace? I have looked into Google's tutorial, but the explanation is not quite complete.


Solution

  • If you want to modify Placemark, select that first.

    Then create the new name element and pull the value from SimpleData to use as the text value for it.

    Finally insert the new element into Placemark.

    Example...

    Python

    from lxml import etree
    
    ns = {"kml": "http://www.opengis.net/kml/2.2"}
    
    # Using a parser to improve formatting of new "name" elements
    # and, most importantly, to preserve CDATA sections.
    parser = etree.XMLParser(remove_blank_text=True, strip_cdata=False)
    
    tree = etree.parse("test.kml", parser=parser)
    
    for placemark in tree.xpath("//kml:Placemark", namespaces=ns):
        # Create new "name" element in kml namespace ({http://www.opengis.net/kml/2.2}name).
        name_element = etree.Element(etree.QName(ns.get("kml"), "name"), nsmap=ns)
    
        # Set value of "name" element to value from SimpleData.
        name_element.text = placemark.xpath("kml:ExtendedData/kml:SchemaData/kml:SimpleData[@name='ID']/text()",
                                            namespaces=ns)[0]
    
        # Insert the new element into "placemark" as the first child.
        placemark.insert(0, name_element)
    
    print(etree.tostring(tree, pretty_print=True).decode("UTF-8"))
    

    Printed XML Output

    <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>Test.kml</name>
        <open>1</open>
        <Schema name="test" id="S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
          <SimpleField type="string" name="ID">
            <displayName>&lt;b&gt;ID&lt;/b&gt;</displayName>
          </SimpleField>
          <SimpleField type="string" name="cname">
            <displayName>&lt;b&gt;cname&lt;/b&gt;</displayName>
          </SimpleField>
        </Schema>
        <Style id="falseColor01">
          <BalloonStyle>
            <text><![CDATA[<table border="0"><tr> 
                <td>b>ID</b>/td>td>$[test/ID]</td></tr>
                <tr><td><b>cname</b></td><td>$[test/cname]</td></tr>
                </table>]]></text>
          </BalloonStyle>
          <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
          </LineStyle>
          <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
          </PolyStyle>
        </Style>
        <StyleMap id="falseColor0">
          <Pair>
            <key>normal</key>
            <styleUrl>#falseColor00</styleUrl>
          </Pair>
          <Pair>
            <key>highlight</key>
            <styleUrl>#falseColor01</styleUrl>
          </Pair>
        </StyleMap>
        <Style id="falseColor00">
          <BalloonStyle>   
                </BalloonStyle>
          <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
          </LineStyle>
          <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
          </PolyStyle>
        </Style>
        <Folder id="layer 0">
          <name>Test_1</name>
          <open>1</open>
          <Placemark>
            <name>FM2</name>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
              <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                <SimpleData name="ID">FM2</SimpleData>
                <SimpleData name="cname">FM2</SimpleData>
              </SchemaData>
            </ExtendedData>
            <Polygon>
              <outerBoundaryIs>
                <LinearRing>
                  <coordinates>150.889999,-32.17281600000001,0 
                                </coordinates>
                </LinearRing>
              </outerBoundaryIs>
            </Polygon>
          </Placemark>
          <Placemark>
            <name>FM3</name>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
              <SchemaData schemaUrl="#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                <SimpleData name="ID">FM3</SimpleData>
                <SimpleData name="cname">FM3</SimpleData>
              </SchemaData>
            </ExtendedData>
            <Polygon>
              <outerBoundaryIs>
                <LinearRing>
                  <coordinates>150.90104,-32.15662800000001,0
                                </coordinates>
                </LinearRing>
              </outerBoundaryIs>
            </Polygon>
          </Placemark>
        </Folder>
      </Document>
    </kml>
    

    Also, see here for more information on XML Namespaces. It does a better job explaining than I could do here.