├── .gitignore ├── 01-introduction-geospatial-data.ipynb ├── 02-coordinate-reference-systems.ipynb ├── 03-spatial-relationships-joins.ipynb ├── 04-spatial-operations-overlays.ipynb ├── 05-more-on-visualization.ipynb ├── 06-scaling-geopandas-dask.ipynb ├── LICENSE ├── README.md ├── _solved ├── 01-introduction-geospatial-data.ipynb ├── 02-coordinate-reference-systems.ipynb ├── 03-spatial-relationships-joins.ipynb ├── 04-spatial-operations-overlays.ipynb ├── case-conflict-mapping.ipynb └── solutions │ ├── 01-introduction-geospatial-data1.py │ ├── 01-introduction-geospatial-data10.py │ ├── 01-introduction-geospatial-data11.py │ ├── 01-introduction-geospatial-data12.py │ ├── 01-introduction-geospatial-data13.py │ ├── 01-introduction-geospatial-data14.py │ ├── 01-introduction-geospatial-data15.py │ ├── 01-introduction-geospatial-data16.py │ ├── 01-introduction-geospatial-data17.py │ ├── 01-introduction-geospatial-data18.py │ ├── 01-introduction-geospatial-data19.py │ ├── 01-introduction-geospatial-data2.py │ ├── 01-introduction-geospatial-data3.py │ ├── 01-introduction-geospatial-data4.py │ ├── 01-introduction-geospatial-data5.py │ ├── 01-introduction-geospatial-data6.py │ ├── 01-introduction-geospatial-data7.py │ ├── 01-introduction-geospatial-data8.py │ ├── 01-introduction-geospatial-data9.py │ ├── 02-coordinate-reference-systems1.py │ ├── 02-coordinate-reference-systems10.py │ ├── 02-coordinate-reference-systems11.py │ ├── 02-coordinate-reference-systems2.py │ ├── 02-coordinate-reference-systems3.py │ ├── 02-coordinate-reference-systems4.py │ ├── 02-coordinate-reference-systems5.py │ ├── 02-coordinate-reference-systems6.py │ ├── 02-coordinate-reference-systems7.py │ ├── 02-coordinate-reference-systems8.py │ ├── 02-coordinate-reference-systems9.py │ ├── 03-spatial-relationships-joins1.py │ ├── 03-spatial-relationships-joins10.py │ ├── 03-spatial-relationships-joins11.py │ ├── 03-spatial-relationships-joins12.py │ ├── 03-spatial-relationships-joins13.py │ ├── 03-spatial-relationships-joins14.py │ ├── 03-spatial-relationships-joins15.py │ ├── 03-spatial-relationships-joins16.py │ ├── 03-spatial-relationships-joins17.py │ ├── 03-spatial-relationships-joins18.py │ ├── 03-spatial-relationships-joins19.py │ ├── 03-spatial-relationships-joins2.py │ ├── 03-spatial-relationships-joins3.py │ ├── 03-spatial-relationships-joins4.py │ ├── 03-spatial-relationships-joins5.py │ ├── 03-spatial-relationships-joins6.py │ ├── 03-spatial-relationships-joins7.py │ ├── 03-spatial-relationships-joins8.py │ ├── 03-spatial-relationships-joins9.py │ ├── 04-spatial-operations-overlays1.py │ ├── 04-spatial-operations-overlays10.py │ ├── 04-spatial-operations-overlays11.py │ ├── 04-spatial-operations-overlays12.py │ ├── 04-spatial-operations-overlays13.py │ ├── 04-spatial-operations-overlays14.py │ ├── 04-spatial-operations-overlays15.py │ ├── 04-spatial-operations-overlays16.py │ ├── 04-spatial-operations-overlays17.py │ ├── 04-spatial-operations-overlays18.py │ ├── 04-spatial-operations-overlays19.py │ ├── 04-spatial-operations-overlays2.py │ ├── 04-spatial-operations-overlays20.py │ ├── 04-spatial-operations-overlays21.py │ ├── 04-spatial-operations-overlays22.py │ ├── 04-spatial-operations-overlays23.py │ ├── 04-spatial-operations-overlays24.py │ ├── 04-spatial-operations-overlays25.py │ ├── 04-spatial-operations-overlays26.py │ ├── 04-spatial-operations-overlays27.py │ ├── 04-spatial-operations-overlays28.py │ ├── 04-spatial-operations-overlays29.py │ ├── 04-spatial-operations-overlays3.py │ ├── 04-spatial-operations-overlays30.py │ ├── 04-spatial-operations-overlays31.py │ ├── 04-spatial-operations-overlays32.py │ ├── 04-spatial-operations-overlays4.py │ ├── 04-spatial-operations-overlays5.py │ ├── 04-spatial-operations-overlays6.py │ ├── 04-spatial-operations-overlays7.py │ ├── 04-spatial-operations-overlays8.py │ ├── 04-spatial-operations-overlays9.py │ ├── case-conflict-mapping10.py │ ├── case-conflict-mapping11.py │ ├── case-conflict-mapping12.py │ ├── case-conflict-mapping13.py │ ├── case-conflict-mapping14.py │ ├── case-conflict-mapping15.py │ ├── case-conflict-mapping16.py │ ├── case-conflict-mapping19.py │ ├── case-conflict-mapping20.py │ ├── case-conflict-mapping21.py │ ├── case-conflict-mapping22.py │ ├── case-conflict-mapping23.py │ ├── case-conflict-mapping24.py │ ├── case-conflict-mapping25.py │ ├── case-conflict-mapping26.py │ ├── case-conflict-mapping27.py │ ├── case-conflict-mapping28.py │ ├── case-conflict-mapping29.py │ ├── case-conflict-mapping3.py │ ├── case-conflict-mapping30.py │ ├── case-conflict-mapping31.py │ ├── case-conflict-mapping32.py │ ├── case-conflict-mapping33.py │ ├── case-conflict-mapping34.py │ ├── case-conflict-mapping35.py │ ├── case-conflict-mapping36.py │ ├── case-conflict-mapping37.py │ ├── case-conflict-mapping38.py │ ├── case-conflict-mapping39.py │ ├── case-conflict-mapping4.py │ ├── case-conflict-mapping40.py │ ├── case-conflict-mapping41.py │ └── case-conflict-mapping5.py ├── case-conflict-mapping.ipynb ├── check_environment.py ├── data ├── cod_conservation.zip ├── cod_mines_curated_all_opendata_p_ipis.geojson ├── data-preparation.ipynb ├── ne_110m_admin_0_countries.zip ├── ne_110m_populated_places.zip ├── ne_50m_rivers_lake_centerlines.zip ├── paris_bike_stations.geojson ├── paris_bike_stations_mercator.gpkg ├── paris_districts.geojson ├── paris_districts_utm.geojson ├── paris_land_use.zip ├── paris_sharing_bike_stations_utm.geojson ├── paris_trees.gpkg └── raw │ └── paris-population.csv ├── environment.yml ├── img ├── GDALLogoColor.svg ├── Open_Source_Geospatial_Foundation.svg ├── TopologicSpatialRelarions2.png ├── download-button.png ├── gdal_formats ├── gdal_users ├── geos.gif ├── illustration-spatial-join.svg ├── inria-logo.png ├── logoUPSayPlusCDS_990.png ├── mercator_projection_area.gif ├── overlay-both.png ├── overlay-countries-circle-intersection.png ├── overlay-countries.png ├── overlay-overlayed.png ├── overlay-regions.png ├── pandas_logo.svg ├── projection.png ├── projections-AlbersEqualArea.png ├── projections-Mercator.png ├── projections-Robinson.png ├── raster_example.png ├── remark.min.js ├── simple_features_3_text.svg ├── slides.css ├── spatial-operations-base.png ├── spatial-operations-buffer-line.png ├── spatial-operations-buffer-point1.png ├── spatial-operations-buffer-point2.png ├── spatial-operations-buffer-polygon.png ├── spatial-operations-difference.png ├── spatial-operations-intersection.png ├── spatial-operations-union.png ├── vector_example.png ├── webfont-ubuntu-400-300-100.css └── webfont-ubuntu-mono-400-700-400italic.css └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # geopandas-tutorial specific ignores 104 | data/raw/* 105 | -------------------------------------------------------------------------------- /02-coordinate-reference-systems.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "

Coordinate reference systems

" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%matplotlib inline\n", 17 | "\n", 18 | "import pandas as pd\n", 19 | "import geopandas" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "countries = geopandas.read_file(\"data/ne_110m_admin_0_countries.zip\")\n", 29 | "cities = geopandas.read_file(\"data/ne_110m_populated_places.zip\")\n", 30 | "rivers = geopandas.read_file(\"data/ne_50m_rivers_lake_centerlines.zip\")" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Coordinate reference systems\n", 38 | "\n", 39 | "Up to now, we have used the geometry data with certain coordinates without further wondering what those coordinates mean or how they are expressed.\n", 40 | "\n", 41 | "> The **Coordinate Reference System (CRS)** relates the coordinates to a specific location on earth.\n", 42 | "\n", 43 | "For an in-depth explanation, see https://docs.qgis.org/2.8/en/docs/gentle_gis_introduction/coordinate_reference_systems.html" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "### Geographic coordinates\n", 51 | "\n", 52 | "> Degrees of latitude and longitude.\n", 53 | ">\n", 54 | "> E.g. 48°51′N, 2°17′E\n", 55 | "\n", 56 | "The most known type of coordinates are geographic coordinates: we define a position on the globe in degrees of latitude and longitude, relative to the equator and the prime meridian. \n", 57 | "With this system, we can easily specify any location on earth. It is used widely, for example in GPS. If you inspect the coordinates of a location in Google Maps, you will also see latitude and longitude.\n", 58 | "\n", 59 | "**Attention!**\n", 60 | "\n", 61 | "in Python we use (lon, lat) and not (lat, lon)\n", 62 | "\n", 63 | "- Longitude: [-180, 180]{{1}}\n", 64 | "- Latitude: [-90, 90]{{1}}" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "### Projected coordinates\n", 72 | "\n", 73 | "> `(x, y)` coordinates are usually in meters or feet\n", 74 | "\n", 75 | "Although the earth is a globe, in practice we usually represent it on a flat surface: think about a physical map, or the figures we have made with Python on our computer screen.\n", 76 | "Going from the globe to a flat map is what we call a *projection*.\n", 77 | "\n", 78 | "![](img/projection.png)\n", 79 | "\n", 80 | "We project the surface of the earth onto a 2D plane so we can express locations in cartesian x and y coordinates, on a flat surface. In this plane, we then typically work with a length unit such as meters instead of degrees, which makes the analysis more convenient and effective.\n", 81 | "\n", 82 | "However, there is an important remark: the 3 dimensional earth can never be represented perfectly on a 2 dimensional map, so projections inevitably introduce distortions. To minimize such errors, there are different approaches to project, each with specific advantages and disadvantages.\n", 83 | "\n", 84 | "Some projection systems will try to preserve the area size of geometries, such as the Albers Equal Area projection. Other projection systems try to preserve angles, such as the Mercator projection, but will see big distortions in the area. Every projection system will always have some distortion of area, angle or distance.\n", 85 | "\n", 86 | "\n", 87 | "\n", 88 | "\n", 89 | "\n", 90 | "\n", 91 | "\n", 92 | "
" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "**Projected size vs actual size (Mercator projection)**:\n", 100 | "\n", 101 | "![](img/mercator_projection_area.gif)" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Coordinate Reference Systems in Python / GeoPandas" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "A GeoDataFrame or GeoSeries has a `.crs` attribute which holds (optionally) a description of the coordinate reference system of the geometries:" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": { 122 | "jupyter": { 123 | "outputs_hidden": false 124 | } 125 | }, 126 | "outputs": [], 127 | "source": [ 128 | "countries.crs" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "For the `countries` dataframe, it indicates that it uses the EPSG 4326 / WGS84 lon/lat reference system, which is one of the most used for geographic coordinates.\n", 136 | "\n", 137 | "\n", 138 | "It uses coordinates as latitude and longitude in degrees, as can you be seen from the x/y labels on the plot:" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": { 145 | "jupyter": { 146 | "outputs_hidden": false 147 | } 148 | }, 149 | "outputs": [], 150 | "source": [ 151 | "countries.plot()" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "The `.crs` attribute returns a `pyproj.CRS` object. To specify a CRS, we typically use some string representation:\n", 159 | "\n", 160 | "\n", 161 | "- **EPSG code**\n", 162 | " \n", 163 | " Example: `EPSG:4326` = WGS84 geographic CRS (longitude, latitude)\n", 164 | " \n", 165 | "- **Well-Know-Text (WKT)** representation\n", 166 | "\n", 167 | "- In older software and datasets, you might also encounter a \"`proj4` string\" representation:\n", 168 | " \n", 169 | " Example: `+proj=longlat +datum=WGS84 +no_defs`\n", 170 | "\n", 171 | " This is however no longer recommended.\n", 172 | "\n", 173 | "\n", 174 | "See eg https://epsg.io/4326\n", 175 | "\n", 176 | "Under the hood, GeoPandas uses the `pyproj` / `PROJ` libraries to deal with the re-projections.\n", 177 | "\n", 178 | "For more information, see also http://geopandas.readthedocs.io/en/latest/projections.html." 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "### Transforming to another CRS\n", 186 | "\n", 187 | "We can convert a GeoDataFrame to another reference system using the `to_crs` function. \n", 188 | "\n", 189 | "For example, let's convert the countries to the World Mercator projection (http://epsg.io/3395):" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "# remove Antartica, as the Mercator projection cannot deal with the poles\n", 199 | "countries = countries[(countries['name'] != \"Antarctica\")]" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "countries_mercator = countries.to_crs(epsg=3395) # or .to_crs(\"EPSG:3395\")" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": { 215 | "jupyter": { 216 | "outputs_hidden": false 217 | } 218 | }, 219 | "outputs": [], 220 | "source": [ 221 | "countries_mercator.plot()" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "metadata": {}, 227 | "source": [ 228 | "Note the different scale of x and y." 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "### Why using a different CRS?\n", 236 | "\n", 237 | "There are sometimes good reasons you want to change the coordinate references system of your dataset, for example:\n", 238 | "\n", 239 | "- Different sources with different CRS -> need to convert to the same crs\n", 240 | "\n", 241 | " ```python\n", 242 | " df1 = geopandas.read_file(...)\n", 243 | " df2 = geopandas.read_file(...)\n", 244 | "\n", 245 | " df2 = df2.to_crs(df1.crs)\n", 246 | " ```\n", 247 | "\n", 248 | "- Mapping (distortion of shape and distances)\n", 249 | "\n", 250 | "- Distance / area based calculations -> ensure you use an appropriate projected coordinate system expressed in a meaningful unit such as meters or feet (not degrees).\n", 251 | "\n", 252 | "
\n", 253 | "\n", 254 | "**ATTENTION:**\n", 255 | "\n", 256 | "All the calculations that happen in GeoPandas and Shapely assume that your data is in a 2D cartesian plane, and thus the result of those calculations will only be correct if your data is properly projected.\n", 257 | "\n", 258 | "
" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "## Let's practice!\n", 266 | "\n", 267 | "Again, we will go back to the Paris datasets. Up to now, we provided the datasets in an appropriate projected CRS for the exercises. But the original data were actually using geographic coordinates. In the following exercises, we will start from there.\n", 268 | "\n", 269 | "---" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "Going back to the Paris districts dataset, this is now provided as a GeoJSON file (`\"data/paris_districts.geojson\"`) in geographic coordinates.\n", 277 | "\n", 278 | "For converting to projected coordinates, we will use the standard projected CRS for France is the RGF93 / Lambert-93 reference system, referenced by the `EPSG:2154` number (in Belgium this would be Lambert 72, EPSG:31370).\n", 279 | "\n", 280 | "
\n", 281 | "\n", 282 | "**EXERCISE 1: Projecting a GeoDataFrame**\n", 283 | "\n", 284 | "* Read the districts datasets (`\"data/paris_districts.geojson\"`) into a GeoDataFrame called `districts`.\n", 285 | "* Look at the CRS attribute of the GeoDataFrame. Do you recognize the EPSG number?\n", 286 | "* Make a plot of the `districts` dataset.\n", 287 | "* Calculate the area of all districts.\n", 288 | "* Convert the `districts` to a projected CRS (using the `EPSG:2154` for France). Call the new dataset `districts_RGF93`.\n", 289 | "* Make a similar plot of `districts_RGF93`.\n", 290 | "* Calculate the area of all districts again with `districts_RGF93` (the result will now be expressed in m²).\n", 291 | " \n", 292 | " \n", 293 | "
Hints\n", 294 | "\n", 295 | "* The CRS information is stored in the `.crs` attribute of a GeoDataFrame.\n", 296 | "* Making a simple plot of a GeoDataFrame can be done with the `.plot()` method.\n", 297 | "* Converting to a different CRS can be done with the `.to_crs()` method, and the CRS can be specified as an EPSG number using the `epsg` keyword.\n", 298 | "\n", 299 | "
\n", 300 | "\n", 301 | "
" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": null, 307 | "metadata": { 308 | "tags": [ 309 | "nbtutor-solution" 310 | ] 311 | }, 312 | "outputs": [], 313 | "source": [ 314 | "# %load _solved/solutions/02-coordinate-reference-systems1.py" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "metadata": { 321 | "jupyter": { 322 | "outputs_hidden": false 323 | }, 324 | "tags": [ 325 | "nbtutor-solution" 326 | ] 327 | }, 328 | "outputs": [], 329 | "source": [ 330 | "# %load _solved/solutions/02-coordinate-reference-systems2.py" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "metadata": { 337 | "jupyter": { 338 | "outputs_hidden": false 339 | }, 340 | "tags": [ 341 | "nbtutor-solution" 342 | ] 343 | }, 344 | "outputs": [], 345 | "source": [ 346 | "# %load _solved/solutions/02-coordinate-reference-systems3.py" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": null, 352 | "metadata": { 353 | "jupyter": { 354 | "outputs_hidden": false 355 | }, 356 | "tags": [ 357 | "nbtutor-solution" 358 | ] 359 | }, 360 | "outputs": [], 361 | "source": [ 362 | "# %load _solved/solutions/02-coordinate-reference-systems4.py" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": null, 368 | "metadata": { 369 | "jupyter": { 370 | "outputs_hidden": false 371 | }, 372 | "tags": [ 373 | "nbtutor-solution" 374 | ] 375 | }, 376 | "outputs": [], 377 | "source": [ 378 | "# %load _solved/solutions/02-coordinate-reference-systems5.py" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": null, 384 | "metadata": { 385 | "tags": [ 386 | "nbtutor-solution" 387 | ] 388 | }, 389 | "outputs": [], 390 | "source": [ 391 | "# %load _solved/solutions/02-coordinate-reference-systems6.py" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": null, 397 | "metadata": { 398 | "jupyter": { 399 | "outputs_hidden": false 400 | }, 401 | "tags": [ 402 | "nbtutor-solution" 403 | ] 404 | }, 405 | "outputs": [], 406 | "source": [ 407 | "# %load _solved/solutions/02-coordinate-reference-systems7.py" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": null, 413 | "metadata": { 414 | "jupyter": { 415 | "outputs_hidden": false 416 | }, 417 | "tags": [ 418 | "nbtutor-solution" 419 | ] 420 | }, 421 | "outputs": [], 422 | "source": [ 423 | "# %load _solved/solutions/02-coordinate-reference-systems8.py" 424 | ] 425 | }, 426 | { 427 | "cell_type": "markdown", 428 | "metadata": {}, 429 | "source": [ 430 | "
\n", 431 | "\n", 432 | "**EXERCISE 2:**\n", 433 | "\n", 434 | "In the previous notebook, we did an exercise on plotting the bike stations locations in Paris and adding a background map to it using the `contextily` package.\n", 435 | "\n", 436 | "Currently, `contextily` assumes that your data is in the Web Mercator projection, the system used by most web tile services. And in that first exercise, we provided the data in the appropriate CRS so you didn't need to care about this aspect.\n", 437 | "\n", 438 | "However, typically, your data will not come in Web Mercator (`EPSG:3857`) and you will have to align them with web tiles on your own.\n", 439 | " \n", 440 | "* Read the bike stations datasets (`\"data/paris_bike_stations.geojson\"`) into a GeoDataFrame called `stations`.\n", 441 | "* Convert the `stations` dataset to the Web Mercator projection (`EPSG:3857`). Call the result `stations_webmercator`, and inspect the result.\n", 442 | "* Make a plot of this projected dataset (specify the marker size to be 5) and add a background map using `contextily`.\n", 443 | "\n", 444 | " \n", 445 | "
Hints\n", 446 | "\n", 447 | "* Making a simple plot of a GeoDataFrame can be done with the `.plot()` method. This returns a matplotlib axes object.\n", 448 | "* The marker size can be specified with the `markersize` keyword if the `.plot()` method.\n", 449 | "* To add a background map, use the `contextily.add_basemap()` function. It takes the matplotlib `ax` to which to add a map as the first argument.\n", 450 | "\n", 451 | "
\n", 452 | "\n", 453 | "
" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": null, 459 | "metadata": { 460 | "jupyter": { 461 | "outputs_hidden": false 462 | }, 463 | "tags": [ 464 | "nbtutor-solution" 465 | ] 466 | }, 467 | "outputs": [], 468 | "source": [ 469 | "# %load _solved/solutions/02-coordinate-reference-systems9.py" 470 | ] 471 | }, 472 | { 473 | "cell_type": "code", 474 | "execution_count": null, 475 | "metadata": { 476 | "jupyter": { 477 | "outputs_hidden": false 478 | }, 479 | "tags": [ 480 | "nbtutor-solution" 481 | ] 482 | }, 483 | "outputs": [], 484 | "source": [ 485 | "# %load _solved/solutions/02-coordinate-reference-systems10.py" 486 | ] 487 | }, 488 | { 489 | "cell_type": "code", 490 | "execution_count": null, 491 | "metadata": { 492 | "jupyter": { 493 | "outputs_hidden": false 494 | }, 495 | "tags": [ 496 | "nbtutor-solution" 497 | ] 498 | }, 499 | "outputs": [], 500 | "source": [ 501 | "# %load _solved/solutions/02-coordinate-reference-systems11.py" 502 | ] 503 | } 504 | ], 505 | "metadata": { 506 | "celltoolbar": "Nbtutor - export exercises", 507 | "kernelspec": { 508 | "display_name": "Python 3 (ipykernel)", 509 | "language": "python", 510 | "name": "python3" 511 | }, 512 | "language_info": { 513 | "codemirror_mode": { 514 | "name": "ipython", 515 | "version": 3 516 | }, 517 | "file_extension": ".py", 518 | "mimetype": "text/x-python", 519 | "name": "python", 520 | "nbconvert_exporter": "python", 521 | "pygments_lexer": "ipython3", 522 | "version": "3.10.6" 523 | }, 524 | "widgets": { 525 | "application/vnd.jupyter.widget-state+json": { 526 | "state": {}, 527 | "version_major": 2, 528 | "version_minor": 0 529 | } 530 | } 531 | }, 532 | "nbformat": 4, 533 | "nbformat_minor": 4 534 | } 535 | -------------------------------------------------------------------------------- /05-more-on-visualization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Visualizing spatial data with Python" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%matplotlib inline\n", 17 | "\n", 18 | "import pandas as pd\n", 19 | "import geopandas\n", 20 | "\n", 21 | "import matplotlib.pyplot as plt" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "countries = geopandas.read_file(\"data/ne_110m_admin_0_countries.zip\")\n", 31 | "cities = geopandas.read_file(\"data/ne_110m_populated_places.zip\")\n", 32 | "rivers = geopandas.read_file(\"data/ne_50m_rivers_lake_centerlines.zip\")" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "## GeoPandas visualization functionality\n", 40 | "\n", 41 | "GeoPandas itself provides some visualization functionality, and together with matplotlib for further customization, you can already get decent results for visualizing vector data." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "#### Basic plot" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "countries.plot()" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "#### Adjusting the figure size" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "countries.plot(figsize=(15, 15))" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "#### Removing the box / x and y coordinate labels" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "ax = countries.plot(figsize=(15, 15))\n", 90 | "ax.set_axis_off()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "#### Coloring based on column values\n", 98 | "\n", 99 | "Let's first create a new column with the GDP per capita:" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": null, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "countries = countries[(countries['pop_est'] >0 ) & (countries['name'] != \"Antarctica\")].copy()" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "countries['gdp_per_cap'] = countries['gdp_md_est'] / countries['pop_est'] * 100" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "and now we can use this column to color the polygons:" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "ax = countries.plot(figsize=(15, 15), column='gdp_per_cap')\n", 134 | "ax.set_axis_off()" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "Using a classification scheme to bin the values (using [`mapclassify`](https://pysal.org/mapclassify/)):" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "ax = countries.plot(figsize=(15, 15), column='gdp_per_cap', scheme='quantiles', legend=True)\n", 151 | "ax.set_axis_off()" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "#### Combining different dataframes on a single plot\n", 159 | "\n", 160 | "The `.plot` method returns a matplotlib Axes object, which can then be re-used to add additional layers to that plot with the `ax=` keyword:" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "ax = countries.plot(figsize=(15, 15))\n", 170 | "cities.plot(ax=ax, color='red', markersize=10)\n", 171 | "ax.set_axis_off()" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "ax = countries.plot(edgecolor='k', facecolor='none', figsize=(15, 10))\n", 181 | "rivers.plot(ax=ax)\n", 182 | "cities.plot(ax=ax, color='C1')\n", 183 | "ax.set(xlim=(-20, 60), ylim=(-40, 40))" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "## Adding a background map with contextily" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "The contextily package allow to easily add a web-tile based backgroubd (basemap) to your GeoPandas plots.\n", 198 | "\n", 199 | "Currently, the only requirement is that your data is already in the WebMercator projection (EPSG:3857)." 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "# selecting the cities in Europe\n", 209 | "cities_europe = cities[cities.within(countries[countries['continent'] == 'Europe'].unary_union)]" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "# converting to WebMercator\n", 219 | "cities_europe2 = cities_europe.to_crs(epsg=3857)" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "ax = cities_europe2.plot()" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "metadata": {}, 235 | "outputs": [], 236 | "source": [ 237 | "import contextily" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "metadata": {}, 244 | "outputs": [], 245 | "source": [ 246 | "ax = cities_europe2.plot(figsize=(10, 6))\n", 247 | "contextily.add_basemap(ax)" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "ax = cities_europe2.plot(figsize=(10, 6))\n", 257 | "contextily.add_basemap(ax, url=contextily.providers.Stamen.TonerLite)" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "metadata": {}, 263 | "source": [ 264 | "## Projection-aware maps with Cartopy\n", 265 | "\n", 266 | "Cartopy is the base matplotlib cartographic library, and it is used by `geoplot` under the hood to provide projection-awareness (http://scitools.org.uk/cartopy/docs/latest/index.html).\n", 267 | "\n", 268 | "The following example is taken from the docs: http://geopandas.readthedocs.io/en/latest/gallery/cartopy_convert.html#sphx-glr-gallery-cartopy-convert-py" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "from cartopy import crs as ccrs" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "# Define the CartoPy CRS object.\n", 287 | "crs = ccrs.AlbersEqualArea()\n", 288 | "\n", 289 | "# This can be converted into a `proj4` string/dict compatible with GeoPandas\n", 290 | "crs_proj4 = crs.proj4_init\n", 291 | "countries_ae = countries.to_crs(crs_proj4)" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": null, 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "# Here's what the plot looks like in GeoPandas\n", 301 | "countries_ae.plot()" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": null, 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "# Here's what the plot looks like when plotting with cartopy\n", 311 | "fig, ax = plt.subplots(subplot_kw={'projection': crs})\n", 312 | "ax.add_geometries(countries_ae['geometry'], crs=crs)" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": null, 318 | "metadata": {}, 319 | "outputs": [], 320 | "source": [ 321 | "# Here's what the plot looks like when plotting with cartopy and geopandas combined\n", 322 | "fig, ax = plt.subplots(subplot_kw={'projection': crs})\n", 323 | "countries_ae['geometry'].plot(ax=ax)" 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "## Using `geoplot`\n", 331 | "\n", 332 | "The `geoplot` packages provides some additional functionality compared to the basic `.plot()` method on GeoDataFrames:\n", 333 | "\n", 334 | "- High-level plotting API (with more plot types as geopandas)\n", 335 | "- Native projection support through cartopy\n", 336 | "\n", 337 | "https://residentmario.github.io/geoplot/index.html" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": null, 343 | "metadata": {}, 344 | "outputs": [], 345 | "source": [ 346 | "import geoplot\n", 347 | "import geoplot.crs as gcrs" 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": null, 353 | "metadata": {}, 354 | "outputs": [], 355 | "source": [ 356 | "fig, ax = plt.subplots(figsize=(10, 10), subplot_kw={\n", 357 | " 'projection': gcrs.Orthographic(central_latitude=40.7128, central_longitude=-74.0059)\n", 358 | "})\n", 359 | "geoplot.choropleth(countries, hue='gdp_per_cap', projection=gcrs.Orthographic(), ax=ax,\n", 360 | " cmap='magma', linewidth=0.5, edgecolor='white')\n", 361 | "ax.set_global()\n", 362 | "ax.spines['geo'].set_visible(True)\n", 363 | "#ax.coastlines()" 364 | ] 365 | }, 366 | { 367 | "cell_type": "markdown", 368 | "metadata": {}, 369 | "source": [ 370 | "## Interactive web-based visualizations\n", 371 | "\n", 372 | "There are nowadays many libraries that target interactive web-based visualizations and that can handle geospatial data. Some packages with an example for each:\n", 373 | "\n", 374 | "- Bokeh: https://bokeh.pydata.org/en/latest/docs/gallery/texas.html\n", 375 | "- GeoViews (other interface to Bokeh/matplotlib): http://geo.holoviews.org\n", 376 | "- Altair: https://altair-viz.github.io/gallery/choropleth.html\n", 377 | "- Plotly: https://plot.ly/python/#maps\n", 378 | "- ..." 379 | ] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "metadata": {}, 384 | "source": [ 385 | "Another popular javascript library for online maps is [Leaflet.js](https://leafletjs.com/), and this has python bindings in the [folium](https://github.com/python-visualization/folium) and [ipyleaflet](https://github.com/jupyter-widgets/ipyleaflet) packages." 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "metadata": {}, 391 | "source": [ 392 | "An example with ipyleaflet:" 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": null, 398 | "metadata": {}, 399 | "outputs": [], 400 | "source": [ 401 | "import ipyleaflet" 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": null, 407 | "metadata": {}, 408 | "outputs": [], 409 | "source": [ 410 | "m = ipyleaflet.Map(center=[48.8566, 2.3429], zoom=6)\n", 411 | "\n", 412 | "layer = ipyleaflet.GeoJSON(data=cities.__geo_interface__)\n", 413 | "m.add_layer(layer)\n", 414 | "m" 415 | ] 416 | }, 417 | { 418 | "cell_type": "code", 419 | "execution_count": null, 420 | "metadata": {}, 421 | "outputs": [], 422 | "source": [ 423 | "m = ipyleaflet.Map(center=[48.8566, 2.3429], zoom=3)\n", 424 | "geo_data = ipyleaflet.GeoData(\n", 425 | " geo_dataframe = countries,\n", 426 | " style={'color': 'black', 'fillColor': '#3366cc', 'opacity':0.05, 'weight':1.9, 'dashArray':'2', 'fillOpacity':0.6},\n", 427 | " hover_style={'fillColor': 'red' , 'fillOpacity': 0.2},\n", 428 | " name = 'Countries')\n", 429 | "m.add_layer(geo_data)\n", 430 | "m" 431 | ] 432 | }, 433 | { 434 | "cell_type": "markdown", 435 | "metadata": {}, 436 | "source": [ 437 | "More: https://ipyleaflet.readthedocs.io/en/latest/api_reference/geodata.html" 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "metadata": {}, 443 | "source": [ 444 | "An example with folium:" 445 | ] 446 | }, 447 | { 448 | "cell_type": "code", 449 | "execution_count": null, 450 | "metadata": {}, 451 | "outputs": [], 452 | "source": [ 453 | "import folium" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": null, 459 | "metadata": {}, 460 | "outputs": [], 461 | "source": [ 462 | "m = folium.Map([48.8566, 2.3429], zoom_start=6, tiles=\"OpenStreetMap\")\n", 463 | "folium.GeoJson(countries).add_to(m)\n", 464 | "folium.GeoJson(cities).add_to(m)\n", 465 | "m" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": null, 471 | "metadata": {}, 472 | "outputs": [], 473 | "source": [ 474 | "m = folium.Map([0, 0], zoom_start=1)\n", 475 | "folium.Choropleth(geo_data=countries, data=countries, columns=['iso_a3', 'gdp_per_cap'],\n", 476 | " key_on='feature.properties.iso_a3', fill_color='BuGn', highlight=True).add_to(m)\n", 477 | "m" 478 | ] 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "metadata": {}, 483 | "source": [ 484 | "
\n", 485 | "\n", 486 | "**NOTE**:
\n", 487 | "\n", 488 | "Making a quick plot using Folium is now also available as the `.explore()` method on a GeoDataFrame or GeoSeries.\n", 489 | "\n", 490 | "See https://geopandas.org/en/stable/docs/user_guide/interactive_mapping.html for more examples.\n", 491 | "
" 492 | ] 493 | } 494 | ], 495 | "metadata": { 496 | "kernelspec": { 497 | "display_name": "Python 3 (ipykernel)", 498 | "language": "python", 499 | "name": "python3" 500 | }, 501 | "language_info": { 502 | "codemirror_mode": { 503 | "name": "ipython", 504 | "version": 3 505 | }, 506 | "file_extension": ".py", 507 | "mimetype": "text/x-python", 508 | "name": "python", 509 | "nbconvert_exporter": "python", 510 | "pygments_lexer": "ipython3", 511 | "version": "3.10.6" 512 | }, 513 | "widgets": { 514 | "application/vnd.jupyter.widget-state+json": { 515 | "state": {}, 516 | "version_major": 2, 517 | "version_minor": 0 518 | } 519 | } 520 | }, 521 | "nbformat": 4, 522 | "nbformat_minor": 4 523 | } 524 | -------------------------------------------------------------------------------- /06-scaling-geopandas-dask.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Scaling geospatial analysis with GeoPandas and Dask" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "For now, see https://dask-geopandas.readthedocs.io/en/stable/guide/basic-intro.html" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [] 23 | } 24 | ], 25 | "metadata": { 26 | "kernelspec": { 27 | "display_name": "Python 3 (ipykernel)", 28 | "language": "python", 29 | "name": "python3" 30 | }, 31 | "language_info": { 32 | "codemirror_mode": { 33 | "name": "ipython", 34 | "version": 3 35 | }, 36 | "file_extension": ".py", 37 | "mimetype": "text/x-python", 38 | "name": "python", 39 | "nbconvert_exporter": "python", 40 | "pygments_lexer": "ipython3", 41 | "version": "3.10.6" 42 | }, 43 | "widgets": { 44 | "application/vnd.jupyter.widget-state+json": { 45 | "state": {}, 46 | "version_major": 2, 47 | "version_minor": 0 48 | } 49 | } 50 | }, 51 | "nbformat": 4, 52 | "nbformat_minor": 4 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Joris Van den Bossche 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction to geospatial data analysis with GeoPandas and the PyData stack 2 | 3 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/jorisvandenbossche/geopandas-tutorial/main) 4 | 5 | ## Tutorial on geospatial data manipulation with Python 6 | 7 | This tutorial is an introduction to geospatial data analysis in Python, with a focus on tabular vector data using GeoPandas. 8 | It will introduce the different libraries to work with geospatial data and will cover munging geo-data and exploring relations over space. This includes importing data in different formats (e.g. shapefile, GeoJSON), visualizing, combining and tidying them up for analysis, exploring spatial relationships, ... and will use libraries such as pandas, geopandas, shapely, pyproj, matplotlib, ... 9 | 10 | The tutorial will cover the following topics, each of them using Jupyter notebooks and hands-on exercises with real-world data: 11 | 12 | 1. Introduction to vector data and GeoPandas 13 | 2. Visualizing geospatial data 14 | 3. Spatial relationships and joins 15 | 4. Spatial operations and overlays 16 | 5. Short showcase of parallel/distributed geospatial analysis with Dask 17 | 18 | This repository initially contained the teaching material for the geospatial data analysis tutorial 19 | at [GeoPython 2018](http://2018.geopython.net), May 7-9 2018, Basel, Switzerland, and was later updated and also 20 | used at [Scipy 2018](https://scipy2018.scipy.org/), [EuroScipy 2018](https://www.euroscipy.org/2018/), [GeoPython 2019](http://2019.geopython.net), [EuroScipy 2019](https://www.euroscipy.org/2019/), [EuroScipy 2022](https://www.euroscipy.org/2022/). 21 | 22 | 23 | ## Installation notes 24 | 25 | Following this tutorial will require recent installations of: 26 | 27 | - Python >= 3.9 28 | - pandas 29 | - geopandas >= 0.10.0 30 | - matplotlib 31 | - mapclassify 32 | - contextily 33 | - folium 34 | - [Jupyter Notebook or Lab](http://jupyter.org) 35 | - *(optional for mining sites case study)* rasterio, rasterstats 36 | - *(optional for visualisation showcase)* cartopy, geoplot, ipyleaflet 37 | 38 | If you do not yet have these packages installed, we recommend to use the [conda](http://conda.pydata.org/docs/intro.html) package manager to install all the requirements 39 | (you can install [miniconda](http://conda.pydata.org/miniconda.html) or install the (larger) Anaconda 40 | distribution, found at https://www.anaconda.com/download/). 41 | 42 | Using conda, we recommend to create a new environment with all packages using the 43 | following commands: 44 | 45 | ```bash 46 | # setting the configuation so all packages come from the conda-forge channel 47 | conda config --add channels conda-forge 48 | conda config --set channel_priority strict 49 | # navigate to the downloaded (or git cloned) material 50 | cd .../geopandas-tutorial/ 51 | # creating the environment 52 | conda env create --name geo-tutorial --file environment.yml 53 | # activating the environment 54 | conda activate geo-tutorial 55 | ``` 56 | 57 | For this, you need to already download the materials first (see below), as it 58 | makes use of the `environment.yml` file included in this repo. 59 | 60 | Alternatively, you can install the packages using conda manually, or you can 61 | use ``pip``, as long as you have the above packages installed. In that case, 62 | we refer to the installation instructions of the individual packages (note: 63 | this won't work on Windows out of the box). 64 | 65 | **Want to try out without installing anything?** You can use the "launch binder" link above at the top of this README, which will launch a notebook instance on Binder with all required libraries installed. 66 | 67 | 68 | ## Downloading the tutorial materials 69 | 70 | **Note**: *I am still updating the materials, so I recommend to only download the materials the morning before the tutorial starts, or to update your local copy then. To update a local copy, you can download the latest version again, or do a `git pull` if you are using git.* 71 | 72 | If you have git installed, you can get the tutorial materials by cloning this repo: 73 | 74 | git clone https://github.com/jorisvandenbossche/geopandas-tutorial.git 75 | 76 | Otherwise, you can download the repository as a .zip file by heading over 77 | to the GitHub repository (https://github.com/jorisvandenbossche/geopandas-tutorial) in 78 | your browser and click the green "Download" button in the upper right: 79 | 80 | ![](img/download-button.png) 81 | 82 | 83 | ## Test the tutorial environment 84 | 85 | To make sure everything was installed correctly, open a terminal, and change its directory (`cd`) so that your working directory is the tutorial materials you downloaded in the step above. Then enter the following: 86 | 87 | ```sh 88 | python check_environment.py 89 | ``` 90 | 91 | Make sure that this scripts prints "All good. Enjoy the tutorial!" 92 | 93 | -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data1.py: -------------------------------------------------------------------------------- 1 | stations = geopandas.read_file("data/paris_bike_stations_mercator.gpkg") -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data10.py: -------------------------------------------------------------------------------- 1 | districts = geopandas.read_file("data/paris_districts_utm.geojson") -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data11.py: -------------------------------------------------------------------------------- 1 | districts.head() -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data12.py: -------------------------------------------------------------------------------- 1 | districts.shape -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data13.py: -------------------------------------------------------------------------------- 1 | districts.plot(figsize=(12, 6)) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data14.py: -------------------------------------------------------------------------------- 1 | districts.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data15.py: -------------------------------------------------------------------------------- 1 | # dividing by 10^6 for showing km² 2 | districts['area'] = districts.geometry.area / 1e6 -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data16.py: -------------------------------------------------------------------------------- 1 | districts.sort_values(by='area', ascending=False) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data17.py: -------------------------------------------------------------------------------- 1 | # Add a population density column 2 | districts['population_density'] = districts['population'] / districts.geometry.area * 10**6 -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data18.py: -------------------------------------------------------------------------------- 1 | # Make a plot of the districts colored by the population density 2 | districts.plot(column='population_density', figsize=(12, 6), legend=True) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data19.py: -------------------------------------------------------------------------------- 1 | # As comparison, the misleading plot when not turning the population number into a density 2 | districts.plot(column='population', figsize=(12, 6), legend=True) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data2.py: -------------------------------------------------------------------------------- 1 | type(stations) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data3.py: -------------------------------------------------------------------------------- 1 | stations.head() -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data4.py: -------------------------------------------------------------------------------- 1 | stations.shape -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data5.py: -------------------------------------------------------------------------------- 1 | stations.plot(figsize=(12,6)) # or .explore() -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data6.py: -------------------------------------------------------------------------------- 1 | import contextily -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data7.py: -------------------------------------------------------------------------------- 1 | ax = stations.plot(figsize=(12,6), markersize=5) 2 | contextily.add_basemap(ax) -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data8.py: -------------------------------------------------------------------------------- 1 | stations['bike_stands'].hist() -------------------------------------------------------------------------------- /_solved/solutions/01-introduction-geospatial-data9.py: -------------------------------------------------------------------------------- 1 | stations.plot(figsize=(12, 6), column='available_bikes', legend=True) -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems1.py: -------------------------------------------------------------------------------- 1 | # Import the districts dataset 2 | districts = geopandas.read_file("data/paris_districts.geojson") -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems10.py: -------------------------------------------------------------------------------- 1 | # Convert to the Web Mercator projection 2 | stations_webmercator = stations.to_crs("EPSG:3857") 3 | stations.head() -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems11.py: -------------------------------------------------------------------------------- 1 | # Plot the stations with a background map 2 | import contextily 3 | ax = stations_webmercator.plot(markersize=5) 4 | contextily.add_basemap(ax) -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems2.py: -------------------------------------------------------------------------------- 1 | # Check the CRS information 2 | districts.crs -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems3.py: -------------------------------------------------------------------------------- 1 | # Show the first rows of the GeoDataFrame 2 | districts.head() -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems4.py: -------------------------------------------------------------------------------- 1 | # Plot the districts dataset 2 | districts.plot() -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems5.py: -------------------------------------------------------------------------------- 1 | # Calculate the area of all districts 2 | districts.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems6.py: -------------------------------------------------------------------------------- 1 | # Convert the districts to the RGF93 reference system 2 | districts_RGF93 = districts.to_crs(epsg=2154) # or to_crs("EPSG:2154") -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems7.py: -------------------------------------------------------------------------------- 1 | # Plot the districts dataset again 2 | districts_RGF93.plot() -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems8.py: -------------------------------------------------------------------------------- 1 | # Calculate the area of all districts (the result is now expressed in m²) 2 | districts_RGF93.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/02-coordinate-reference-systems9.py: -------------------------------------------------------------------------------- 1 | stations = geopandas.read_file("data/paris_bike_stations.geojson") 2 | stations.head() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins1.py: -------------------------------------------------------------------------------- 1 | # Construct a point object for the Eiffel Tower 2 | eiffel_tower = Point(648237.3, 6862271.9) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins10.py: -------------------------------------------------------------------------------- 1 | # Filter the bike stations closer than 1 km 2 | stations_eiffel = stations[dist_eiffel < 1000] -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins11.py: -------------------------------------------------------------------------------- 1 | joined = geopandas.sjoin(stations, districts[['district_name', 'geometry']], predicate='within') -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins12.py: -------------------------------------------------------------------------------- 1 | joined.head() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins13.py: -------------------------------------------------------------------------------- 1 | # Read the trees and districts data 2 | trees = geopandas.read_file("data/paris_trees.gpkg") 3 | districts = geopandas.read_file("data/paris_districts.geojson").to_crs(trees.crs) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins14.py: -------------------------------------------------------------------------------- 1 | # The trees dataset with point locations of trees 2 | trees.head() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins15.py: -------------------------------------------------------------------------------- 1 | # Spatial join of the trees and districts datasets 2 | joined = geopandas.sjoin(trees, districts, predicate='within') 3 | joined.head() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins16.py: -------------------------------------------------------------------------------- 1 | # Calculate the number of trees in each district 2 | trees_by_district = joined.groupby('district_name').size() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins17.py: -------------------------------------------------------------------------------- 1 | # Merge the 'districts' and 'trees_by_district' dataframes 2 | districts_trees = pd.merge(districts, trees_by_district, on='district_name') 3 | districts_trees.head() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins18.py: -------------------------------------------------------------------------------- 1 | # Add a column with the tree density 2 | districts_trees['n_trees_per_area'] = districts_trees['n_trees'] / districts_trees.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins19.py: -------------------------------------------------------------------------------- 1 | # Make of map of the districts colored by 'n_trees_per_area' 2 | ax = districts_trees.plot(column='n_trees_per_area', figsize=(12, 6)) 3 | ax.set_axis_off() -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins2.py: -------------------------------------------------------------------------------- 1 | # Print the result 2 | print(eiffel_tower) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins3.py: -------------------------------------------------------------------------------- 1 | # Is the Eiffel Tower located within the Montparnasse district? 2 | print(eiffel_tower.within(district_montparnasse)) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins4.py: -------------------------------------------------------------------------------- 1 | # Does the Montparnasse district contains the bike station? 2 | print(district_montparnasse.contains(bike_station)) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins5.py: -------------------------------------------------------------------------------- 1 | # The distance between the Eiffel Tower and the bike station? 2 | print(eiffel_tower.distance(bike_station)) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins6.py: -------------------------------------------------------------------------------- 1 | # Create a boolean Series 2 | mask = districts.contains(eiffel_tower) 3 | mask -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins7.py: -------------------------------------------------------------------------------- 1 | # Filter the districts with the boolean mask 2 | districts[mask] -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins8.py: -------------------------------------------------------------------------------- 1 | # The distance from each stations to the Eiffel Tower 2 | dist_eiffel = stations.distance(eiffel_tower) -------------------------------------------------------------------------------- /_solved/solutions/03-spatial-relationships-joins9.py: -------------------------------------------------------------------------------- 1 | # The distance to the closest station 2 | dist_eiffel.min() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays1.py: -------------------------------------------------------------------------------- 1 | # Take a buffer 2 | seine_buffer = seine.buffer(150) 3 | seine_buffer -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays10.py: -------------------------------------------------------------------------------- 1 | # Print proportion of district area that occupied park 2 | print(intersection.area / muette.area) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays11.py: -------------------------------------------------------------------------------- 1 | # Calculate the intersection of the land use polygons with Muette 2 | land_use_muette = land_use.geometry.intersection(muette) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays12.py: -------------------------------------------------------------------------------- 1 | # Print the first five rows of the intersection 2 | land_use_muette.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays13.py: -------------------------------------------------------------------------------- 1 | # Remove the empty geometries 2 | land_use_muette = land_use_muette[~land_use_muette.is_empty] -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays14.py: -------------------------------------------------------------------------------- 1 | # Print the first five rows of the intersection 2 | land_use_muette.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays15.py: -------------------------------------------------------------------------------- 1 | # Plot the intersection 2 | land_use_muette.plot(edgecolor='black') -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays16.py: -------------------------------------------------------------------------------- 1 | land_use_muette = land_use.copy() 2 | land_use_muette['geometry'] = land_use.geometry.intersection(muette) 3 | land_use_muette = land_use_muette[~land_use_muette.is_empty] 4 | land_use_muette.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays17.py: -------------------------------------------------------------------------------- 1 | land_use_muette.plot(column="class") #edgecolor="black") -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays18.py: -------------------------------------------------------------------------------- 1 | land_use_muette.dissolve(by='class') -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays19.py: -------------------------------------------------------------------------------- 1 | land_use_muette['area'] = land_use_muette.geometry.area 2 | # Total land use per class 3 | land_use_muette.groupby("class")["area"].sum() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays2.py: -------------------------------------------------------------------------------- 1 | # Use the intersection 2 | districts_seine = districts[districts.intersects(seine_buffer)] -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays20.py: -------------------------------------------------------------------------------- 1 | # Relative percentage of land use classes 2 | land_use_muette.groupby("class")["area"].sum() / land_use_muette.geometry.area.sum() * 100 -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays21.py: -------------------------------------------------------------------------------- 1 | # Overlay both datasets based on the intersection 2 | combined = geopandas.overlay(land_use, districts, how='intersection') -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays22.py: -------------------------------------------------------------------------------- 1 | # Print the first five rows of the result 2 | combined.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays23.py: -------------------------------------------------------------------------------- 1 | # Add the area as a column 2 | combined['area'] = combined.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays24.py: -------------------------------------------------------------------------------- 1 | # Take a subset for the Muette district 2 | land_use_muette = combined[combined['district_name'] == 'Muette'] -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays25.py: -------------------------------------------------------------------------------- 1 | # Visualize the land use of the Muette district 2 | land_use_muette.plot(column='class') -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays26.py: -------------------------------------------------------------------------------- 1 | # Calculate the total area for each land use class 2 | print(land_use_muette.groupby('class')['area'].sum() / 1000**2) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays27.py: -------------------------------------------------------------------------------- 1 | districts_area = combined.groupby("district_name")["area"].sum() 2 | districts_area.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays28.py: -------------------------------------------------------------------------------- 1 | urban_green = combined[combined["class"] == "Green urban areas"] -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays29.py: -------------------------------------------------------------------------------- 1 | urban_green_area = urban_green.groupby("district_name")["area"].sum() 2 | urban_green_area.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays3.py: -------------------------------------------------------------------------------- 1 | # Make a plot 2 | fig, ax = plt.subplots(figsize=(20, 10)) 3 | districts.plot(ax=ax, color='grey', alpha=0.4, edgecolor='k') 4 | districts_seine.plot(ax=ax, color='blue', alpha=0.4, edgecolor='k') 5 | s_seine_utm.plot(ax=ax) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays30.py: -------------------------------------------------------------------------------- 1 | urban_green_fraction = urban_green_area / districts_area * 100 -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays31.py: -------------------------------------------------------------------------------- 1 | urban_green_fraction.nlargest() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays32.py: -------------------------------------------------------------------------------- 1 | urban_green_fraction.nsmallest() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays4.py: -------------------------------------------------------------------------------- 1 | # Import the land use dataset 2 | land_use = geopandas.read_file("data/paris_land_use.zip") 3 | land_use.head() -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays5.py: -------------------------------------------------------------------------------- 1 | # Make a plot of the land use with 'class' as the color 2 | land_use.plot(column='class', legend=True, figsize=(15, 10)) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays6.py: -------------------------------------------------------------------------------- 1 | # Add the area as a new column 2 | land_use['area'] = land_use.geometry.area -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays7.py: -------------------------------------------------------------------------------- 1 | # Calculate the total area for each land use class 2 | total_area = land_use.groupby('class')['area'].sum() / 1000**2 3 | total_area -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays8.py: -------------------------------------------------------------------------------- 1 | # Calculate the intersection of both polygons 2 | intersection = park_boulogne.intersection(muette) -------------------------------------------------------------------------------- /_solved/solutions/04-spatial-operations-overlays9.py: -------------------------------------------------------------------------------- 1 | # Plot the intersection 2 | geopandas.GeoSeries([intersection]).plot() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping10.py: -------------------------------------------------------------------------------- 1 | protected_areas = geopandas.read_file("data/Conservation/RDC_aire_protegee_2013.shp") 2 | # or to read it directly from the zip file: 3 | # protected_areas = geopandas.read_file("/Conservation", vfs="zip://./data/cod_conservation.zip") -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping11.py: -------------------------------------------------------------------------------- 1 | protected_areas.plot() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping12.py: -------------------------------------------------------------------------------- 1 | from shapely.geometry import Point -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping13.py: -------------------------------------------------------------------------------- 1 | goma = Point(29.22, -1.66) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping14.py: -------------------------------------------------------------------------------- 1 | dist_goma = data.distance(goma) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping15.py: -------------------------------------------------------------------------------- 1 | dist_goma.nsmallest(5) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping16.py: -------------------------------------------------------------------------------- 1 | ax = protected_areas.plot() 2 | data.plot(ax=ax, color='C1') -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping19.py: -------------------------------------------------------------------------------- 1 | data_utm = data.to_crs(epsg=32735) 2 | protected_areas_utm = protected_areas.to_crs(epsg=32735) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping20.py: -------------------------------------------------------------------------------- 1 | ax = protected_areas_utm.plot() 2 | data_utm.plot(ax=ax, color='C1') -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping21.py: -------------------------------------------------------------------------------- 1 | ax = protected_areas_utm.plot(figsize=(10, 10), color='green') 2 | data_utm.plot(ax=ax, markersize=5, alpha=0.5) 3 | ax.set_axis_off() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping22.py: -------------------------------------------------------------------------------- 1 | # alternative with constructing the matplotlib figure first 2 | fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(aspect='equal')) 3 | protected_areas_utm.plot(ax=ax, color='green') 4 | data_utm.plot(ax=ax, markersize=5, alpha=0.5) 5 | ax.set_axis_off() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping23.py: -------------------------------------------------------------------------------- 1 | ax = protected_areas_utm.plot(figsize=(10, 10), color='green') 2 | data_utm.plot(ax=ax, markersize=5, alpha=0.5, column='interference') 3 | ax.set_axis_off() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping24.py: -------------------------------------------------------------------------------- 1 | ax = protected_areas_utm.plot(figsize=(10, 10), color='green') 2 | data_utm.plot(ax=ax, markersize=5, alpha=0.5, column='mineral1', legend=True) 3 | ax.set_axis_off() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping25.py: -------------------------------------------------------------------------------- 1 | kahuzi = protected_areas_utm[protected_areas_utm['NAME_AP'] == "Kahuzi-Biega National park"].geometry.squeeze() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping26.py: -------------------------------------------------------------------------------- 1 | mines_kahuzi = data_utm[data_utm.within(kahuzi)] 2 | mines_kahuzi -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping27.py: -------------------------------------------------------------------------------- 1 | len(mines_kahuzi) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping28.py: -------------------------------------------------------------------------------- 1 | single_mine = data_utm.geometry[0] -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping29.py: -------------------------------------------------------------------------------- 1 | dist = protected_areas_utm.distance(single_mine) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping3.py: -------------------------------------------------------------------------------- 1 | data_visits = geopandas.read_file("data/cod_mines_curated_all_opendata_p_ipis.geojson") -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping30.py: -------------------------------------------------------------------------------- 1 | idx = dist.idxmin() 2 | closest_area = protected_areas_utm.loc[idx, 'NAME_AP'] 3 | closest_area -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping31.py: -------------------------------------------------------------------------------- 1 | def closest_protected_area(mine, protected_areas): 2 | dist = protected_areas.distance(mine) 3 | idx = dist.idxmin() 4 | closest_area = protected_areas.loc[idx, 'NAME_AP'] 5 | return closest_area -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping32.py: -------------------------------------------------------------------------------- 1 | result = data_utm.geometry.apply(lambda site: closest_protected_area(site, protected_areas_utm)) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping33.py: -------------------------------------------------------------------------------- 1 | data_within_protected = geopandas.sjoin(data_utm, protected_areas_utm[['NAME_AP', 'geometry']], 2 | op='within', how='inner') -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping34.py: -------------------------------------------------------------------------------- 1 | len(data_within_protected) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping35.py: -------------------------------------------------------------------------------- 1 | data_within_protected['NAME_AP'].value_counts() 2 | # or data_within_protected.groupby('NAME_AP').size() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping36.py: -------------------------------------------------------------------------------- 1 | data_within_protected.groupby('NAME_AP')['workers_numb'].sum() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping37.py: -------------------------------------------------------------------------------- 1 | protected_areas_border = protected_areas_utm[['NAME_AP', 'geometry']].copy() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping38.py: -------------------------------------------------------------------------------- 1 | protected_areas_border['geometry'] = protected_areas_border.buffer(10000).difference(protected_areas_utm.unary_union) -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping39.py: -------------------------------------------------------------------------------- 1 | protected_areas_border.plot() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping4.py: -------------------------------------------------------------------------------- 1 | data_visits.head() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping40.py: -------------------------------------------------------------------------------- 1 | data_within_border = geopandas.sjoin(data_utm, protected_areas_border, 2 | op='within', how='inner') -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping41.py: -------------------------------------------------------------------------------- 1 | data_within_border['NAME_AP'].value_counts() -------------------------------------------------------------------------------- /_solved/solutions/case-conflict-mapping5.py: -------------------------------------------------------------------------------- 1 | len(data_visits) -------------------------------------------------------------------------------- /case-conflict-mapping.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib inline\n", 10 | "\n", 11 | "import pandas as pd\n", 12 | "import geopandas\n", 13 | "import matplotlib.pyplot as plt" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "# Case study - Conflict mapping: mining sites in eastern DR Congo\n", 21 | "\n", 22 | "In this case study, we will explore a dataset on artisanal mining sites located in eastern DR Congo.\n", 23 | "\n", 24 | "**Note**: this tutorial is meant as a hands-on session, and most code examples are provided as exercises to be filled in. I highly recommend actually trying to do this yourself, but if you want to follow the solved tutorial, you can find this in the `_solved` directory.\n", 25 | "\n", 26 | "---\n", 27 | "\n", 28 | "#### Background\n", 29 | "\n", 30 | "[IPIS](http://ipisresearch.be/), the International Peace Information Service, manages a database on mining site visits in eastern DR Congo: http://ipisresearch.be/home/conflict-mapping/maps/open-data/\n", 31 | "\n", 32 | "Since 2009, IPIS has visited artisanal mining sites in the region during various data collection campaigns. As part of these campaigns, surveyor teams visit mining sites in the field, meet with miners and complete predefined questionnaires. These contain questions about the mining site, the minerals mined at the site and the armed groups possibly present at the site.\n", 33 | "\n", 34 | "Some additional links:\n", 35 | "\n", 36 | "* Tutorial on the same data using R from IPIS (but without geospatial aspect): http://ipisresearch.be/home/conflict-mapping/maps/open-data/open-data-tutorial/\n", 37 | "* Interactive web app using the same data: http://www.ipisresearch.be/mapping/webmapping/drcongo/v5/" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "## 1. Importing and exploring the data" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "### The mining site visit data\n", 52 | "\n", 53 | "IPIS provides a WFS server to access the data. We can send a query to this server to download the data, and load the result into a geopandas GeoDataFrame:" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "import requests\n", 63 | "import json\n", 64 | "\n", 65 | "wfs_url = \"http://geo.ipisresearch.be/geoserver/public/ows\"\n", 66 | "params = dict(service='WFS', version='1.0.0', request='GetFeature',\n", 67 | " typeName='public:cod_mines_curated_all_opendata_p_ipis', outputFormat='json')\n", 68 | "\n", 69 | "r = requests.get(wfs_url, params=params)\n", 70 | "data_features = json.loads(r.content.decode('UTF-8'))\n", 71 | "data_visits = geopandas.GeoDataFrame.from_features(data_features, crs={'init': 'epsg:4326'})" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "However, the data is also provided in the tutorial materials as a GeoJSON file, so it is certainly available during the tutorial." 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "
\n", 86 | " EXERCISE:\n", 87 | " \n", 91 | "\n", 92 | "
" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": { 99 | "clear_cell": true 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "# %load _solved/solutions/case-conflict-mapping3.py" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": { 110 | "clear_cell": true 111 | }, 112 | "outputs": [], 113 | "source": [ 114 | "# %load _solved/solutions/case-conflict-mapping4.py" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": { 121 | "clear_cell": true 122 | }, 123 | "outputs": [], 124 | "source": [ 125 | "# %load _solved/solutions/case-conflict-mapping5.py" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "The provided dataset contains a lot of information, much more than we are going to use in this tutorial. Therefore, we will select a subset of the column:" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "data_visits = data_visits[['vid', 'project', 'visit_date', 'name', 'pcode', 'workers_numb', 'interference', 'armed_group1', 'mineral1', 'geometry']]" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "data_visits.head()" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "Before starting the actual geospatial tutorial, we will use some more advanced pandas queries to construct a subset of the data that we will use further on: " 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "# Take only the data of visits by IPIS\n", 167 | "data_ipis = data_visits[data_visits['project'].str.contains('IPIS') & (data_visits['workers_numb'] > 0)]" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "# For those mining sites that were visited multiple times, take only the last visit\n", 177 | "data_ipis_lastvisit = data_ipis.sort_values('visit_date').groupby('pcode', as_index=False).last()\n", 178 | "data = geopandas.GeoDataFrame(data_ipis_lastvisit, crs=data_visits.crs)" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "### Data on protected areas in the same region\n", 186 | "\n", 187 | "Next to the mining site data, we are also going to use a dataset on protected areas (national parks) in Congo. This dataset was downloaded from http://www.wri.org/our-work/project/congo-basin-forests/democratic-republic-congo#project-tabs and included in the tutorial repository: `data/cod_conservation.zip`." 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "
\n", 195 | " EXERCISE:\n", 196 | " \n", 200 | "
" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": { 207 | "clear_cell": true 208 | }, 209 | "outputs": [], 210 | "source": [ 211 | "# %load _solved/solutions/case-conflict-mapping10.py" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "metadata": { 218 | "clear_cell": true 219 | }, 220 | "outputs": [], 221 | "source": [ 222 | "# %load _solved/solutions/case-conflict-mapping11.py" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "### Conversion to a common Coordinate Reference System\n", 230 | "\n", 231 | "We will see that both datasets use a different Coordinate Reference System (CRS). For many operations, however, it is important that we use a consistent CRS, and therefore we will convert both to a commong CRS.\n", 232 | "\n", 233 | "But first, we explore problems we can encounter related to CRSs.\n", 234 | "\n", 235 | "---" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": {}, 241 | "source": [ 242 | "[Goma](https://en.wikipedia.org/wiki/Goma) is the capital city of North Kivu province of Congo, close to the border with Rwanda. It's coordinates are 1.66°S 29.22°E.\n", 243 | "\n", 244 | "
\n", 245 | " EXERCISE:\n", 246 | " \n", 250 | "
" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": { 257 | "clear_cell": true 258 | }, 259 | "outputs": [], 260 | "source": [ 261 | "# %load _solved/solutions/case-conflict-mapping12.py" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": null, 267 | "metadata": { 268 | "clear_cell": true 269 | }, 270 | "outputs": [], 271 | "source": [ 272 | "# %load _solved/solutions/case-conflict-mapping13.py" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "metadata": { 279 | "clear_cell": true 280 | }, 281 | "outputs": [], 282 | "source": [ 283 | "# %load _solved/solutions/case-conflict-mapping14.py" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "metadata": { 290 | "clear_cell": true 291 | }, 292 | "outputs": [], 293 | "source": [ 294 | "# %load _solved/solutions/case-conflict-mapping15.py" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "The distances we see here in degrees, which is not helpful for interpreting those distances. That is a reason we will convert the data to another coordinate reference system (CRS) for the remainder of this tutorial." 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "
\n", 309 | " EXERCISE:\n", 310 | " \n", 313 | " \n", 314 | "

Check the first section of the [04-more-on-visualization.ipynb](04-more-on-visualization.ipynb) notebook for tips and tricks to plot with GeoPandas.

\n", 315 | "
" 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": null, 321 | "metadata": { 322 | "clear_cell": true 323 | }, 324 | "outputs": [], 325 | "source": [ 326 | "# %load _solved/solutions/case-conflict-mapping16.py" 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "metadata": {}, 332 | "source": [ 333 | "You will notice that the protected areas and mining sites do not map to the same area on the plot. This is because the Coordinate Reference Systems (CRS) differ for both datasets. Another reason we will need to convert the CRS!\n", 334 | "\n", 335 | "Let's check the Coordinate Reference System (CRS) for both datasets.\n", 336 | "\n", 337 | "The mining sites data uses the [WGS 84 lat/lon (EPSG 4326)](http://spatialreference.org/ref/epsg/4326/) CRS:" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": null, 343 | "metadata": {}, 344 | "outputs": [], 345 | "source": [ 346 | "data.crs" 347 | ] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "metadata": {}, 352 | "source": [ 353 | "The protected areas dataset, on the other hand, uses a [WGS 84 / World Mercator (EPSG 3395)](http://spatialreference.org/ref/epsg/wgs-84-world-mercator/) projection (with meters as unit):" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": null, 359 | "metadata": {}, 360 | "outputs": [], 361 | "source": [ 362 | "protected_areas.crs" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "metadata": {}, 368 | "source": [ 369 | "We will convert both datasets to a local UTM zone, so we can plot them together and that distance-based calculations give sensible results.\n", 370 | "\n", 371 | "To find the appropriate UTM zone, you can check http://www.dmap.co.uk/utmworld.htm or https://www.latlong.net/lat-long-utm.html, and in this case we will use UTM zone 35, which gives use EPSG 32735: https://epsg.io/32735\n", 372 | "\n", 373 | "
\n", 374 | " EXERCISE:\n", 375 | " \n", 379 | "\n", 380 | "
" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "metadata": { 387 | "clear_cell": true 388 | }, 389 | "outputs": [], 390 | "source": [ 391 | "# %load _solved/solutions/case-conflict-mapping19.py" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": null, 397 | "metadata": { 398 | "clear_cell": true 399 | }, 400 | "outputs": [], 401 | "source": [ 402 | "# %load _solved/solutions/case-conflict-mapping20.py" 403 | ] 404 | }, 405 | { 406 | "cell_type": "markdown", 407 | "metadata": {}, 408 | "source": [ 409 | "### More advanced visualizations\n", 410 | "\n", 411 | "

For the following exercises, check the first section of the [04-more-on-visualization.ipynb](04-more-on-visualization.ipynb) notebook for tips and tricks to plot with GeoPandas.

" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "metadata": {}, 417 | "source": [ 418 | "
\n", 419 | " EXERCISE:\n", 420 | " \n", 431 | "
" 432 | ] 433 | }, 434 | { 435 | "cell_type": "code", 436 | "execution_count": null, 437 | "metadata": { 438 | "clear_cell": true 439 | }, 440 | "outputs": [], 441 | "source": [ 442 | "# %load _solved/solutions/case-conflict-mapping21.py" 443 | ] 444 | }, 445 | { 446 | "cell_type": "code", 447 | "execution_count": null, 448 | "metadata": { 449 | "clear_cell": true 450 | }, 451 | "outputs": [], 452 | "source": [ 453 | "# %load _solved/solutions/case-conflict-mapping22.py" 454 | ] 455 | }, 456 | { 457 | "cell_type": "markdown", 458 | "metadata": {}, 459 | "source": [ 460 | "
\n", 461 | " EXERCISE:\n", 462 | " \n", 463 | " In addition to the previous figure:\n", 464 | " \n", 467 | "
" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": null, 473 | "metadata": { 474 | "clear_cell": true 475 | }, 476 | "outputs": [], 477 | "source": [ 478 | "# %load _solved/solutions/case-conflict-mapping23.py" 479 | ] 480 | }, 481 | { 482 | "cell_type": "markdown", 483 | "metadata": {}, 484 | "source": [ 485 | "
\n", 486 | " EXERCISE:\n", 487 | " \n", 488 | " In addition to the previous figure:\n", 489 | " \n", 492 | "
" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": null, 498 | "metadata": { 499 | "clear_cell": true 500 | }, 501 | "outputs": [], 502 | "source": [ 503 | "# %load _solved/solutions/case-conflict-mapping24.py" 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "metadata": {}, 509 | "source": [ 510 | "## 2. Spatial operations" 511 | ] 512 | }, 513 | { 514 | "cell_type": "markdown", 515 | "metadata": {}, 516 | "source": [ 517 | "
\n", 518 | " EXERCISE:\n", 519 | " \n", 520 | " \n", 524 | "
" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": null, 530 | "metadata": { 531 | "clear_cell": true 532 | }, 533 | "outputs": [], 534 | "source": [ 535 | "# %load _solved/solutions/case-conflict-mapping25.py" 536 | ] 537 | }, 538 | { 539 | "cell_type": "code", 540 | "execution_count": null, 541 | "metadata": { 542 | "clear_cell": true 543 | }, 544 | "outputs": [], 545 | "source": [ 546 | "# %load _solved/solutions/case-conflict-mapping26.py" 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "execution_count": null, 552 | "metadata": { 553 | "clear_cell": true 554 | }, 555 | "outputs": [], 556 | "source": [ 557 | "# %load _solved/solutions/case-conflict-mapping27.py" 558 | ] 559 | }, 560 | { 561 | "cell_type": "markdown", 562 | "metadata": {}, 563 | "source": [ 564 | "
\n", 565 | " EXERCISE: Determine for each mining site the \"closest\" protected area:\n", 566 | " \n", 567 | " \n", 582 | "
" 583 | ] 584 | }, 585 | { 586 | "cell_type": "code", 587 | "execution_count": null, 588 | "metadata": { 589 | "clear_cell": true 590 | }, 591 | "outputs": [], 592 | "source": [ 593 | "# %load _solved/solutions/case-conflict-mapping28.py" 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": null, 599 | "metadata": { 600 | "clear_cell": true 601 | }, 602 | "outputs": [], 603 | "source": [ 604 | "# %load _solved/solutions/case-conflict-mapping29.py" 605 | ] 606 | }, 607 | { 608 | "cell_type": "code", 609 | "execution_count": null, 610 | "metadata": { 611 | "clear_cell": true 612 | }, 613 | "outputs": [], 614 | "source": [ 615 | "# %load _solved/solutions/case-conflict-mapping30.py" 616 | ] 617 | }, 618 | { 619 | "cell_type": "code", 620 | "execution_count": null, 621 | "metadata": { 622 | "clear_cell": true 623 | }, 624 | "outputs": [], 625 | "source": [ 626 | "# %load _solved/solutions/case-conflict-mapping31.py" 627 | ] 628 | }, 629 | { 630 | "cell_type": "code", 631 | "execution_count": null, 632 | "metadata": { 633 | "clear_cell": true 634 | }, 635 | "outputs": [], 636 | "source": [ 637 | "# %load _solved/solutions/case-conflict-mapping32.py" 638 | ] 639 | }, 640 | { 641 | "cell_type": "markdown", 642 | "metadata": {}, 643 | "source": [ 644 | "## 3. Using spatial join to determine mining sites in the protected areas\n", 645 | "\n", 646 | "Based on the analysis and visualizations above, we can already see that there are mining sites inside the protected areas. Let's now do an actual spatial join to determine which sites are within the protected areas." 647 | ] 648 | }, 649 | { 650 | "cell_type": "markdown", 651 | "metadata": {}, 652 | "source": [ 653 | "### Mining sites in protected areas\n", 654 | "\n", 655 | "
\n", 656 | " EXERCISE:\n", 657 | " \n", 669 | "\n", 670 | "
" 671 | ] 672 | }, 673 | { 674 | "cell_type": "code", 675 | "execution_count": null, 676 | "metadata": { 677 | "clear_cell": true 678 | }, 679 | "outputs": [], 680 | "source": [ 681 | "# %load _solved/solutions/case-conflict-mapping33.py" 682 | ] 683 | }, 684 | { 685 | "cell_type": "code", 686 | "execution_count": null, 687 | "metadata": { 688 | "clear_cell": true 689 | }, 690 | "outputs": [], 691 | "source": [ 692 | "# %load _solved/solutions/case-conflict-mapping34.py" 693 | ] 694 | }, 695 | { 696 | "cell_type": "code", 697 | "execution_count": null, 698 | "metadata": { 699 | "clear_cell": true 700 | }, 701 | "outputs": [], 702 | "source": [ 703 | "# %load _solved/solutions/case-conflict-mapping35.py" 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": null, 709 | "metadata": { 710 | "clear_cell": true 711 | }, 712 | "outputs": [], 713 | "source": [ 714 | "# %load _solved/solutions/case-conflict-mapping36.py" 715 | ] 716 | }, 717 | { 718 | "cell_type": "markdown", 719 | "metadata": {}, 720 | "source": [ 721 | "### Mining sites in the borders of protected areas\n", 722 | "\n", 723 | "And what about the borders of the protected areas? (just outside the park)\n", 724 | "\n", 725 | "
\n", 726 | " EXERCISE:\n", 727 | " \n", 737 | "\n", 738 | "
" 739 | ] 740 | }, 741 | { 742 | "cell_type": "code", 743 | "execution_count": null, 744 | "metadata": { 745 | "clear_cell": true 746 | }, 747 | "outputs": [], 748 | "source": [ 749 | "# %load _solved/solutions/case-conflict-mapping37.py" 750 | ] 751 | }, 752 | { 753 | "cell_type": "code", 754 | "execution_count": null, 755 | "metadata": { 756 | "clear_cell": true 757 | }, 758 | "outputs": [], 759 | "source": [ 760 | "# %load _solved/solutions/case-conflict-mapping38.py" 761 | ] 762 | }, 763 | { 764 | "cell_type": "code", 765 | "execution_count": null, 766 | "metadata": { 767 | "clear_cell": true 768 | }, 769 | "outputs": [], 770 | "source": [ 771 | "# %load _solved/solutions/case-conflict-mapping39.py" 772 | ] 773 | }, 774 | { 775 | "cell_type": "code", 776 | "execution_count": null, 777 | "metadata": { 778 | "clear_cell": true 779 | }, 780 | "outputs": [], 781 | "source": [ 782 | "# %load _solved/solutions/case-conflict-mapping40.py" 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": null, 788 | "metadata": { 789 | "clear_cell": true 790 | }, 791 | "outputs": [], 792 | "source": [ 793 | "# %load _solved/solutions/case-conflict-mapping41.py" 794 | ] 795 | } 796 | ], 797 | "metadata": { 798 | "kernelspec": { 799 | "display_name": "Python 3", 800 | "language": "python", 801 | "name": "python3" 802 | }, 803 | "language_info": { 804 | "codemirror_mode": { 805 | "name": "ipython", 806 | "version": 3 807 | }, 808 | "file_extension": ".py", 809 | "mimetype": "text/x-python", 810 | "name": "python", 811 | "nbconvert_exporter": "python", 812 | "pygments_lexer": "ipython3", 813 | "version": "3.5.5" 814 | } 815 | }, 816 | "nbformat": 4, 817 | "nbformat_minor": 2 818 | } 819 | -------------------------------------------------------------------------------- /check_environment.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | packages = ['geopandas', 'contextily', 'fiona', 'rtree', 'mapclassify'] 4 | 5 | bad = [] 6 | for package in packages: 7 | try: 8 | importlib.import_module(package) 9 | except ImportError: 10 | bad.append("Can't import %s" % package) 11 | else: 12 | if len(bad) > 0: 13 | print('Your tutorial environment is not yet fully set up:') 14 | print('\n'.join(bad)) 15 | else: 16 | try: 17 | import geopandas 18 | countries = geopandas.read_file("zip://./data/ne_110m_admin_0_countries.zip") 19 | print("All good. Enjoy the tutorial!") 20 | except Exception as e: 21 | print("Couldn't read countries shapefile.") 22 | print(e) 23 | 24 | -------------------------------------------------------------------------------- /data/cod_conservation.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/cod_conservation.zip -------------------------------------------------------------------------------- /data/ne_110m_admin_0_countries.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/ne_110m_admin_0_countries.zip -------------------------------------------------------------------------------- /data/ne_110m_populated_places.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/ne_110m_populated_places.zip -------------------------------------------------------------------------------- /data/ne_50m_rivers_lake_centerlines.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/ne_50m_rivers_lake_centerlines.zip -------------------------------------------------------------------------------- /data/paris_bike_stations_mercator.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/paris_bike_stations_mercator.gpkg -------------------------------------------------------------------------------- /data/paris_land_use.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/paris_land_use.zip -------------------------------------------------------------------------------- /data/paris_trees.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/data/paris_trees.gpkg -------------------------------------------------------------------------------- /data/raw/paris-population.csv: -------------------------------------------------------------------------------- 1 | district_name,population 2 | SALPETRIERE,18246 3 | GARE,69008 4 | MAISON BLANCHE,64797 5 | CROULEBARBE,19526 6 | BELLEVILLE,35773 7 | SAINT FARGEAU,42087 8 | PERE LACHAISE,42332 9 | CHARONNE,62901 10 | GAILLON,1345 11 | VIVIENNE,2917 12 | MAIL,5783 13 | BONNE NOUVELLE,9595 14 | SAINT VINCENT DE PAUL,21624 15 | PORTE SAINT DENIS,15066 16 | PORTE SAINT MARTIN,23125 17 | HOPITAL SAINT LOUIS,29870 18 | SAINT LAMBERT,82032 19 | NECKER,46932 20 | GRENELLE,47411 21 | JAVEL,49092 22 | FOLIE MERICOURT,33002 23 | SAINT AMBROISE,32168 24 | ROQUETTE,47520 25 | SAINTE MARGUERITE,36476 26 | SAINT GEORGES,20850 27 | CHAUSSEE D'ANTIN,3488 28 | FAUBOURG MONTMARTRE,9233 29 | ROCHECHOUART,22212 30 | ARTS ET METIERS,9560 31 | ENFANTS ROUGES,8562 32 | ARCHIVES,8609 33 | SAINT AVOYE,7501 34 | AUTEUIL,67967 35 | MUETTE,45214 36 | PORTE DAUPHINE,27423 37 | CHAILLOT,21213 38 | SAINT MERRI,6523 39 | SAINT GERVAIS,10587 40 | ARSENAL,9474 41 | NOTRE DAME,4087 42 | SAINT GERMAIN L'AUXERROIS,1672 43 | HALLES,8984 44 | PALAIS ROYAL,3195 45 | PLACE VENDOME,3044 46 | BEL AIR,33976 47 | PICPUS,62947 48 | BERCY,13987 49 | QUINZE VINGTS,25752 50 | VILLETTE,53650 51 | PONT DE FLANDRE,24584 52 | AMERIQUE,55365 53 | COMBAT,38988 54 | CHAMPS ELYSEES,4614 55 | FAUBOURG DU ROULE,10038 56 | MADELEINE,6045 57 | EUROPE,18606 58 | MONTPARNASSE,18570 59 | PARC DE MONTSOURIS,19793 60 | PETIT MONTROUGE,37230 61 | PLAISANCE,57229 62 | TERNES,39137 63 | PLAINE MONCEAU,38958 64 | BATIGNOLLES,38691 65 | EPINETTES,44352 66 | SAINT THOMAS D'AQUIN,12661 67 | INVALIDES,6276 68 | ECOLE MILITAIRE,12895 69 | GROS CAILLOU,25156 70 | SAINT VICTOR,11661 71 | JARDIN DES PLANTES,18005 72 | VAL DE GRACE,19492 73 | SORBONNE,9683 74 | GRANDES CARRIERES,67152 75 | CLIGNANCOURT,64868 76 | GOUTTE D'OR,28524 77 | LA CHAPELLE,24037 78 | MONNAIE,6185 79 | ODEON,8833 80 | NOTRE DAME DES CHAMPS,24731 81 | SAINT GERMAIN DES PRES,5154 82 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | dependencies: 4 | - python=3.10 5 | - jupyter 6 | - jupyterlab>3 7 | - ipython 8 | - numpy 9 | - pandas 10 | - geopandas 11 | - pygeos 12 | - rasterio 13 | - rasterstats 14 | - matplotlib 15 | - mapclassify 16 | - cartopy 17 | - contextily 18 | - geoplot 19 | - ipympl 20 | - ipyleaflet 21 | - folium 22 | - hvplot 23 | - geoviews 24 | - pyepsg 25 | -------------------------------------------------------------------------------- /img/GDALLogoColor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /img/Open_Source_Geospatial_Foundation.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | ]> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /img/TopologicSpatialRelarions2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/TopologicSpatialRelarions2.png -------------------------------------------------------------------------------- /img/download-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/download-button.png -------------------------------------------------------------------------------- /img/gdal_formats: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/gdal_formats -------------------------------------------------------------------------------- /img/gdal_users: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/gdal_users -------------------------------------------------------------------------------- /img/geos.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/geos.gif -------------------------------------------------------------------------------- /img/inria-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/inria-logo.png -------------------------------------------------------------------------------- /img/logoUPSayPlusCDS_990.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/logoUPSayPlusCDS_990.png -------------------------------------------------------------------------------- /img/mercator_projection_area.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/mercator_projection_area.gif -------------------------------------------------------------------------------- /img/overlay-both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/overlay-both.png -------------------------------------------------------------------------------- /img/overlay-countries-circle-intersection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/overlay-countries-circle-intersection.png -------------------------------------------------------------------------------- /img/overlay-countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/overlay-countries.png -------------------------------------------------------------------------------- /img/overlay-overlayed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/overlay-overlayed.png -------------------------------------------------------------------------------- /img/overlay-regions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/overlay-regions.png -------------------------------------------------------------------------------- /img/pandas_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /img/projection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/projection.png -------------------------------------------------------------------------------- /img/projections-AlbersEqualArea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/projections-AlbersEqualArea.png -------------------------------------------------------------------------------- /img/projections-Mercator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/projections-Mercator.png -------------------------------------------------------------------------------- /img/projections-Robinson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/projections-Robinson.png -------------------------------------------------------------------------------- /img/raster_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/raster_example.png -------------------------------------------------------------------------------- /img/simple_features_3_text.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 114 | 135 | 146 | 165 | 185 | 197 | 221 | 229 | 230 | 243 | 264 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 304 | 328 | 359 | 375 | 408 | 418 | 442 | 448 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 504 | 520 | 552 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | -------------------------------------------------------------------------------- /img/slides.css: -------------------------------------------------------------------------------- 1 | @import url(webfont-ubuntu-400-300-100.css); 2 | @import url(webfont-ubuntu-mono-400-700-400italic.css); 3 | 4 | body { 5 | font-family: 'Ubuntu'; 6 | font-weight: normal; 7 | } 8 | 9 | h1, h2, h3, h4, h5, h6 { 10 | font-family: 'Ubuntu'; 11 | font-weight: 300; 12 | margin-top: 0; 13 | } 14 | h1 { 15 | margin-top: 0.5em; 16 | } 17 | h2 { 18 | font-size: 140%; 19 | line-height: 150%; 20 | } 21 | h3 { 22 | font-size: 120%; 23 | line-height: 140%; 24 | } 25 | 26 | 27 | 28 | li { 29 | font-size: 120%; 30 | line-height: 180%; 31 | } 32 | 33 | li li { 34 | font-size: 100%; 35 | line-height: 160%; 36 | } 37 | 38 | p { 39 | font-size: 120%; 40 | line-height: 140%; 41 | } 42 | 43 | .singleimg .middlebelowheader { 44 | text-align: center; 45 | } 46 | 47 | .singleimg img { 48 | max-width: 90%; 49 | max-height: 600px; 50 | /*border: 2px solid #ddd;*/ 51 | } 52 | table { 53 | margin: 0 auto 0.8em; 54 | border-collapse: collapse; 55 | } 56 | td, th { 57 | border: 1px solid #ddd; 58 | padding: 0.3em 0.5em; 59 | } 60 | 61 | .bgheader h1 { 62 | background-color: rgba(0, 0, 0, 0.9); 63 | opacity: 50%; 64 | padding: 0.5em; 65 | color: white; 66 | border-radius: .5em; 67 | } 68 | .middlebelowheader { 69 | /* This fixed size height was found to work well with the slide 70 | scaling mechanism of remark.js: 71 | */ 72 | height: 500px; 73 | display: table-cell; 74 | vertical-align: middle; 75 | } 76 | .widespace h2 { 77 | line-height: 200%; 78 | } 79 | .big .remark-code { 80 | font-size: 200%; 81 | } 82 | .remark-code, .remark-inline-code { 83 | font-family: 'Ubuntu Mono'; 84 | font-size: 110%; 85 | } 86 | 87 | .medium .remark-code { 88 | font-size: 130%; 89 | } 90 | 91 | .mmedium .remark-code { 92 | font-size: 99%; 93 | } 94 | 95 | .small .remark-code { 96 | font-size: 93%; 97 | } 98 | 99 | .affiliations img { 100 | /*height: 100px;*/ 101 | margin: 2em; 102 | margin-right: 0.5em; 103 | margin-left:0.5em; 104 | } 105 | 106 | .hidden { 107 | visibility: hidden; 108 | } 109 | 110 | .small { 111 | font-size: 90%; 112 | } 113 | 114 | .credits { 115 | font-style: italic; 116 | font-size: 70%; 117 | } 118 | 119 | .bunchoflogos img { 120 | max-height: 100px; 121 | padding: 1em; 122 | } 123 | 124 | .bunchoflogos p { 125 | text-align: center; 126 | width: 750px; 127 | } 128 | 129 | a:visited { 130 | color: blue; 131 | } 132 | 133 | .inverse a:visited { 134 | color: Maroon; 135 | } 136 | 137 | .inverse { 138 | background: #272822; 139 | color: #777872; 140 | text-shadow: 0 0 20px #333; 141 | } 142 | .inverse h1, .inverse h2 { 143 | color: #f3f3f3; 144 | } 145 | 146 | code { 147 | background: #e7e8e2; 148 | border-radius: 5px; 149 | } 150 | .pull-left { 151 | float: left; 152 | width: 47%; 153 | } 154 | .pull-right { 155 | float: right; 156 | width: 47%; 157 | } 158 | .pull-right ~ p { 159 | clear: both; 160 | } 161 | 162 | .left-column { 163 | width: 40%; 164 | float: left; 165 | font-size: 120%; 166 | } 167 | .right-column { 168 | width: 56%; 169 | float: left; 170 | font-size: 120%; 171 | } 172 | .right-column { 173 | float: right; 174 | } 175 | .left-column { 176 | float: left; 177 | } 178 | .reset-column { 179 | overflow: auto; 180 | width: 100%; 181 | } 182 | 183 | .red { color: #fa0000; } 184 | .blue { color: #0000ff; } 185 | .darkred {color: #C10000;} 186 | 187 | .fat { font-weight: 800; } 188 | 189 | 190 | /*@page { 191 | size: 1024px 768px; 192 | margin: 0; 193 | } 194 | 195 | @media print { 196 | .remark-slide-scaler { 197 | width: 100% !important; 198 | height: 100% !important; 199 | transform: scale(1) !important; 200 | top: 0 !important; 201 | left: 0 !important; 202 | } 203 | }*/ 204 | -------------------------------------------------------------------------------- /img/spatial-operations-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-base.png -------------------------------------------------------------------------------- /img/spatial-operations-buffer-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-buffer-line.png -------------------------------------------------------------------------------- /img/spatial-operations-buffer-point1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-buffer-point1.png -------------------------------------------------------------------------------- /img/spatial-operations-buffer-point2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-buffer-point2.png -------------------------------------------------------------------------------- /img/spatial-operations-buffer-polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-buffer-polygon.png -------------------------------------------------------------------------------- /img/spatial-operations-difference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-difference.png -------------------------------------------------------------------------------- /img/spatial-operations-intersection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-intersection.png -------------------------------------------------------------------------------- /img/spatial-operations-union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/spatial-operations-union.png -------------------------------------------------------------------------------- /img/vector_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisvandenbossche/geopandas-tutorial/72c7f3168e1e4e257caf84eb7b3fcaa47ade09c0/img/vector_example.png -------------------------------------------------------------------------------- /img/webfont-ubuntu-400-300-100.css: -------------------------------------------------------------------------------- 1 | /* cyrillic-ext */ 2 | @font-face { 3 | font-family: 'Ubuntu'; 4 | font-style: normal; 5 | font-weight: 300; 6 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/X_EdMnknKUltk57alVVbVxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 7 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 8 | } 9 | /* cyrillic */ 10 | @font-face { 11 | font-family: 'Ubuntu'; 12 | font-style: normal; 13 | font-weight: 300; 14 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/nBF2d6Y3AbOwfkBM-9HcWBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 15 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 16 | } 17 | /* greek-ext */ 18 | @font-face { 19 | font-family: 'Ubuntu'; 20 | font-style: normal; 21 | font-weight: 300; 22 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/CdlIlwqST01WNAKqZbtZkhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 23 | unicode-range: U+1F00-1FFF; 24 | } 25 | /* greek */ 26 | @font-face { 27 | font-family: 'Ubuntu'; 28 | font-style: normal; 29 | font-weight: 300; 30 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/7k0RmqCN8EFxqS6sChuRzRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 31 | unicode-range: U+0370-03FF; 32 | } 33 | /* latin-ext */ 34 | @font-face { 35 | font-family: 'Ubuntu'; 36 | font-style: normal; 37 | font-weight: 300; 38 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/WtcvfJHWXKxx4x0kuS1koRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 39 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 40 | } 41 | /* latin */ 42 | @font-face { 43 | font-family: 'Ubuntu'; 44 | font-style: normal; 45 | font-weight: 300; 46 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(https://fonts.gstatic.com/s/ubuntu/v9/_aijTyevf54tkVDLy-dlnFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); 47 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 48 | } 49 | /* cyrillic-ext */ 50 | @font-face { 51 | font-family: 'Ubuntu'; 52 | font-style: normal; 53 | font-weight: 400; 54 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/ODszJI8YqNw8V2xPulzjO_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 55 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 56 | } 57 | /* cyrillic */ 58 | @font-face { 59 | font-family: 'Ubuntu'; 60 | font-style: normal; 61 | font-weight: 400; 62 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/iQ9VJx1UMASKNiGywyyCXvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 63 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 64 | } 65 | /* greek-ext */ 66 | @font-face { 67 | font-family: 'Ubuntu'; 68 | font-style: normal; 69 | font-weight: 400; 70 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/WkvQmvwsfw_KKeau9SlQ2_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 71 | unicode-range: U+1F00-1FFF; 72 | } 73 | /* greek */ 74 | @font-face { 75 | font-family: 'Ubuntu'; 76 | font-style: normal; 77 | font-weight: 400; 78 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/gYAtqXUikkQjyJA1SnpDLvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 79 | unicode-range: U+0370-03FF; 80 | } 81 | /* latin-ext */ 82 | @font-face { 83 | font-family: 'Ubuntu'; 84 | font-style: normal; 85 | font-weight: 400; 86 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/Wu5Iuha-XnKDBvqRwQzAG_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 87 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 88 | } 89 | /* latin */ 90 | @font-face { 91 | font-family: 'Ubuntu'; 92 | font-style: normal; 93 | font-weight: 400; 94 | src: local('Ubuntu'), url(https://fonts.gstatic.com/s/ubuntu/v9/sDGTilo5QRsfWu6Yc11AXg.woff2) format('woff2'); 95 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 96 | } 97 | -------------------------------------------------------------------------------- /img/webfont-ubuntu-mono-400-700-400italic.css: -------------------------------------------------------------------------------- 1 | /* cyrillic-ext */ 2 | @font-face { 3 | font-family: 'Ubuntu Mono'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkTTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2'); 7 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 8 | } 9 | /* cyrillic */ 10 | @font-face { 11 | font-family: 'Ubuntu Mono'; 12 | font-style: normal; 13 | font-weight: 400; 14 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkTUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2'); 15 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 16 | } 17 | /* greek-ext */ 18 | @font-face { 19 | font-family: 'Ubuntu Mono'; 20 | font-style: normal; 21 | font-weight: 400; 22 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkUbcKLIaa1LC45dFaAfauRA.woff2) format('woff2'); 23 | unicode-range: U+1F00-1FFF; 24 | } 25 | /* greek */ 26 | @font-face { 27 | font-family: 'Ubuntu Mono'; 28 | font-style: normal; 29 | font-weight: 400; 30 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkWo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2'); 31 | unicode-range: U+0370-03FF; 32 | } 33 | /* latin-ext */ 34 | @font-face { 35 | font-family: 'Ubuntu Mono'; 36 | font-style: normal; 37 | font-weight: 400; 38 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2'); 39 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 40 | } 41 | /* latin */ 42 | @font-face { 43 | font-family: 'Ubuntu Mono'; 44 | font-style: normal; 45 | font-weight: 400; 46 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2'); 47 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 48 | } 49 | /* cyrillic-ext */ 50 | @font-face { 51 | font-family: 'Ubuntu Mono'; 52 | font-style: normal; 53 | font-weight: 700; 54 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytp6iIh_FvlUHQwED9Yt5Kbw.woff2) format('woff2'); 55 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 56 | } 57 | /* cyrillic */ 58 | @font-face { 59 | font-family: 'Ubuntu Mono'; 60 | font-style: normal; 61 | font-weight: 700; 62 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molyti_vZmeiCMnoWNN9rHBYaTc.woff2) format('woff2'); 63 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 64 | } 65 | /* greek-ext */ 66 | @font-face { 67 | font-family: 'Ubuntu Mono'; 68 | font-style: normal; 69 | font-weight: 700; 70 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytiFaMxiho_5XQnyRZzQsrZs.woff2) format('woff2'); 71 | unicode-range: U+1F00-1FFF; 72 | } 73 | /* greek */ 74 | @font-face { 75 | font-family: 'Ubuntu Mono'; 76 | font-style: normal; 77 | font-weight: 700; 78 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytgalQocB-__pDVGhF3uS2Ks.woff2) format('woff2'); 79 | unicode-range: U+0370-03FF; 80 | } 81 | /* latin-ext */ 82 | @font-face { 83 | font-family: 'Ubuntu Mono'; 84 | font-style: normal; 85 | font-weight: 700; 86 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytujkDdvhIIFj_YMdgqpnSB0.woff2) format('woff2'); 87 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 88 | } 89 | /* latin */ 90 | @font-face { 91 | font-family: 'Ubuntu Mono'; 92 | font-style: normal; 93 | font-weight: 700; 94 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(https://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytolIZu-HDpmDIZMigmsroc4.woff2) format('woff2'); 95 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 96 | } 97 | /* cyrillic-ext */ 98 | @font-face { 99 | font-family: 'Ubuntu Mono'; 100 | font-style: italic; 101 | font-weight: 400; 102 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKAxNcqx07xvyppV96iFRdwiM.woff2) format('woff2'); 103 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 104 | } 105 | /* cyrillic */ 106 | @font-face { 107 | font-family: 'Ubuntu Mono'; 108 | font-style: italic; 109 | font-weight: 400; 110 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA-fhZE2STYI3KzBGzrJG_ik.woff2) format('woff2'); 111 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 112 | } 113 | /* greek-ext */ 114 | @font-face { 115 | font-family: 'Ubuntu Mono'; 116 | font-style: italic; 117 | font-weight: 400; 118 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA26cj8HaeL2jS4NIBPr3RFo.woff2) format('woff2'); 119 | unicode-range: U+1F00-1FFF; 120 | } 121 | /* greek */ 122 | @font-face { 123 | font-family: 'Ubuntu Mono'; 124 | font-style: italic; 125 | font-weight: 400; 126 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA9cKKn5Xt5n-nnvkqIBMZms.woff2) format('woff2'); 127 | unicode-range: U+0370-03FF; 128 | } 129 | /* latin-ext */ 130 | @font-face { 131 | font-family: 'Ubuntu Mono'; 132 | font-style: italic; 133 | font-weight: 400; 134 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA0_0lycXMw8PhobHtu2Qgco.woff2) format('woff2'); 135 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 136 | } 137 | /* latin */ 138 | @font-face { 139 | font-family: 'Ubuntu Mono'; 140 | font-style: italic; 141 | font-weight: 400; 142 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(https://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA8u2Q0OS-KeTAWjgkS85mDg.woff2) format('woff2'); 143 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 144 | } 145 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Introduction to geospatial data analysis with GeoPandas and the PyData stack 12 | 13 | 14 | 28 | 29 | 30 | 336 | 338 | 340 | 357 | 358 | 359 | --------------------------------------------------------------------------------