# Exercise 2#

Important

Please complete this exercise by the end of day on Thursday, 16 November, 2023 (the day before the next work session).

To start this assignment, accept the GitHub classroom assignment, and clone your own repository, e.g., in a CSC Notebook instance. Make sure you commit and push all changes you make (you can revisit instructions on how to use `git` and the JupyterLab git-plugin on the website of the Geo-Python course.

To preview the exercise without logging in, you can find the open course copy of the course’s GitHub repository at github.com/Automating-GIS-processes-II-2023/Exercise-2. Don’t attempt to commit changes to that repository, but rather work with your personal GitHub classroom copy (see above).

## Hints#

### Converting a `pandas.DataFrame` into a `geopandas.GeoDataFrame`#

Sometimes, we work with data that are in a non-spatial format (such as Excel or CSV spreadsheets) but contain information on the location of records, for instance, in columns for longitude and latitude values. While geopandas’s `read_file()` function can read some formats, often, the safest way is to use pandas to read the data set and then convert it to a `GeoDataFrame`.

Let’s assume, we read the following table using `pandas.read_csv()` into a variable `df`:

```df
```
longitude latitude
0 24.9557 60.1555
1 24.8353 60.1878
2 24.9587 60.2029

The `geopandas.GeoDataFrame()` constructor accepts a `pandas.DataFrame` as an input, but it does not automatically fill the `geometry` column. However, the library comes with a handy helper function `geopandas.points_from_xy()`. As we all know, a spatial data set should always have a coordinate reference system (CRS) defined; we can specify the CRS of the input data, here, too:

```import geopandas

gdf = geopandas.GeoDataFrame(
df,
geometry=geopandas.points_from_xy(df.longitude, df.latitude),
crs="EPSG:4326"
)

gdf
```
longitude latitude geometry
0 24.9557 60.1555 POINT (24.95570 60.15550)
1 24.8353 60.1878 POINT (24.83530 60.18780)
2 24.9587 60.2029 POINT (24.95870 60.20290)

Now, we have a ‘proper‘ `GeoDataFrame` with which we can do all geospatial operations we would want to do.

### Creating a new `geopandas.GeoDataFrame`: alternative 1#

Sometimes, it makes sense to start from scratch with an empty data set and gradually add records. Of course, this is also possible with geopandas’ data frames, that can then be saved as a new geopackage or shapefile.

First, create a completely empty `GeoDataFrame`:

```import geopandas

new_geodataframe = geopandas.GeoDataFrame()
```

Then, create shapely geometry objects and insert them into the data frame. To insert a geometry object into the `geometry` column, and a name into the `name` column, in a newly added row, use:

```import shapely.geometry
polygon = shapely.geometry.Polygon(
[
(24.9510, 60.1690),
(24.9510, 60.1698),
(24.9536, 60.1698),
(24.9536, 60.1690)
]
)
name = "Senaatintori"

new_geodataframe.loc[
len(new_geodataframe),  # in which row,
["name", "geometry"]    # in which columns to save values
] = [name, polygon]

new_geodataframe
```
name geometry
0 Senaatintori POLYGON ((24.95100 60.16900, 24.95100 60.16980...

Before saving the newly created dataset, don’t forget to define a cartographic reference system for it. Otherwise, you will have trouble reusing the file in other programs:

```new_geodataframe.crs = "EPSG:4326"
```

Hint

In the example above, we used the `len(new_geodataframe)` as a row index (which, in a newly created data frame is equivalent to the row number). Since rows are counted from 0, the number of rows (length of data frame) is one greater than the address of the last row. This expression, thus, always adds a new row, independent of the actual length of the data frame.

Note, that, strictly speaking, the index is independent from the row number, but in newly created data frames there are identical.

### Creating a new `geopandas.GeoDataFrame`: alternative 2#

Often, it is more convenient, and more elegant, to first create a dictionary to collect data, that can then be converted into a data frame all at once.

For this, first define a `dict` with the column names as keys, and empty `list`s as values:

```data = {
"name": [],
"geometry": []
}
```

Then, fill the dict with data:

```import shapely.geometry

data["name"].append("Senaatintori")
data["geometry"].append(
shapely.geometry.Polygon(
[
(24.9510, 60.1690),
(24.9510, 60.1698),
(24.9536, 60.1698),
(24.9536, 60.1690)
]
)
)
```

Finally, use this dictionary as input for a new `GeoDataFrame`. Don’t forget to specify a CRS:

```new_geodataframe = geopandas.GeoDataFrame(data, crs="EPSG:4326")
new_geodataframe
```
name geometry
0 Senaatintori POLYGON ((24.95100 60.16900, 24.95100 60.16980...

Note

These two approaches result in identical `GeoDataFrame`s. Sometimes, one technique is more convenient than the other. You should always evaluate different ways of solving a problem, and find the most appropriate and efficient solution (there is always more than one possible solution).