Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

269 improve the documentation with examples #371

Merged
merged 11 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Document-clean.kml
Binary file added docs/co2-per-capita-2020.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
230 changes: 230 additions & 0 deletions docs/create_kml_files.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
Creating KML files
==================

Read a shapefile and build a 3D KML visualization.
--------------------------------------------------

This example shows how to read a shapefile and build a 3D KML visualization from it.

You will need to install `pyshp <https://pypi.org/project/pyshp/>`_ (``pip install pyshp``).

For this example we will use the
`Data on CO2 and Greenhouse Gas Emissions <https://github.com/owid/co2-data>`_ by
Our World in Data, and the Small scale data (1:110m) shapefile from
`Natural Earth <https://www.naturalearthdata.com/downloads/>`_.

First we import the necessary modules:

.. code-block:: python

import csv
import pathlib
import random

import shapefile
from pygeoif.factories import force_3d
from pygeoif.factories import shape

import fastkml
import fastkml.containers
import fastkml.features
import fastkml.styles
from fastkml.enums import AltitudeMode
from fastkml.geometry import create_kml_geometry


Read the shapefile:

Comment on lines +36 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use more descriptive variable names to improve code readability [Best practice, importance: 6]

Suggested change
Read the shapefile:
folder = kml.Folder(ns=ns, id="fid", name="folder name", description="folder description")
d.append(folder)

Comment on lines +36 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use more descriptive variable names to improve code readability [Best practice, importance: 6]

Suggested change
Read the shapefile:
folder = kml.Folder(ns=ns, id="fid", name="folder name", description="folder description")
d.append(folder)

.. code-block:: python

shp = shapefile.Reader("ne_110m_admin_0_countries.shp")

Read the CSV file and store the CO2 data for 2020:

.. code-block:: python

co2_csv = pathlib.Path("owid-co2-data.csv")
co2_data = {}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
if row["year"] == "2020":
co2_data[row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
Comment on lines +46 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for CSV data parsing.

The CSV parsing code should handle potential errors such as missing or malformed data.

Consider adding try-except blocks:

     with co2_csv.open() as csvfile:
         reader = csv.DictReader(csvfile)
         for row in reader:
-            if row["year"] == "2020":
-                co2_data[row["iso_code"]] = (
-                    float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
-                )
+            try:
+                if row["year"] == "2020" and row["iso_code"]:
+                    co2_data[row["iso_code"]] = (
+                        float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
+                    )
+            except (KeyError, ValueError) as e:
+                print(f"Warning: Error processing row: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
co2_csv = pathlib.Path("owid-co2-data.csv")
co2_data = {}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
if row["year"] == "2020":
co2_data[row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
co2_csv = pathlib.Path("owid-co2-data.csv")
co2_data = {}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
try:
if row["year"] == "2020" and row["iso_code"]:
co2_data[row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
except (KeyError, ValueError) as e:
print(f"Warning: Error processing row: {e}")



We prepare the styles and placemarks for the KML file, using random colors for each
country and the CO2 emissions as the height of the geometry. The shapefile offers
a handy ``__geo_interface__`` attribute that we can use to iterate over the features,
just like we would with a ``GeoJSON`` object, and extract the necessary information:

.. code-block:: python

placemarks = []
for feature in shp.__geo_interface__["features"]:
iso3_code = feature["properties"]["ADM0_A3"]
geometry = shape(feature["geometry"])
co2_emission = co2_data.get(iso3_code, 0)
geometry = force_3d(geometry, co2_emission * 100_000)
kml_geometry = create_kml_geometry(
geometry,
extrude=True,
altitude_mode=AltitudeMode.relative_to_ground,
)
color = random.randint(0, 0xFFFFFF)
style = fastkml.styles.Style(
id=iso3_code,
styles=[
fastkml.styles.LineStyle(color=f"33{color:06X}", width=2),
fastkml.styles.PolyStyle(
color=f"88{color:06X}",
fill=True,
outline=True,
),
],
)
placemark = fastkml.features.Placemark(
name=feature["properties"]["NAME"],
description=feature["properties"]["FORMAL_EN"],
kml_geometry=kml_geometry,
styles=[style],
)
placemarks.append(placemark)


Finally, we create the KML object and write it to a file:

.. code-block:: python

document = fastkml.containers.Document(features=placemarks, styles=styles)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix undefined variable usage.

The styles variable used in Document(features=placemarks, styles=styles) is undefined. It should be a list of styles created for each placemark.

Apply this diff to fix the issue:

-    document = fastkml.containers.Document(features=placemarks, styles=styles)
+    document = fastkml.containers.Document(features=placemarks)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document = fastkml.containers.Document(features=placemarks, styles=styles)
document = fastkml.containers.Document(features=placemarks)

kml = fastkml.KML(features=[document])

outfile = pathlib.Path("co2_per_capita_2020.kml")
with outfile.open("w") as f:
f.write(kml.to_string(prettyprint=True, precision=6))

The resulting KML file can be opened in Google Earth or any other KML viewer.

.. image:: co2-per-capita-2020.jpg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (documentation): Image filename inconsistency with examples/README.md

The image is referenced as 'co2_per_capita.jpg' in examples/README.md but as 'co2-per-capita-2020.jpg' here.

:alt: CO2 emissions per capita in 2020
:align: center
:width: 800px


Build an animated over time KML visualization
----------------------------------------------

This example shows how to build an animated KML visualization over time.
We will use the same data as in the previous example, but this time we will
create a KML file that shows the CO2 emissions accumulating from 1995 to 2022.

First we import the necessary modules:

.. code-block:: python

import csv
import pathlib
import random
import datetime
import shapefile
from pygeoif.factories import force_3d
from pygeoif.factories import shape

import fastkml
import fastkml.containers
import fastkml.features
import fastkml.styles
import fastkml.times
from fastkml.enums import AltitudeMode, DateTimeResolution
from fastkml.geometry import create_kml_geometry

Read the shapefile, the CSV file and store the CO2 data for each year:

.. code-block:: python

shp = shapefile.Reader("ne_110m_admin_0_countries.shp")
co2_csv = pathlib.Path("owid-co2-data.csv")
co2_pa = {str(i): {} for i in range(1995, 2023)}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
if row["year"] >= "1995":
co2_pa[row["year"]][row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
Comment on lines +148 to +155
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for CSV data parsing.

The CSV parsing code should handle potential errors such as missing or malformed data.

Consider adding try-except blocks:

     with co2_csv.open() as csvfile:
         reader = csv.DictReader(csvfile)
         for row in reader:
-            if row["year"] >= "1995":
-                co2_pa[row["year"]][row["iso_code"]] = (
-                    float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
-                )
+            try:
+                if row["year"] >= "1995" and row["iso_code"] and row["year"] in co2_pa:
+                    co2_pa[row["year"]][row["iso_code"]] = (
+                        float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
+                    )
+            except (KeyError, ValueError) as e:
+                print(f"Warning: Error processing row: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
co2_pa = {str(i): {} for i in range(1995, 2023)}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
if row["year"] >= "1995":
co2_pa[row["year"]][row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
co2_pa = {str(i): {} for i in range(1995, 2023)}
with co2_csv.open() as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
try:
if row["year"] >= "1995" and row["iso_code"] and row["year"] in co2_pa:
co2_pa[row["year"]][row["iso_code"]] = (
float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
)
except (KeyError, ValueError) as e:
print(f"Warning: Error processing row: {e}")




This time we will create a folder for each country, and a placemark for each year,
with the CO2 emissions per capita as the height of the geometry.
We will also create a style for each country, which we store at the document level to
prevent creating duplicate styles.
Each placemark will have a time-span that covers the whole year:

.. code-block:: python

styles = []
folders = []
for feature in shp.__geo_interface__["features"]:
iso3_code = feature["properties"]["ADM0_A3"]
geometry = shape(feature["geometry"])
color = random.randint(0, 0xFFFFFF)
styles.append(
fastkml.styles.Style(
id=iso3_code,
styles=[
fastkml.styles.LineStyle(color=f"33{color:06X}", width=2),
fastkml.styles.PolyStyle(
color=f"88{color:06X}",
fill=True,
outline=True,
),
],
),
)
style_url = fastkml.styles.StyleUrl(url=f"#{iso3_code}")
folder = fastkml.containers.Folder(name=feature["properties"]["NAME"])
co2_growth = 0
for year in range(1995, 2023):
co2_year = co2_pa[str(year)].get(iso3_code, 0)
co2_growth += co2_year

kml_geometry = create_kml_geometry(
force_3d(geometry, co2_growth * 5_000),
extrude=True,
altitude_mode=AltitudeMode.relative_to_ground,
)
timespan = fastkml.times.TimeSpan(
begin=fastkml.times.KmlDateTime(
datetime.date(year, 1, 1), resolution=DateTimeResolution.year_month
),
end=fastkml.times.KmlDateTime(
datetime.date(year, 12, 31), resolution=DateTimeResolution.year_month
),
)
placemark = fastkml.features.Placemark(
name=f"{feature['properties']['NAME']} - {year}",
description=feature["properties"]["FORMAL_EN"],
kml_geometry=kml_geometry,
style_url=style_url,
times=timespan,
)
folder.features.append(placemark)
folders.append(folder)

Finally, we create the KML object and write it to a file:

.. code-block:: python

document = fastkml.containers.Document(features=folders, styles=styles)
kml = fastkml.KML(features=[document])

outfile = pathlib.Path("co2_growth_1995_2022.kml")
with outfile.open("w") as f:
f.write(kml.to_string(prettyprint=True, precision=3))


You can open the resulting KML file in Google Earth Desktop and use the time slider to
see the CO2 emissions per capita grow over time, Google Earth Web does not support
time animations.
26 changes: 7 additions & 19 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
Welcome to FastKML's documentation!
===================================

``fastkml`` is a library to read, write and manipulate KML files. It aims to keep
it simple and fast (using lxml_ if available). "Fast" refers to the time you
spend to write and read KML files, as well as the time you spend to get
acquainted with the library or to create KML objects. It provides a subset of KML
and is aimed at documents that can be read from multiple clients such as
openlayers and google maps rather than to give you all functionality that KML
on google earth provides.

For more details about the KML Specification, check out the `KML Reference
<https://developers.google.com/kml/documentation/kmlreference>`_ on the Google
developers site.
.. include:: ../README.rst
:start-after: inclusion-marker-do-not-remove

Rationale
---------
Expand All @@ -26,18 +17,15 @@ requirements, namely:
* It is fully tested and actively maintained.
* Geometries are handled in the ``__geo_interface__`` standard.
* Minimal dependencies, pure Python.
* If available, lxml_ will be used to increase its speed.
* If available, ``lxml`` will be used to increase its speed.

.. toctree::
:maxdepth: 2

quickstart
installing
usage_guide
reference_guide
create_kml_files
working_with_kml
configuration
modules
contributing

.. _lxml: https://pypi.python.org/pypi/lxml
.. _tox: https://pypi.python.org/pypi/tox
.. _kml_reference: https://developers.google.com/kml/documentation/kmlreference
HISTORY
24 changes: 0 additions & 24 deletions docs/installing.rst

This file was deleted.

5 changes: 3 additions & 2 deletions docs/modules.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
fastkml
=======
===============
Reference Guide
===============

.. toctree::
:maxdepth: 4
Expand Down
Loading
Loading