In this tutorial, the basics of creating map gifs is introduced
/content/drive/My Drive/Software Development Documents/dataplay/dataplay/gifmap.py:34: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)
/usr/local/lib/python3.7/dist-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
  """)

About this Tutorial:

Binder Binder Binder Open Source Love svg3

NPM License Active Python Versions GitHub last commit

GitHub stars GitHub watchers GitHub forks GitHub followers

Tweet Twitter Follow

Whats Inside?

Description: This notebook was made to demonstrate how to make a gif map by merging 2 datasets. The first being a dataset containing mappable coordinates onto which the second dataset may mapping its information of interest.

This lab is split into two sections.

  • The first part of this lab provides help you understand the basics operations.
  • The second part of this notebook provides a single python function that handles everything covered in this lab (and more).

Input(s):

  • Dataset (points/ bounds) url
  • Points/ bounds geometry column(s)
  • Points/ bounds crs's
  • Points/ bounds mapping color(s)
  • New filename

Output: Files, Gif

*please note

  • This lab in particular makes heavy use of data that is not publicly accessible. Later labs use functions created here on public data.
  • A table of contents is provided in the menu to the left.
  • And, that this notebook has been optimized for Google Colabs ran on a Chrome Browser.
  • While still fully usable, non-critical section of code (eg. Python Magics and HTML) may break if used in a different enviornment.

Guided Walkthrough

SETUP

Import Modules

%%capture
!pip install geopandas
!pip install VitalSigns
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:20: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.

Configure Enviornment

pd.set_option('display.expand_frame_repr', False)
pd.set_option('display.precision', 2)
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

# pd.set_option('display.expand_frame_repr', False)
# pd.set_option('display.precision', 2)
# pd.reset_option('max_colwidth')
pd.set_option('max_colwidth', 20)
# pd.reset_option('max_colwidth')

Conveince Functions

getColName[source]

getColName(df, col)

getColByName[source]

getColByName(df, col)

addKey[source]

addKey(df, fi, col)

nullIfEqual[source]

nullIfEqual(df, c1, c2)

sumInts[source]

sumInts(df)

This next function was created in previous colabs. We are going to recycle it for use in this lab

Retrieve GIS Data

regexMatchingColumnsToMakeTheGifWith = 'hhchpov'

Import Data of Interest: (HHCHPOV)

# BNIA ArcGIS Homepage: https://data-bniajfi.opendata.arcgis.com/
final = intaker.Intake.getData("https://services1.arcgis.com/mVFRs7NF4iFitgbY/ArcGIS/rest/services/"+regexMatchingColumnsToMakeTheGifWith.capitalize()+"/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=true&f=pgeojson")[['CSA2010', 'hhchpov15', 'hhchpov16', 'hhchpov17', 'hhchpov18', 'hhchpov19', 'geometry']]
final.head(1)
CSA2010 hhchpov15 hhchpov16 hhchpov17 hhchpov18 hhchpov19 geometry
0 Allendale/Irving... 38.93 34.73 32.77 35.27 32.6 POLYGON ((-76.65...
final.head(1)
CSA2010 hhchpov15 hhchpov16 hhchpov17 hhchpov18 hhchpov19 geometry
0 Allendale/Irving... 38.93 34.73 32.77 35.27 32.6 POLYGON ((-76.65...
final.plot(column='hhchpov15')
<matplotlib.axes._subplots.AxesSubplot at 0x7ff0e82e6a50>

MAPPING

Fantastic!

Your data is all together in a single dataset.

now what?

First lets take the centerpoint of each geometry. This will be where we place text on the each geometry.

final['centroid'] = final['geometry'].representative_point()

Lets make a GIF

pd.set_option('precision', 0)
fileNames = []
labelBounds = True
specialLabelCol = False # Labels on GEOM Centroids
saveGifAs = './test.gif'
label = 'Household Poverty'
annotation = 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance' 
fontsize='22'

Data was successfully merged across all years and geometry.

Now we want the tractname, geometry, and the specific column we want to make a gif from.

td = final.copy()
td = td.reindex(sorted(td.columns), axis=1)
# This will ensure numbers are rounded to whole digits when displaying the reults

gifCols = td.filter(regex=regexMatchingColumnsToMakeTheGifWith).columns.values

td[gifCols] = td[gifCols].fillna(-1)
td[gifCols] = td[gifCols].astype('int32')
td.head()
CSA2010 centroid geometry hhchpov15 hhchpov16 hhchpov17 hhchpov18 hhchpov19
0 Allendale/Irving... POINT (-76.67653... POLYGON ((-76.65... 38 34 32 35 32
1 Beechfield/Ten H... POINT (-76.70331... POLYGON ((-76.69... 19 21 23 21 15
2 Belair-Edison POINT (-76.57463... POLYGON ((-76.56... 36 36 34 39 41
3 Brooklyn/Curtis ... POINT (-76.58389... MULTIPOLYGON (((... 45 46 46 39 41
4 Canton POINT (-76.58204... POLYGON ((-76.57... 5 2 4 4 4

Data exploration is essential! But not covered in this lab.

td.filter(regex=regexMatchingColumnsToMakeTheGifWith).hist()
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dcea8d50>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dcebcbd0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dce75290>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dce24910>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dcdd3f90>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff0dce0f650>]],
      dtype=object)

Everything is almost ready to start making our gifmap!

Lets just get the minimum and maximum values so that our color ramp will have consistent values on each picture.

mins = []
maxs = []
for col in td.filter(regex=regexMatchingColumnsToMakeTheGifWith).columns:
  mins.append(td[col].min())
  maxs.append(td[col].max())
print(mins, maxs)

# set the min and max range for the choropleth map
vmin, vmax = min(mins), max(maxs)
print('Smallest Value: ', vmin, ', Max Value:', vmax)
[3, 0, 0, 0, 0] [65, 65, 64, 60, 66]
Smallest Value:  0 , Max Value: 66
merged = td.copy()
for indx, col in enumerate(merged.filter(regex="hhchpov").columns):
    print('INDEX', indx)
    print('Col: '+str(col) )
    image_name = col+'.jpg'
    fileNames.append(image_name)

    # create map, UDPATE: added plt.Normalize to keep the legend range the same for all maps
    fig = merged.plot(column=col, cmap='Blues', figsize=(10,10), 
        linewidth=0.8, edgecolor='0.8', vmin=vmin, vmax=vmax,
        legend=True, norm=plt.Normalize(vmin=vmin, vmax=vmax) 
    )
    
    # https://stackoverflow.com/questions/38899190/geopandas-label-polygons
    if labelBounds:
      labelColumn = col
      if specialLabelCol: labelColumn = specialLabelCol
      merged.apply(lambda x: fig.annotate(s=x[labelColumn], xy=x.geometry.centroid.coords[0], ha='center'),axis=1);
    
    # remove axis off chart and set title
    fig.axis('off')
    fig.set_title(str(col.replace("hhchpov", "Houshold Childhood Poverty 20")), fontdict={'fontsize': fontsize, 'fontweight' : '3'})
    
    # create an annotation for the  data source
    fig.annotate(annotation,
            xy=(0.1, .08), xycoords='figure fraction',
            horizontalalignment='left', verticalalignment='top',
            fontsize=10, color='#555555')
    
    # this will save the figure as a high-res png in the output path. you can also save as svg if you prefer.
    # filepath = os.path.join(output_path, image_name)
    chart = fig.get_figure()
    # fig.savefig(“map_export.png”, dpi=300)
    chart.savefig(image_name, dpi=300)
    plt.close(chart)
        
images = []
for filename in fileNames:
    images.append(imageio.imread(filename))
imageio.mimsave(saveGifAs, images, fps=.5)


# This will print out a picture of each picture in the gifmap.
from PIL import Image
import requests
from io import BytesIO
for filename in fileNames:
    img = Image.open(filename) 
    size = 328, 328
    img.thumbnail(size, Image.ANTIALIAS)
    img
INDEX 0
Col: hhchpov15
0     Annotation(-76.6...
1     Annotation(-76.7...
2     Annotation(-76.5...
3     Annotation(-76.5...
4     Annotation(-76.5...
5     Annotation(-76.5...
6     Annotation(-76.6...
7     Annotation(-76.6...
8     Annotation(-76.5...
9     Annotation(-76.5...
10    Annotation(-76.6...
11    Annotation(-76.7...
12    Annotation(-76.6...
13    Annotation(-76.6...
14    Annotation(-76.6...
15    Annotation(-76.5...
16    Annotation(-76.6...
17    Annotation(-76.6...
18    Annotation(-76.6...
19    Annotation(-76.6...
20    Annotation(-76.6...
21    Annotation(-76.6...
22    Annotation(-76.6...
23    Annotation(-76.6...
24    Annotation(-76.5...
25    Annotation(-76.5...
26    Annotation(-76.5...
27    Annotation(-76.5...
28    Annotation(-76.7...
29    Annotation(-76.6...
30    Annotation(-76.5...
31    Annotation(-76.5...
32    Annotation(-76.5...
33    Annotation(-76.6...
34    Annotation(-76.6...
35    Annotation(-76.5...
36    Annotation(-76.6...
37    Annotation(-76.6...
38    Annotation(-76.6...
39    Annotation(-76.5...
40    Annotation(-76.5...
41    Annotation(-76.5...
42    Annotation(-76.5...
43    Annotation(-76.6...
44    Annotation(-76.6...
45    Annotation(-76.6...
46    Annotation(-76.6...
47    Annotation(-76.6...
48    Annotation(-76.5...
49    Annotation(-76.6...
50    Annotation(-76.6...
51    Annotation(-76.6...
52    Annotation(-76.6...
53    Annotation(-76.6...
54    Annotation(-76.6...
dtype: object
(-76.72049409966101, -76.52058984362787, 39.18850298950285, 39.38074613803655)
Text(0.5, 1.0, 'Houshold Childhood Poverty 2015')
Text(0.1, 0.08, 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance')
INDEX 1
Col: hhchpov16
0     Annotation(-76.6...
1     Annotation(-76.7...
2     Annotation(-76.5...
3     Annotation(-76.5...
4     Annotation(-76.5...
5     Annotation(-76.5...
6     Annotation(-76.6...
7     Annotation(-76.6...
8     Annotation(-76.5...
9     Annotation(-76.5...
10    Annotation(-76.6...
11    Annotation(-76.7...
12    Annotation(-76.6...
13    Annotation(-76.6...
14    Annotation(-76.6...
15    Annotation(-76.5...
16    Annotation(-76.6...
17    Annotation(-76.6...
18    Annotation(-76.6...
19    Annotation(-76.6...
20    Annotation(-76.6...
21    Annotation(-76.6...
22    Annotation(-76.6...
23    Annotation(-76.6...
24    Annotation(-76.5...
25    Annotation(-76.5...
26    Annotation(-76.5...
27    Annotation(-76.5...
28    Annotation(-76.7...
29    Annotation(-76.6...
30    Annotation(-76.5...
31    Annotation(-76.5...
32    Annotation(-76.5...
33    Annotation(-76.6...
34    Annotation(-76.6...
35    Annotation(-76.5...
36    Annotation(-76.6...
37    Annotation(-76.6...
38    Annotation(-76.6...
39    Annotation(-76.5...
40    Annotation(-76.5...
41    Annotation(-76.5...
42    Annotation(-76.5...
43    Annotation(-76.6...
44    Annotation(-76.6...
45    Annotation(-76.6...
46    Annotation(-76.6...
47    Annotation(-76.6...
48    Annotation(-76.5...
49    Annotation(-76.6...
50    Annotation(-76.6...
51    Annotation(-76.6...
52    Annotation(-76.6...
53    Annotation(-76.6...
54    Annotation(-76.6...
dtype: object
(-76.72049409966101, -76.52058984362787, 39.18850298950285, 39.38074613803655)
Text(0.5, 1.0, 'Houshold Childhood Poverty 2016')
Text(0.1, 0.08, 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance')
INDEX 2
Col: hhchpov17
0     Annotation(-76.6...
1     Annotation(-76.7...
2     Annotation(-76.5...
3     Annotation(-76.5...
4     Annotation(-76.5...
5     Annotation(-76.5...
6     Annotation(-76.6...
7     Annotation(-76.6...
8     Annotation(-76.5...
9     Annotation(-76.5...
10    Annotation(-76.6...
11    Annotation(-76.7...
12    Annotation(-76.6...
13    Annotation(-76.6...
14    Annotation(-76.6...
15    Annotation(-76.5...
16    Annotation(-76.6...
17    Annotation(-76.6...
18    Annotation(-76.6...
19    Annotation(-76.6...
20    Annotation(-76.6...
21    Annotation(-76.6...
22    Annotation(-76.6...
23    Annotation(-76.6...
24    Annotation(-76.5...
25    Annotation(-76.5...
26    Annotation(-76.5...
27    Annotation(-76.5...
28    Annotation(-76.7...
29    Annotation(-76.6...
30    Annotation(-76.5...
31    Annotation(-76.5...
32    Annotation(-76.5...
33    Annotation(-76.6...
34    Annotation(-76.6...
35    Annotation(-76.5...
36    Annotation(-76.6...
37    Annotation(-76.6...
38    Annotation(-76.6...
39    Annotation(-76.5...
40    Annotation(-76.5...
41    Annotation(-76.5...
42    Annotation(-76.5...
43    Annotation(-76.6...
44    Annotation(-76.6...
45    Annotation(-76.6...
46    Annotation(-76.6...
47    Annotation(-76.6...
48    Annotation(-76.5...
49    Annotation(-76.6...
50    Annotation(-76.6...
51    Annotation(-76.6...
52    Annotation(-76.6...
53    Annotation(-76.6...
54    Annotation(-76.6...
dtype: object
(-76.72049409966101, -76.52058984362787, 39.18850298950285, 39.38074613803655)
Text(0.5, 1.0, 'Houshold Childhood Poverty 2017')
Text(0.1, 0.08, 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance')
INDEX 3
Col: hhchpov18
0     Annotation(-76.6...
1     Annotation(-76.7...
2     Annotation(-76.5...
3     Annotation(-76.5...
4     Annotation(-76.5...
5     Annotation(-76.5...
6     Annotation(-76.6...
7     Annotation(-76.6...
8     Annotation(-76.5...
9     Annotation(-76.5...
10    Annotation(-76.6...
11    Annotation(-76.7...
12    Annotation(-76.6...
13    Annotation(-76.6...
14    Annotation(-76.6...
15    Annotation(-76.5...
16    Annotation(-76.6...
17    Annotation(-76.6...
18    Annotation(-76.6...
19    Annotation(-76.6...
20    Annotation(-76.6...
21    Annotation(-76.6...
22    Annotation(-76.6...
23    Annotation(-76.6...
24    Annotation(-76.5...
25    Annotation(-76.5...
26    Annotation(-76.5...
27    Annotation(-76.5...
28    Annotation(-76.7...
29    Annotation(-76.6...
30    Annotation(-76.5...
31    Annotation(-76.5...
32    Annotation(-76.5...
33    Annotation(-76.6...
34    Annotation(-76.6...
35    Annotation(-76.5...
36    Annotation(-76.6...
37    Annotation(-76.6...
38    Annotation(-76.6...
39    Annotation(-76.5...
40    Annotation(-76.5...
41    Annotation(-76.5...
42    Annotation(-76.5...
43    Annotation(-76.6...
44    Annotation(-76.6...
45    Annotation(-76.6...
46    Annotation(-76.6...
47    Annotation(-76.6...
48    Annotation(-76.5...
49    Annotation(-76.6...
50    Annotation(-76.6...
51    Annotation(-76.6...
52    Annotation(-76.6...
53    Annotation(-76.6...
54    Annotation(-76.6...
dtype: object
(-76.72049409966101, -76.52058984362787, 39.18850298950285, 39.38074613803655)
Text(0.5, 1.0, 'Houshold Childhood Poverty 2018')
Text(0.1, 0.08, 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance')
INDEX 4
Col: hhchpov19
0     Annotation(-76.6...
1     Annotation(-76.7...
2     Annotation(-76.5...
3     Annotation(-76.5...
4     Annotation(-76.5...
5     Annotation(-76.5...
6     Annotation(-76.6...
7     Annotation(-76.6...
8     Annotation(-76.5...
9     Annotation(-76.5...
10    Annotation(-76.6...
11    Annotation(-76.7...
12    Annotation(-76.6...
13    Annotation(-76.6...
14    Annotation(-76.6...
15    Annotation(-76.5...
16    Annotation(-76.6...
17    Annotation(-76.6...
18    Annotation(-76.6...
19    Annotation(-76.6...
20    Annotation(-76.6...
21    Annotation(-76.6...
22    Annotation(-76.6...
23    Annotation(-76.6...
24    Annotation(-76.5...
25    Annotation(-76.5...
26    Annotation(-76.5...
27    Annotation(-76.5...
28    Annotation(-76.7...
29    Annotation(-76.6...
30    Annotation(-76.5...
31    Annotation(-76.5...
32    Annotation(-76.5...
33    Annotation(-76.6...
34    Annotation(-76.6...
35    Annotation(-76.5...
36    Annotation(-76.6...
37    Annotation(-76.6...
38    Annotation(-76.6...
39    Annotation(-76.5...
40    Annotation(-76.5...
41    Annotation(-76.5...
42    Annotation(-76.5...
43    Annotation(-76.6...
44    Annotation(-76.6...
45    Annotation(-76.6...
46    Annotation(-76.6...
47    Annotation(-76.6...
48    Annotation(-76.5...
49    Annotation(-76.6...
50    Annotation(-76.6...
51    Annotation(-76.6...
52    Annotation(-76.6...
53    Annotation(-76.6...
54    Annotation(-76.6...
dtype: object
(-76.72049409966101, -76.52058984362787, 39.18850298950285, 39.38074613803655)
Text(0.5, 1.0, 'Houshold Childhood Poverty 2019')
Text(0.1, 0.08, 'Source: Maryland Vital Statistics; Analysis by: Baltimore Neighborhood Indicators Alliance')

Lets make a GIF

Final Result

getMinMax[source]

getMinMax(df)

getAbsMinMax[source]

getAbsMinMax(df)

createGif[source]

createGif(fileNames, saveGifAs, images)

createPicture[source]

createPicture(df, col, vmin, vmax, labelBounds, title, annotation, fontsize)

createGifMap[source]

createGifMap(df, saveGifAs, labelBounds, title, annotation, fontsize)

# Change these values in the cell below using different geographic reference codes will change those parameters

# Group By Crosswalked column. Included automatically in final result
# Do Not Group, Include the Crosswalked Column in the final result 
# Create the trav45 Indicator 

state = '24'
county = '510'
tract = '*'
# Specify the download parameters the acs download function will receieve here
year = '19'
years = ['17', '16', '15']
tableId = 'B08303' 
saveAcs = True

# Crosswalk Table 
cwUrl = 'CSA-to-Tract-2010.csv'
cw_left_col = 'tract'
cw_right_col= 'TRACTCE10' 
merge_how= 'CSA2010'
saveCrosswalked = True
crosswalkedFileName = False

groupBy = False # 'CSA2010'
aggMethod = 'sum'
columnsToInclude = ['CSA2010']

finalFileName = './trav45_20'+year+'_tracts_26July2019.csv' 
# Alternatively - groupBy = False & columnsToInclude = ['CSA2010']


# This lower half is to merge to the geom
from dataplay import merge 

# Secondary Table 
right_ds = 'https://services1.arcgis.com/mVFRs7NF4iFitgbY/ArcGIS/rest/services/Hhchpov/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=true&f=pgeojson'
right_col ='CSA2010'

interactive = True
merge_how = 'outer'
saveGifAs = './test.gif'
labelBounds = False # 'CSA2010'
annotation = 'Source: Baltimore Neighborhood Indicators Alliance' 
title = 'Indicator Name' 
fontsize='22'
td = finaldf.filter(regex="final|CSA2010|tract|geometry")
td = td.reindex(sorted(td.columns), axis=1)