pyKML Tutorial
==============

The following tutorial gives a brief overview of many of the features of pyKML. 
It is designed to run from within a Python or iPython shell, and assumes that 
pyKML has been installed and is part of your Python search path.  The tutorial 
is designed to be followed from start to finish.

For complete stand alone programs that demonstrate how to use pyKML, check out
the :doc:`examples`.

Constructing KML from scratch
-----------------------------

The pyKML library can be used to construct KML documents, using the 
:ref:`pykml-factory` module:

.. ipython::
    
    # create a factory object that can create elements in the KML namespace
    In [11]: from pykml.factory import KML_ElementMaker as KML
    
    # create an the object equivalent of a KML <name> element
    In [12]: name_object = KML.name("Hello World!")

If you are creating KML documents that utilize elements that are not part
of the default KML namespace, you will want to create an additional factory 
objects for each namespace.  
For example, the following creates factory objects that can
be used to create elements that are part of the ATOM and Google Extensions 
namespace:

.. ipython::

    In [13]: from pykml.factory import ATOM_ElementMaker as ATOM

    In [14]: from pykml.factory import GX_ElementMaker as GX

Documents with nested KML tags can be created by nesting the creation
of Python objects:

.. ipython::
    
    In [16]: pm1 = KML.Placemark(
       ....:         KML.name("Hello World!"),
       ....:         KML.Point(
       ....:           KML.coordinates("-64.5253,18.4607")
       ....:         )
       ....:       )

Once a pyKML object element has been created, a string representation can be 
generated by using the `.tostring()` method:

.. ipython::
    
    In [21]: from lxml import etree
    
    @doctest
    In [22]: etree.tostring(pm1)
    Out[22]: '<Placemark xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://www.opengis.net/kml/2.2"><name>Hello World!</name><Point><coordinates>-64.5253,18.4607</coordinates></Point></Placemark>'

    # use the pretty_print keyword if you want something more readable
    In [23]: print etree.tostring(pm1, pretty_print=True)

pyKML creates Python objects, which  can be passed around and 
later aggregated.  The following creates a second placemark object, and then 
groups the two placemarks together in a folder.

.. ipython::
    
    # create another placemark
    In [31]: pm2 = KML.Placemark(
       ....:         KML.name("A second placemark!"),
       ....:         KML.Point(
       ....:           KML.coordinates("-64.5358,18.4486")
       ....:         )
       ....:       )
    
    # group the two placemarks in a folder
    In [32]: fld = KML.Folder(pm1,pm2)
    
    In [33]: print etree.tostring(fld, pretty_print=True)

Objects representing KML elements can also be appended into objects that have
already been created.
For example, the following appends yet another placemark to the folder.

.. ipython::
    
    # create yet another placemark
    In [41]: pm3=KML.Placemark(KML.name("A third placemark!"))
    
    # append the placemark to the series already in the folder
    In [42]: fld.append(pm3)
    
    In [43]: print etree.tostring(fld, pretty_print=True)

Similarly, you can remove elements from an existing object.  
The following removes the second of three placemarks from the folder:

.. ipython::
    
    # remove a particular placemark
    In [51]: fld.remove(pm2)
    
    In [52]: print etree.tostring(fld, pretty_print=True)

Once you have a KML document, you can access elements using object attributes:

.. ipython::

    In [55]: print fld.Placemark.name.text

This type of attribute-based access is provided by the `lxml` packages's
`objectify API`. pyKML users are encouraged to familiarize themselves with the 
`objectify API documentation`_ on the lxml website, because pyKML inherits this
functionality.

.. _objectify API documentation: http://lxml.de/objectify.html


Parsing existing KML documents
------------------------------

Sometimes instead of building a KML document from scratch, you may want to 
modify an existing KML document.  For this case, pyKML's parsing capabilities
are useful.  pyKML can parse information from a variety of sources, including
strings, local files, and remote URLs.  

The most straightforward is parsing from a string...

.. ipython::

    In [61]: from pykml import parser

    In [62]: kml_str = '<kml xmlns="http://www.opengis.net/kml/2.2">' \
       ....:             '<Document>' \
       ....:               '<Folder>' \
       ....:                 '<name>sample folder</name>' \
       ....:               '</Folder>' \
       ....:             '</Document>' \
       ....:           '</kml>'

    In [63]: root = parser.fromstring(kml_str)

    In [64]: print root.Document.Folder.name.text

You can also parse a local file...

.. ipython::

    In [71]: from os import path
    
    In [72]: kml_file = path.join( \
       ....:     '../src/pykml/test', \
       ....:     'testfiles/google_kml_developers_guide', \
       ....:     'complete_tour_example.kml')
    
    In [73]: with open(kml_file) as f:

    In [74]:     doc = parser.parse(f)
    
    In [75]: 

... or a remote URL...

.. ipython::

    In [80]: import urllib2
    
    In [81]: url = 'http://code.google.com/apis/kml/documentation/KML_Samples.kml'

    In [82]: fileobject = urllib2.urlopen(url)
        
    In [83]: root = parser.parse(fileobject).getroot()
    
    In [84]: print root.Document.name

Validation of KML documents
---------------------------

KML documents that you create can be validated against XML Schema documents,
which define the rules of which elements are acceptible and what ordering can 
be used.  Both the OGC KML schema and the Google Extension schemas are included 
with pyKML.

To validate your KML document, first create instances of the schemas:

.. ipython::

    In [100]: from pykml.parser import Schema

    In [101]: schema_ogc = Schema("ogckml22.xsd")

    In [102]: schema_gx = Schema("kml22gx.xsd")

Then use the schemas to validate your KML objects, using the `.validate()` 
or `.assertValid()` methods.  
The following code creates a small invalide KML document which
includes an element from the Google Extension namespace (`<gx_Tour>`) so 
the document does not validate against the basic OGC KML schema, but does
validate agains the Google Extensions schema. 

.. ipython::

    # create a small KML document
    In [110]: doc = KML.kml(GX.Tour())
    
    # validate it against the OGC KML schema
    In [111]: schema_ogc.validate(doc)
    
    # validate it against the Google Extension schema
    In [112]: schema_gx.validate(doc)

The `.validate()` method only returns True or False.  For invalid documents, 
it is often useful to obtain details of why the document is invalid
using the `.assertValid()` method:
    
.. ipython::

    # validate against the OGC KML schema, and generate an exception
    In [113]: schema_ogc.assertValid(doc)

You can also validate while parsing by including a schema object as a parameter.

.. ipython::

    # the following triggers an error because <eggplant> is not a valid OGC KML element
    In [62]: bad_kml_str = '<kml xmlns="http://www.opengis.net/kml/2.2">' \
       ....:             '<Document>' \
       ....:               '<Folder>' \
       ....:                 '<eggplant/>' \
       ....:               '</Folder>' \
       ....:             '</Document>' \
       ....:           '</kml>'
    
    In [63]: root = parser.fromstring(bad_kml_str, schema_ogc)



Setting the Number of Decimal Places
------------------------------------
Many KML files, especially those authored by Google Earth, contain coordinate
information with more decimal places that often is necessary.  
The `set_max_decimal_places()` function addresses this, by allowing a user
to reduce the number of decimal places used.  The example below demonstrates 
this for a previously created placemark.

.. ipython::

    In [70]: from pykml.helpers import set_max_decimal_places

    In [71]: print etree.tostring(pm1, pretty_print=True)

    # set the coordinate precision to something smaller
    In [72]: set_max_decimal_places(
       ....:            pm1, 
       ....:            max_decimals={
       ....:                'longitude': 2,
       ....:                'latitude': 1,
       ....:            }
       ....:        )

    # note that the coordinate values have changed
    In [73]: print etree.tostring(pm1, pretty_print=True)



Building pyKML Python Scripts
-----------------------------
While pyKML allows you use leverage programming to create
customized KML files, writing the initial pyKML code can be tedious.
To help with this, pyKML provides the verbosely named
`.write_python_script_for_kml_document()` function which will produce
a Python script that can serve as a starting point for further customization.

.. ipython::

    In [10]: from pykml.factory import write_python_script_for_kml_document

    In [11]: url = 'http://code.google.com/apis/kml/documentation/kmlfiles/altitudemode_reference.kml'

    In [12]: fileobject = urllib2.urlopen(url)
        
    In [13]: doc = parser.parse(fileobject).getroot()

    In [14]: script = write_python_script_for_kml_document(doc)

    In [15]: print script


That concludes the tutorial.  For further examples of how pyKML can be used, 
head on over to the :doc:`examples` section of the documentation.
