Go "Birding" with Google Street View?

Virtual Field Survey Workflow Showcase of Sandhill Cranes and Their Habitats in North America

Geospatial Data Science Project - Part 3: This notebook documents the workflow for Google Street View 'Birding,' which encompasses processes such as sample retrieval, Google Street View metadata and image downloads, and more.

By ZJ Zhou (zhijiez2@illinois.edu)

Environment Setup

In [1]:
import pandas as pd
import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from shapely.geometry import Point
!pip install streetview
from streetview import search_panoramas, get_panorama_meta, get_panorama
!pip install earthengine-api
!pip install geemap
import geemap
from pathlib import Path
import os
import ee
import glob
import csv
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: streetview in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (0.0.6)
Requirement already satisfied: requests in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from streetview) (2.27.1)
Requirement already satisfied: pillow in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from streetview) (8.4.0)
Requirement already satisfied: pydantic in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from streetview) (2.4.2)
Requirement already satisfied: typing-extensions>=4.6.1 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from pydantic->streetview) (4.8.0)
Requirement already satisfied: pydantic-core==2.10.1 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from pydantic->streetview) (2.10.1)
Requirement already satisfied: annotated-types>=0.4.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from pydantic->streetview) (0.6.0)
Requirement already satisfied: charset-normalizer~=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->streetview) (2.0.10)
Requirement already satisfied: certifi>=2017.4.17 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->streetview) (2021.10.8)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->streetview) (1.25.11)
Requirement already satisfied: idna<4,>=2.5 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->streetview) (3.3)
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: earthengine-api in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (0.1.374)
Requirement already satisfied: google-api-python-client>=1.12.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (2.36.0)
Requirement already satisfied: google-auth-httplib2>=0.0.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (0.1.0)
Requirement already satisfied: httplib2<1dev,>=0.9.2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (0.20.2)
Requirement already satisfied: google-cloud-storage in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (2.12.0)
Requirement already satisfied: requests in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (2.27.1)
Requirement already satisfied: google-auth>=1.4.1 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api) (2.23.3)
Requirement already satisfied: google-api-core<3.0.0dev,>=1.21.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-python-client>=1.12.1->earthengine-api) (2.4.0)
Requirement already satisfied: uritemplate<5,>=3.0.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-python-client>=1.12.1->earthengine-api) (4.1.1)
Requirement already satisfied: rsa<5,>=3.1.4 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api) (4.8)
Requirement already satisfied: cachetools<6.0,>=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api) (4.2.4)
Requirement already satisfied: pyasn1-modules>=0.2.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api) (0.2.8)
Requirement already satisfied: six in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth-httplib2>=0.0.3->earthengine-api) (1.16.0)
Requirement already satisfied: pyparsing!=3.0.0,!=3.0.1,!=3.0.2,!=3.0.3,<4,>=2.4.2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from httplib2<1dev,>=0.9.2->earthengine-api) (3.0.7)
Requirement already satisfied: google-resumable-media>=2.6.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api) (2.6.0)
Requirement already satisfied: google-crc32c<2.0dev,>=1.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api) (1.5.0)
Requirement already satisfied: google-cloud-core<3.0dev,>=2.3.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api) (2.3.3)
Requirement already satisfied: certifi>=2017.4.17 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api) (2021.10.8)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api) (1.25.11)
Requirement already satisfied: idna<4,>=2.5 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api) (3.3)
Requirement already satisfied: charset-normalizer~=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api) (2.0.10)
Requirement already satisfied: googleapis-common-protos<2.0dev,>=1.52.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api) (1.54.0)
Requirement already satisfied: setuptools>=40.3.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api) (59.8.0)
Requirement already satisfied: protobuf>=3.12.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api) (3.19.3)
Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.4.1->earthengine-api) (0.4.8)
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: geemap in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (0.29.0)
Requirement already satisfied: colour in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.1.5)
Requirement already satisfied: ipyfilechooser>=0.6.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.6.0)
Requirement already satisfied: ipyevents in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (2.0.2)
Requirement already satisfied: pyshp>=2.1.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (2.1.3)
Requirement already satisfied: earthengine-api>=0.1.347 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.1.374)
Requirement already satisfied: bqplot in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.12.40)
Requirement already satisfied: ipyleaflet>=0.17.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.17.4)
Requirement already satisfied: ipytree in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.2.2)
Requirement already satisfied: scooby in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.7.4)
Requirement already satisfied: plotly in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (5.5.0)
Requirement already satisfied: geocoder in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (1.38.1)
Requirement already satisfied: folium>=0.13.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.14.0)
Requirement already satisfied: pandas in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (1.3.5)
Requirement already satisfied: matplotlib in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (3.4.3)
Requirement already satisfied: python-box in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (7.1.1)
Requirement already satisfied: numpy in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geemap) (1.22.0)
Requirement already satisfied: eerepr>=0.0.4 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (0.0.4)
Requirement already satisfied: pyperclip in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geemap) (1.8.2)
Requirement already satisfied: httplib2<1dev,>=0.9.2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (0.20.2)
Requirement already satisfied: google-auth-httplib2>=0.0.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (0.1.0)
Requirement already satisfied: google-api-python-client>=1.12.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (2.36.0)
Requirement already satisfied: google-cloud-storage in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (2.12.0)
Requirement already satisfied: google-auth>=1.4.1 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (2.23.3)
Requirement already satisfied: requests in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from earthengine-api>=0.1.347->geemap) (2.27.1)
Requirement already satisfied: branca>=0.6.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from folium>=0.13.0->geemap) (0.6.0)
Requirement already satisfied: jinja2>=2.9 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from folium>=0.13.0->geemap) (3.0.3)
Requirement already satisfied: ipywidgets in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipyfilechooser>=0.6.0->geemap) (7.6.5)
Requirement already satisfied: xyzservices>=2021.8.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipyleaflet>=0.17.0->geemap) (2022.1.1)
Requirement already satisfied: traittypes<3,>=0.2.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipyleaflet>=0.17.0->geemap) (0.2.1)
Requirement already satisfied: traitlets>=4.3.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from bqplot->geemap) (5.1.1)
Requirement already satisfied: python-dateutil>=2.7.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from pandas->geemap) (2.8.0)
Requirement already satisfied: pytz>=2017.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from pandas->geemap) (2021.3)
Requirement already satisfied: future in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geocoder->geemap) (0.18.2)
Requirement already satisfied: ratelim in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from geocoder->geemap) (0.1.6)
Requirement already satisfied: click in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geocoder->geemap) (7.1.2)
Requirement already satisfied: six in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from geocoder->geemap) (1.16.0)
Requirement already satisfied: pyparsing>=2.2.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from matplotlib->geemap) (3.0.7)
Requirement already satisfied: pillow>=6.2.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from matplotlib->geemap) (8.4.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from matplotlib->geemap) (1.3.2)
Requirement already satisfied: cycler>=0.10 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from matplotlib->geemap) (0.11.0)
Requirement already satisfied: tenacity>=6.2.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from plotly->geemap) (8.0.1)
Requirement already satisfied: uritemplate<5,>=3.0.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-python-client>=1.12.1->earthengine-api>=0.1.347->geemap) (4.1.1)
Requirement already satisfied: google-api-core<3.0.0dev,>=1.21.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-python-client>=1.12.1->earthengine-api>=0.1.347->geemap) (2.4.0)
Requirement already satisfied: pyasn1-modules>=0.2.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api>=0.1.347->geemap) (0.2.8)
Requirement already satisfied: rsa<5,>=3.1.4 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api>=0.1.347->geemap) (4.8)
Requirement already satisfied: cachetools<6.0,>=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-auth>=1.4.1->earthengine-api>=0.1.347->geemap) (4.2.4)
Requirement already satisfied: ipython>=4.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (8.3.0)
Requirement already satisfied: ipykernel>=4.5.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (6.7.0)
Requirement already satisfied: nbformat>=4.2.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (5.4.0)
Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.1.0)
Requirement already satisfied: widgetsnbextension~=3.5.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (3.5.2)
Requirement already satisfied: ipython-genutils~=0.2.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.2.0)
Requirement already satisfied: MarkupSafe>=2.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jinja2>=2.9->folium>=0.13.0->geemap) (2.0.1)
Requirement already satisfied: google-resumable-media>=2.6.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api>=0.1.347->geemap) (2.6.0)
Requirement already satisfied: google-crc32c<2.0dev,>=1.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api>=0.1.347->geemap) (1.5.0)
Requirement already satisfied: google-cloud-core<3.0dev,>=2.3.0 in /home/jovyan/.local/python3-0.9.0/lib/python3.8/site-packages (from google-cloud-storage->earthengine-api>=0.1.347->geemap) (2.3.3)
Requirement already satisfied: idna<4,>=2.5 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api>=0.1.347->geemap) (3.3)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api>=0.1.347->geemap) (1.25.11)
Requirement already satisfied: certifi>=2017.4.17 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api>=0.1.347->geemap) (2021.10.8)
Requirement already satisfied: charset-normalizer~=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from requests->earthengine-api>=0.1.347->geemap) (2.0.10)
Requirement already satisfied: decorator in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ratelim->geocoder->geemap) (5.1.1)
Requirement already satisfied: googleapis-common-protos<2.0dev,>=1.52.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api>=0.1.347->geemap) (1.54.0)
Requirement already satisfied: setuptools>=40.3.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api>=0.1.347->geemap) (59.8.0)
Requirement already satisfied: protobuf>=3.12.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from google-api-core<3.0.0dev,>=1.21.0->google-api-python-client>=1.12.1->earthengine-api>=0.1.347->geemap) (3.19.3)
Requirement already satisfied: nest-asyncio in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.5.4)
Requirement already satisfied: debugpy<2.0,>=1.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.5.1)
Requirement already satisfied: tornado<7.0,>=4.2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (6.1)
Requirement already satisfied: jupyter-client<8.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (7.1.1)
Requirement already satisfied: matplotlib-inline<0.2.0,>=0.1.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.1.3)
Requirement already satisfied: pygments>=2.4.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.11.2)
Requirement already satisfied: pickleshare in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.7.5)
Requirement already satisfied: jedi>=0.16 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.18.1)
Requirement already satisfied: backcall in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.2.0)
Requirement already satisfied: stack-data in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.1.4)
Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (3.0.24)
Requirement already satisfied: pexpect>4.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.8.0)
Requirement already satisfied: jupyter-core in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.9.1)
Requirement already satisfied: fastjsonschema in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.16.1)
Requirement already satisfied: jsonschema>=2.6 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (3.2.0)
Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.4.1->earthengine-api>=0.1.347->geemap) (0.4.8)
Requirement already satisfied: notebook>=4.4.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (6.4.7)
Requirement already satisfied: parso<0.9.0,>=0.8.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jedi>=0.16->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.8.3)
Requirement already satisfied: attrs>=17.4.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jsonschema>=2.6->nbformat>=4.2.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (21.4.0)
Requirement already satisfied: pyrsistent>=0.14.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jsonschema>=2.6->nbformat>=4.2.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.18.1)
Requirement already satisfied: pyzmq>=13 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jupyter-client<8.0->ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (22.3.0)
Requirement already satisfied: entrypoints in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from jupyter-client<8.0->ipykernel>=4.5.1->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.3)
Requirement already satisfied: Send2Trash>=1.8.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.8.0)
Requirement already satisfied: argon2-cffi in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (21.3.0)
Requirement already satisfied: nbconvert in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (7.0.0)
Requirement already satisfied: prometheus-client in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.12.0)
Requirement already satisfied: terminado>=0.8.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.12.1)
Requirement already satisfied: ptyprocess>=0.5 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from pexpect>4.3->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.7.0)
Requirement already satisfied: wcwidth in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.2.5)
Requirement already satisfied: pure-eval in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.2.1)
Requirement already satisfied: executing in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.8.2)
Requirement already satisfied: asttokens in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.0.5)
Requirement already satisfied: argon2-cffi-bindings in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (21.2.0)
Requirement already satisfied: importlib-metadata>=3.6 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.2.0)
Requirement already satisfied: nbclient>=0.5.0 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.5.10)
Requirement already satisfied: defusedxml in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.7.1)
Requirement already satisfied: packaging in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (21.3)
Requirement already satisfied: bleach in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.1.0)
Requirement already satisfied: lxml in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.9.1)
Requirement already satisfied: beautifulsoup4 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (4.10.0)
Requirement already satisfied: tinycss2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.1.1)
Requirement already satisfied: mistune<3,>=2.0.3 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.0.4)
Requirement already satisfied: jupyterlab-pygments in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.1.2)
Requirement already satisfied: pandocfilters>=1.4.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.5.0)
Requirement already satisfied: zipp>=0.5 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from importlib-metadata>=3.6->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (3.7.0)
Requirement already satisfied: cffi>=1.0.1 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (1.15.0)
Requirement already satisfied: soupsieve>1.2 in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from beautifulsoup4->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.3.1)
Requirement already satisfied: webencodings in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (0.5.1)
Requirement already satisfied: pycparser in /cvmfs/cybergis.illinois.edu/software/conda/cybergisx/python3-0.9.0/lib/python3.8/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets->ipyfilechooser>=0.6.0->geemap) (2.21)

Working Directory Check

In [2]:
# Create a Path object for the current directory
current_directory = Path.cwd()

# Get the parent directory
parent_directory = current_directory.parent

print("Current Directory:", current_directory)
print("Parent Directory:", parent_directory)
Current Directory: /home/jovyan/work/GGIS407_23Fall/Project
Parent Directory: /home/jovyan/work/GGIS407_23Fall

Data Preparation

In [3]:
# Read CSV file into a pandas DataFrame
df = pd.read_csv('observations-360459.csv')

# Convert the "observed_on" column to a datetime data type
df['observed_on'] = pd.to_datetime(df['observed_on'])

# Extract the year and month
df['year'] = df['observed_on'].dt.year
df['month'] = df['observed_on'].dt.month

# Get the basic summary information of the dataset
# df.info()

# Filter out observation records without valid latitude or longitude entries
# also, we filter out those without valid observed time entries OR dated before the year of 2008 
# Google Street View images are not available before the year of 2008
filtered_df = df[df['latitude'].notna() & df['longitude'].notna() & df['observed_on'].notna() & (df['year'] >= 2008)] 

# Filter out observation records outside of North America
filtered_df = filtered_df[(filtered_df['place_country_name'] == 'United States') |
                          (filtered_df['place_country_name'] == 'Canada') |
                          (filtered_df['place_country_name'] == 'Mexico')]

print(f'In total, we have {str(len(filtered_df))} observation records of Sandhill Cranes across the North America since 2008!!!')
In total, we have 38559 observation records of Sandhill Cranes across the North America since 2008!!!

Overview (Map)

In [4]:
# Define the bounding box coordinates for North America in WGS 84
north_america_bbox = (-180, 20, -50, 85)  # (minx, miny, maxx, maxy)
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))

# Set up the figure
fig, ax = plt.subplots(figsize=(10, 6))

# Set the extent of the plot to the North America bounding box in WGS 84
ax.set_xlim(north_america_bbox[0], north_america_bbox[2])
ax.set_ylim(north_america_bbox[1], north_america_bbox[3])

# Build geopandas dataframe for mapping
gdf = gpd.GeoDataFrame(filtered_df, geometry=gpd.points_from_xy(filtered_df.longitude, filtered_df.latitude))
world.boundary.plot(ax=ax, linewidth=1)
gdf.plot(ax=ax, color='darkgreen', markersize=10)
plt.title('Locations of iNaturalist Sandhill Crane Observations, North America')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.show()

Explorative Analysis & Visualizations

In [5]:
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Plot the distribution by year
unique_years =  filtered_df['year'].unique()
axes[0].hist(filtered_df['year'], bins=len(unique_years), edgecolor='black', alpha=0.7)
axes[0].set_xlabel('Year')
axes[0].set_ylabel('Frequency')
axes[0].set_title('Distribution by Year')
axes[0].set_xticks(filtered_df['year'].unique())
axes[0].set_xticklabels(filtered_df['year'].unique(), rotation=90, ha="center")

# Plot the distribution by month
unique_months =  filtered_df['month'].unique()
axes[1].hist(filtered_df['month'], bins=len(unique_months), edgecolor='black', alpha=0.7)
axes[1].set_xlabel('Month')
axes[1].set_ylabel('Frequency')
axes[1].set_title('Distribution by Month')
axes[1].set_xticks(filtered_df['month'].unique())
fig.suptitle('Temporal Distribution of Sandhill Cranes in North America (2008-2023)\n', fontsize=16)
# plt.tight_layout()
# Add a caption below the plots
caption = "Data Source: iNaturalist (38,559 records)."
plt.figtext(0.5, -0.05, caption, ha="center", fontsize=10, color='grey')
plt.show()
In [6]:
# Combine region info for visualization purpose
top_20_states = filtered_df['place_admin1_name'].value_counts().head(20).index.tolist()
# Replace state names not in the top 20 with "Other Regions"
temp_state_viz = filtered_df
temp_state_viz.loc[~temp_state_viz['place_admin1_name'].isin(top_20_states), 'place_admin1_name'] = "Other Regions"

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Plot the distribution by country
unique_countries = filtered_df['place_country_name'].unique()
axes[0].hist(filtered_df['place_country_name'], bins=len(unique_countries), edgecolor='black', alpha=0.7)
axes[0].set_xlabel('Country')
axes[0].set_ylabel('Frequency')
axes[0].set_title('Distribution by Country')
axes[0].set_xticks(filtered_df['place_country_name'].unique())
axes[0].set_xticklabels(unique_countries, rotation=90, ha="center")

# Plot the distribution by state/province
unique_regions = temp_state_viz['place_admin1_name'].unique()
axes[1].hist(temp_state_viz['place_admin1_name'], bins=len(unique_regions), edgecolor='black', alpha=0.7)
axes[1].set_xlabel('State/Province')
axes[1].set_ylabel('Frequency')
axes[1].set_title('Distribution by State/Province')
axes[1].set_xticks(unique_regions)
axes[1].set_xticklabels(unique_regions, rotation=90, ha="center")

fig.suptitle('Spatial Distribution of Sandhill Cranes in North America (2008-2023)\n', fontsize=16)
# plt.tight_layout()
# Add a caption below the plots
caption = "Data Source: iNaturalist (38,559 records)."
plt.figtext(0.5, -0.1, caption, ha="center", fontsize=10, color='grey')
plt.show()

Clustering Analysis

In [7]:
# Extract coordinates for spatial clustering purpose
coordinates = list(zip(filtered_df['latitude'], filtered_df['longitude']))

# Set the number of clusters as 10
num_clusters = 10
kmeans = KMeans(num_clusters) 

# Perform the kmeans clustering algorithm
clusters = kmeans.fit_predict(coordinates)

# print(clusters)

# Add the cluster labels to the original DataFrame
filtered_df['Cluster'] = clusters

Random Sampling for Each Cluster Group

In [8]:
# Define the number of samples per cluster
samples_per_cluster = 100

# Group the DataFrame by 'Cluster' and sample within each group
# Note that if we would like to reproduce the same results, keep a specific random_state parameter for reproducibility
sampled_df = filtered_df.groupby('Cluster').apply(lambda group: group.sample(samples_per_cluster, random_state = 999))

# Reset the index
sampled_df = sampled_df.reset_index(drop=True)

# sampled_df
# sampled_df.columns

print(f"We have successfully extracted {str(len(sampled_df))} samples from our clustered observation records!")
We have successfully extracted 1000 samples from our clustered observation records!

Land Cover Information for Sampled Observations Based on Cropland Data Layer

Generate 100-meter Buffers for Each Sampled Point

In [9]:
# Set Buffer Directory
buffer_dir = os.path.normpath(f"{current_directory}/sampled_buffers")
if not os.path.exists(buffer_dir):
    os.mkdir(buffer_dir)
    
# Create a GeoDataFrame based on sampled_df
sampled_gdf = gpd.GeoDataFrame(sampled_df, geometry=gpd.points_from_xy(sampled_df.longitude, sampled_df.latitude))
# Specify WGS84 as the reference coordinate system for our sampled dataset 
sampled_gdf = sampled_gdf.set_crs('EPSG:4326')
# Reproject it Web Mercator (in meters)
sampled_gdf = sampled_gdf.to_crs('EPSG:3857')

# Set buffer size in meters
buffer_size = 100

# Create an empty GeoDataFrame for the buffers to be generated
sampled_buffer_gdf = gpd.GeoDataFrame(columns=['id','geometry', 'year'])

# Generate buffers for the GeoDataFrame
buffer_size = 100  # Buffer size in meters

# Set the column entries of buffer dataset
sampled_buffer_gdf['id'] = sampled_gdf['id']
sampled_buffer_gdf['year'] = sampled_gdf['year']
sampled_buffer_gdf['geometry'] = sampled_gdf['geometry'].buffer(buffer_size)

# Save buffers by year
for YEAR in range(2013, 2024):
    
    # Define the output shapefile name
    output_buffer = os.path.normpath(f"{buffer_dir}/sampled_buffer_{YEAR}.shp")  
    
    buffer_gdf = sampled_buffer_gdf[sampled_buffer_gdf['year'] == YEAR]

    # Save generated buffers in the specified directory (for back up purposes)
    buffer_gdf.to_file(output_buffer, driver='ESRI Shapefile')

Use geemap to Calculate the Crop Type Area Ratio for Each Buffer

In [10]:
# To use geemap or any other Google Earth Engine related features in python, we need to authenticate it first.
ee.Authenticate()
ee.Initialize()
Successfully saved authorization token.
In [11]:
for YEAR in range(2013, 2024):
    
    # Specify the buffer storage path 
    output_buffer = os.path.normpath(f"{buffer_dir}/sampled_buffer_{YEAR}.shp")  
    
    # Convert our shapefile to ee (Earth Engine) objects
    buffer = geemap.shp_to_ee(output_buffer)
    
    # Note that since the 2023 CDL have not be published yet, we use the 2022 CDL as a proxy
    if YEAR == 2023:
        dataset = ee.Image("USDA/NASS/CDL/" + str(2022))
    else:
        dataset = ee.Image("USDA/NASS/CDL/" + str(YEAR))
    
    # Select the corresponding band in CDL dataset for crop type information
    cropland = ee.Image(dataset.select('cropland'))
    
    # Set up the path for CDL statistics output
    cdl_stats = os.path.normpath(f'{current_directory}/cdl_stats_{YEAR}.csv')
    
    # Use zonal_statistics_by_group to calculate the area ratio of each crop type within each buffer
    # statistics_type can be either 'SUM' or 'PERCENTAGE'
    # denominator can be used to convert square meters to other areal units, such as square kilometers
    geemap.zonal_statistics_by_group(
        cropland.clip(buffer),
        buffer,
        cdl_stats,
        statistics_type='PERCENTAGE',
        #denominator=1000000,
        #decimal_places=2,
    )
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/f489c3d72f8b8dfc015d2f92cf761e13-32b9f58921f6c3423ee48e00c134c595:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2013.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/2e8be02da7ace0babafc4ba8e401b948-024582f8415b405ee6f0f9fcff801e36:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2014.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/697c714f320af33b69e0845ac86f3979-5670d67253ad2922f84e041647aa7d23:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2015.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/49e98b868aea51e09ce733a10eeed15e-af5ce036a3f13b7ffd1b21789ce448c7:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2016.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/f648eb54c0b08a6bdd28bd5de34acc96-f8234e08706bf6b632df7533fe9ff835:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2017.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/250dd7414e06dfb8942eebaae02f2e1f-048196115b2dc375bc0fc93b4a98a1f1:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2018.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/2530255c343bca01e9428b3dc63540f6-e3b2549321a076a78e9192c0218ce230:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2019.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/1afd37d2086bdd71529dd1f8cf9c69d5-9a5acf177d8273c3d694da4dbfd2dbc3:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2020.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/16dc6d65b11ed8921f44bfa66bad2ed7-7694bfd845a38b8a00330fd01c7e107f:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2021.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/509a30d7dff25f669c7c7e7137600adc-0c7b5d220d35ce5aeeea0e19f17a5114:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2022.csv
Computing ... 
Generating URL ...
Downloading data from https://earthengine.googleapis.com/v1/projects/earthengine-legacy/tables/6ec96da8c6f72926f3f48298cceb938e-1d05331e4887ae790f8176420c51c8be:getFeatures
Please wait ...
Data downloaded to /home/jovyan/work/GGIS407_23Fall/Project/cdl_stats_2023.csv
In [12]:
# Use the glob module to get a list of CSV file paths in a directory.
file_paths = glob.glob('cdl_stats_*')

# Create an empty list to store DataFrames, one for each CSV file
dataframes = []

# Read each CSV file into a DataFrame and append it to the list of DataFrames
for file_path in file_paths:
    df = pd.read_csv(file_path)
    dataframes.append(df)

# Concatenate the DataFrames vertically to merge them while retaining all columns
merged_df = pd.concat(dataframes, ignore_index=True)

# Fill missing values (empty entries) with 0
merged_df.fillna(0, inplace=True)

# Replace 'column_to_remove' with the name of the column you want to remove
column_to_remove = 'system:index'
if column_to_remove in merged_df.columns:
    merged_df.drop(columns=[column_to_remove], inplace=True)
column_to_remove = 'Class_sum'
if column_to_remove in merged_df.columns:
    merged_df.drop(columns=[column_to_remove], inplace=True)

# Save the merged DataFrame to a new CSV file if needed
merged_df.to_csv('merged_file.csv', index=False)
In [13]:
merged_df.columns
Out[13]:
Index(['Class_61', 'Class_190', 'Class_195', 'Class_141', 'Class_152',
       'Class_21', 'Class_123', 'Class_36', 'Class_131', 'Class_142',
       'Class_121', 'Class_176', 'Class_124', 'Class_1', 'Class_3', 'Class_5',
       'id', 'year', 'Class_111', 'Class_122', 'Class_24', 'Class_37',
       'Class_212', 'Class_2', 'Class_4', 'Class_76', 'Class_33', 'Class_23',
       'Class_112', 'Class_143', 'Class_6', 'Class_41', 'Class_42', 'Class_69',
       'Class_75', 'Class_205', 'Class_28', 'Class_216', 'Class_59',
       'Class_217'],
      dtype='object')
In [14]:
# Load the CSV file containing the crop mapping
codebook_df = pd.read_csv('CDL_crop_codebook.csv')

# Create a custom mapping dictionary from the codebook DataFrame
custom_mapping = dict(zip(codebook_df['Codes'], codebook_df['Class_Names']))

# Iterate through the DataFrame's column names
for col_name in merged_df.columns:
    if (col_name != 'id') & (col_name != 'year'):

        # Extract the numerical part from the column name
        num = int(col_name.split('_')[1])

        # Look up the corresponding crop name from the custom mapping
        if num in custom_mapping:
            crop_name = custom_mapping[num]
            merged_df.rename(columns={col_name: crop_name}, inplace=True)
In [15]:
merged_df.columns
Out[15]:
Index(['Fallow/Idle Cropland', 'Woody Wetlands', 'Herbaceous Wetlands',
       'Deciduous Forest', 'Shrubland', 'Barley', 'Developed/Med Intensity',
       'Alfalfa', 'Barren', 'Evergreen Forest', 'Developed/Open Space',
       'Grass/Pasture', 'Developed/High Intensity', 'Corn', 'Rice', 'Soybeans',
       'id', 'year', 'Open Water', 'Developed/Low Intensity', 'Winter Wheat',
       'Other Hay/Non Alfalfa', 'Oranges', 'Cotton', 'Sorghum', 'Walnuts',
       'Safflower', 'Spring Wheat', 'Perennial Ice/Snow ', 'Mixed Forest',
       'Sunflower', 'Sugarbeets', 'Dry Beans', 'Grapes', 'Almonds',
       'Triticale', 'Oats', 'Peppers', 'Sod/Grass Seed', 'Pomegranates'],
      dtype='object')
In [16]:
merged_df
Out[16]:
Fallow/Idle Cropland Woody Wetlands Herbaceous Wetlands Deciduous Forest Shrubland Barley Developed/Med Intensity Alfalfa Barren Evergreen Forest ... Sunflower Sugarbeets Dry Beans Grapes Almonds Triticale Oats Peppers Sod/Grass Seed Pomegranates
0 0.0 0.851855 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 0.0 1.000000 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 0.0 0.000000 1.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 0.0 0.000000 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 1.0 0.000000 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
963 0.0 0.000000 0.000000 0.0 1.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
964 0.0 0.942866 0.057134 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
965 0.0 0.000000 0.236111 0.0 0.25 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
966 0.0 0.000000 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
967 0.0 0.000000 0.000000 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

968 rows × 40 columns

Example: Getting all Points with >50% Corn

In [17]:
corn_df = merged_df[merged_df['Corn'] > 0.5]
print(f"We got {len(corn_df)} points with larger than 50% of its 100-meter buffer covered by corn in our sample!!!")
We got 24 points with larger than 50% of its 100-meter buffer covered by corn in our sample!!!
In [18]:
corn_df['id'].tolist()
Out[18]:
[4373626,
 10747177,
 17932911,
 146770529,
 147172132,
 151970324,
 180432061,
 144993828,
 142121284,
 119643906,
 109229187,
 113250058,
 141789316,
 21407838,
 8919418,
 69743841,
 103853866,
 68025771,
 103937493,
 82995323,
 73540094,
 41257012,
 43245038,
 44267370]
In [19]:
joined_corn_df = pd.merge(corn_df['id'], filtered_df, on='id')
In [ ]:
 
In [20]:
joined_corn_df[['id','observed_on','latitude','longitude']]
Out[20]:
id observed_on latitude longitude
0 4373626 2016-10-15 38.147981 -121.457291
1 10747177 2018-04-08 46.996799 -119.587029
2 17932911 2018-10-28 38.745567 -108.135105
3 146770529 2023-01-17 38.207037 -121.503203
4 147172132 2023-01-21 38.206757 -121.504154
5 151970324 2023-03-21 43.036608 -112.481733
6 180432061 2023-08-27 38.891274 -87.134136
7 144993828 2022-12-23 38.197551 -121.501237
8 142121284 2022-11-13 38.160591 -121.476371
9 119643906 2022-05-30 40.221247 -111.707807
10 109229187 2022-03-15 44.688756 -92.962979
11 113250058 2022-04-28 44.688790 -92.965724
12 141789316 2022-11-11 34.937956 -106.056497
13 21407838 2019-03-19 42.457908 -89.118133
14 8919418 2017-05-08 33.705039 -80.501501
15 69743841 2021-02-17 38.212669 -121.501287
16 103853866 2021-12-28 38.171917 -121.512593
17 68025771 2021-01-13 38.164362 -121.516341
18 103937493 2021-11-25 38.157489 -121.465637
19 82995323 2021-06-12 45.280931 -92.814049
20 73540094 2021-04-10 45.544775 -93.860925
21 41257012 2020-03-29 43.756469 -116.987699
22 43245038 2020-04-16 44.041486 -92.563167
23 44267370 2020-04-29 41.083733 -96.245114
In [21]:
# Convert values from two columns to lists
lat_list = joined_corn_df['latitude'].values.tolist()
lon_list = joined_corn_df['longitude'].values.tolist()
List_coord = list(zip(lat_list, lon_list))
In [22]:
List_coord
Out[22]:
[(38.1479810716, -121.4572906494),
 (46.9967990222, -119.5870287034),
 (38.745567, -108.135105),
 (38.2070368747, -121.5032030789),
 (38.2067568135, -121.5041537504),
 (43.0366083333, -112.4817333333),
 (38.8912740294, -87.1341360773),
 (38.1975511174, -121.5012371103),
 (38.1605905419, -121.4763713378),
 (40.2212469, -111.7078073),
 (44.6887559587, -92.9629791467),
 (44.6887903001, -92.9657239561),
 (34.9379556365, -106.0564966812),
 (42.4579083333, -89.1181333333),
 (33.7050387545, -80.5015014287),
 (38.2126693424, -121.5012868844),
 (38.1719167, -121.51259327),
 (38.1643617, -121.5163415),
 (38.1574892772, -121.4656373089),
 (45.280931488, -92.814049077),
 (45.5447747988, -93.8609251621),
 (43.7564685844, -116.9876991212),
 (44.0414864328, -92.5631665438),
 (41.0837329679, -96.2451140081)]

Google Street View Fetching

Function: downloadGSVMetaData(LIST_COORDINATES, TASK)

In [23]:
def downloadGSVMetaData(LIST_COORDINATES, TASK):

    """
    Downloads Google Street View metadata for a list of coordinates.

    Parameters:
    - LIST_COORDINATES (list of tuples): A list of latitude and longitude coordinates to search for GSV imagery.
    - TASK (str): A task identifier to be used in the generated metadata CSV file name.

    This function searches for GSV imagery around the provided coordinates and saves metadata to a CSV file.
    """
    
    # Generate the file path for the metadata CSV file
    path = os.path.normpath("GSV_METADATA_{}.csv".format(TASK))
    
    # Open the metadata CSV file for appending
    metadata = open(path, "a")
    
    # Create a CSV writer object with newline terminator
    writer = csv.writer(metadata, lineterminator='\n')
    
    # Write the header row to the CSV file
    writer.writerow(['ID', 'Panoid', 'Lat', 'Lon', 'Heading', 'Pitch', 'Roll', 'Year', 'Month']) 
    
    # Initialize an index
    index = 0
    
    # Iterate through the list of coordinates (LAT, LON)
    for LAT, LON in LIST_COORDINATES:
        try:
            # Use the search_panoramas function to get panorama information
            panoids = search_panoramas(lat=LAT, lon=LON)
        except:
            # Handle errors if the search_panoramas function fails
            print("Index out of range error at lat={}, lon={}: no GSV imagery in this area.".format(LAT, LON))
        print("Searching GSV Images around...({}, {})".format(LAT, LON))
        if len(panoids)>0: 
            for id in range(len(panoids)):
                if panoids[id].date:
                    year = int(panoids[id].date[:4])
                    month= int(panoids[id].date[5:])
                    index += 1
                    metadata = open(path, "a")
                    writer = csv.writer(metadata, lineterminator = '\n')
                    writer.writerow([index, panoids[id].pano_id,panoids[id].lat,panoids[id].lon,panoids[id].heading,panoids[id].pitch, panoids[id].roll,year,month])
                    print(index)  

        else:
            print('no GSV imagery found in this area')
            continue
In [24]:
downloadGSVMetaData(List_coord, 'CORN')
Searching GSV Images around...(38.1479810716, -121.4572906494)
no GSV imagery found in this area
Searching GSV Images around...(46.9967990222, -119.5870287034)
no GSV imagery found in this area
Searching GSV Images around...(38.745567, -108.135105)
no GSV imagery found in this area
Searching GSV Images around...(38.2070368747, -121.5032030789)
Searching GSV Images around...(38.2067568135, -121.5041537504)
no GSV imagery found in this area
Searching GSV Images around...(43.0366083333, -112.4817333333)
no GSV imagery found in this area
Searching GSV Images around...(38.8912740294, -87.1341360773)
no GSV imagery found in this area
Searching GSV Images around...(38.1975511174, -121.5012371103)
no GSV imagery found in this area
Searching GSV Images around...(38.1605905419, -121.4763713378)
no GSV imagery found in this area
Searching GSV Images around...(40.2212469, -111.7078073)
no GSV imagery found in this area
Searching GSV Images around...(44.6887559587, -92.9629791467)
no GSV imagery found in this area
Searching GSV Images around...(44.6887903001, -92.9657239561)
no GSV imagery found in this area
Searching GSV Images around...(34.9379556365, -106.0564966812)
no GSV imagery found in this area
Searching GSV Images around...(42.4579083333, -89.1181333333)
no GSV imagery found in this area
Searching GSV Images around...(33.7050387545, -80.5015014287)
no GSV imagery found in this area
Searching GSV Images around...(38.2126693424, -121.5012868844)
Searching GSV Images around...(38.1719167, -121.51259327)
no GSV imagery found in this area
Searching GSV Images around...(38.1643617, -121.5163415)
no GSV imagery found in this area
Searching GSV Images around...(38.1574892772, -121.4656373089)
no GSV imagery found in this area
Searching GSV Images around...(45.280931488, -92.814049077)
no GSV imagery found in this area
Searching GSV Images around...(45.5447747988, -93.8609251621)
1
Searching GSV Images around...(43.7564685844, -116.9876991212)
Searching GSV Images around...(44.0414864328, -92.5631665438)
no GSV imagery found in this area
Searching GSV Images around...(41.0837329679, -96.2451140081)
no GSV imagery found in this area
In [25]:
gsv_df = pd.read_csv('GSV_METADATA_CORN.csv').drop_duplicates()
gsv_df
Out[25]:
ID Panoid Lat Lon Heading Pitch Roll Year Month
0 ID Panoid Lat Lon Heading Pitch Roll Year Month
1 1 YdyDug5KipDP88_g3LlzcA 38.2613558375869 -121.4406002341907 169.7516479492188 93.66971588134766 0.8723912239074707 2007 6
2 2 zjOWG3903_sas4CstIlUWg 38.26135944130262 -121.440610609495 169.9563446044922 88.99956512451172 1.432158350944519 2015 8
3 3 3kA8skjnYcwFXRB4WofR8w 38.26133969218689 -121.4406068604355 169.4710083007812 89.78001403808594 1.364865064620972 2016 11
4 4 5lZ2CQ6r13vrIyEE1fB9CA 30.62476293717697 -97.39541297004071 341.0656433105469 94.39850616455078 1.765115857124329 2008 2
5 5 4v3QNsEpA7dGAcmNV77-tg 30.62476069881489 -97.39543697001042 156.5848693847656 89.1475830078125 0.5970472097396851 2009 5
6 6 62v-z86n9AVfzwO_a25BNg 30.62474089233968 -97.39541231024785 339.2624206542969 89.32353973388672 4.39376974105835 2011 5
7 7 FjH1CP_ZEqDyOVnTeVPKSg 30.62474800067029 -97.39540703190518 339.759521484375 89.33876037597656 1.649017810821533 2019 2
8 8 72C1xZJJiczquY1mNCcz4w 30.62477748232022 -97.39543499063116 339.3891906738281 90.19628143310547 2.001705646514893 2021 12
9 9 Tg0ssRBHpm9nbY6Qz-pCSQ 46.33407233763008 -119.3732652217733 340.61767578125 89.03545379638672 2.730541944503784 2012 4
10 10 eFWOzRvfUvGS5fqcHXGVeQ 45.54476323631403 -93.86085421527041 89.4974594116211 95.06910705566406 1.029643774032593 2009 6
34 1 eFWOzRvfUvGS5fqcHXGVeQ 45.54476323631403 -93.86085421527041 89.4974594116211 95.06910705566406 1.029643774032593 2009 6

Download GSV Panoramas

In [ ]:
Panoids = gsv_df['Panoid'].iloc[1:len(gsv_df)].tolist()

# Set panoramas directory
pano_dir = os.path.normpath(f"{current_directory}/panorama/")
if not os.path.exists(pano_dir):
    os.mkdir(pano_dir)

for Panoid in Panoids:
    meta_info = gsv_df[gsv_df['Panoid'] == Panoid]
    save_dir = f"{pano_dir}/{Panoid}_{meta_info['Lat'].values[0]}_{meta_info['Lon'].values[0]}_{meta_info['Heading'].values[0]}_{meta_info['Year'].values[0]}_{meta_info['Month'].values[0]}.jpg"
    print(save_dir)
    try:
        image = get_panorama(Panoid)
    except:
        print(str(Panoid) + " has problems with downloading...")
        continue
    image.save(save_dir, "jpeg")
    print('Saved Successfully!')
/home/jovyan/work/GGIS407_23Fall/Project/panorama/YdyDug5KipDP88_g3LlzcA_38.2613558375869_-121.4406002341907_169.7516479492188_2007_6.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/zjOWG3903_sas4CstIlUWg_38.26135944130262_-121.440610609495_169.9563446044922_2015_8.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/3kA8skjnYcwFXRB4WofR8w_38.26133969218689_-121.4406068604355_169.4710083007812_2016_11.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/5lZ2CQ6r13vrIyEE1fB9CA_30.62476293717697_-97.39541297004071_341.0656433105469_2008_2.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/4v3QNsEpA7dGAcmNV77-tg_30.62476069881489_-97.39543697001042_156.5848693847656_2009_5.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/62v-z86n9AVfzwO_a25BNg_30.62474089233968_-97.39541231024785_339.2624206542969_2011_5.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/FjH1CP_ZEqDyOVnTeVPKSg_30.62474800067029_-97.39540703190518_339.759521484375_2019_2.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/72C1xZJJiczquY1mNCcz4w_30.62477748232022_-97.39543499063116_339.3891906738281_2021_12.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/Tg0ssRBHpm9nbY6Qz-pCSQ_46.33407233763008_-119.3732652217733_340.61767578125_2012_4.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/eFWOzRvfUvGS5fqcHXGVeQ_45.54476323631403_-93.86085421527041_89.4974594116211_2009_6.jpg
Saved Successfully!
/home/jovyan/work/GGIS407_23Fall/Project/panorama/eFWOzRvfUvGS5fqcHXGVeQ_45.54476323631403_-93.86085421527041_89.4974594116211_2009_6.jpg
In [ ]:
def tiles_info(panoid, width, length, zoom=5):
    
    """
    Generate a list of a panorama's tiles and their position.
    The format is (x, y, filename, fileurl)
    """

    image_url =  "https://cbks2.google.com/cbk?cb_client=maps_sv.tactile&authuser=%200&output=tile&hl=en&panoid={}&zoom={}&x={}&y={}"

    # The tiles positions
    coord = list(itertools.product(range(length), range(width)))
    tiles = [(x, y, "%s_%dx%d.jpg" % (panoid, x, y), image_url.format(panoid, zoom, x, y)) for x, y in coord]

    return tiles
In [ ]:
def download_panorama_v3(panoid, width,length,zoom=5,panorama_path=None, disp=False):

    '''

    save image information in a buffer. 

    input:

        panoid: which is an id of image on google maps

        zoom: larger number -> higher resolution, from 1 to 5, better less than 3, some location will fail when zoom larger than 3

        disp: verbose of downloading progress, basically you don't need it

    output:

        panorama image (uncropped)

    '''

    tile_width = 512

    tile_height = 512

    img_w, img_h = int(np.ceil(416*(2**zoom)/tile_width)*tile_width), int(np.ceil(416*( 2**(zoom-1) )/tile_width)*tile_width)

    tiles = tiles_info( panoid, length,width,zoom=zoom)

    valid_tiles = []

    # function of download_tiles

    for i, tile in enumerate(tiles):

        x, y, fname, url = tile

        if disp and i % 20 == 0:

            print("Image %d / %d" % (i, len(tiles)))

        if x*tile_width < img_w and y*tile_height < img_h: # tile is valid

            # Try to download the image file

            while True:

                try:

                    response = requests.get(url, stream=True)

                    break

                except requests.ConnectionError:

                    print("Connection error. Trying again in 2 seconds.")

                    time.sleep(2)

            valid_tiles.append( Image.open(BytesIO(response.content)) )

            del response

 

    # function to stich

    panorama = Image.new('RGB', (img_w, img_h))

    i = 0

    for x, y, fname, url in tiles:

        if x*tile_width < img_w and y*tile_height < img_h: # tile is valid

            tile = valid_tiles[i]

            i+=1

            panorama.paste(im=tile, box=(x*tile_width, y*tile_height))

    im.imwrite(panorama_path, panorama)

    # return np.array(panorama)
In [ ]: