<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Bryan Brattlof - Tips</title><link href="https://0x42.sh/" rel="alternate"/><link href="https://0x42.sh/feeds/tips.atom.xml" rel="self"/><id>https://0x42.sh/</id><updated>2020-10-21T00:00:00+00:00</updated><entry><title>Adding OpenStreetMaps To Matplotlib</title><link href="https://0x42.sh/adding-openstreetmaps-to-matplotlib/" rel="alternate"/><published>2020-10-21T00:00:00+00:00</published><updated>2020-10-21T00:00:00+00:00</updated><author><name>bryan brattlof</name></author><id>tag:0x42.sh,2020-10-21:/adding-openstreetmaps-to-matplotlib/</id><summary type="html">&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;It's Dead Jim:&lt;/p&gt;
&lt;p&gt;OpenStreetMap uses expensive hardware amazingly donated by community
members. Recently they've started to block requests that abuse this
hardware which, unfortunately, includes this script.&lt;/p&gt;
&lt;p class="last"&gt;As someone who enjoys hosting publicly accessible computers on the
internet, I completly understand this position and wish to honnor it.
Therefore these …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;It's Dead Jim:&lt;/p&gt;
&lt;p&gt;OpenStreetMap uses expensive hardware amazingly donated by community
members. Recently they've started to block requests that abuse this
hardware which, unfortunately, includes this script.&lt;/p&gt;
&lt;p class="last"&gt;As someone who enjoys hosting publicly accessible computers on the
internet, I completly understand this position and wish to honnor it.
Therefore these scripts will remain broken. You are welcome to fix
them but I encourage you to read the &lt;a class="reference external" href="https://operations.osmfoundation.org/policies/tiles/"&gt;tile server
usage policy&lt;/a&gt; first to
ensure you do not abuse this wonderful asset.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Adding a map to your visuals is a great way to quickly understand the geographic
information you're trying to investigate. Thankfully there are quite a few
packages and libraries (like &lt;a class="reference external" href="https://geopandas.org/"&gt;geopandas&lt;/a&gt;, &lt;a class="reference external" href="https://scitools.org.uk/cartopy/docs/latest/"&gt;cartopy&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/rossant/smopy"&gt;smopy&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/python-visualization/folium"&gt;folium&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/MatthewDaws/TileMapBase"&gt;tilemapbase&lt;/a&gt;, or &lt;a class="reference external" href="https://ipyleaflet.readthedocs.io/en/latest/"&gt;ipyleaflet&lt;/a&gt;) that can make creating these
visuals fairly straightforward and easy in your jupyter notebooks or whatever
stack you're using.&lt;/p&gt;
&lt;p&gt;For this essay though, I'll walk through the process of adding a base-map from
OpenStreetMap to you're matplotlib visuals without using any of these libraries.
In the end, we'll have a visual much like this (very messy) scatter-plot of
buses as they service route 16 in New Orleans.&lt;/p&gt;
&lt;img alt="A plot of the ~466,000 position reports for buses servicing route 16 as they work their way up and down South Claiborne Avenue, between South Carrollton Avenue and Harrah's near the French Quarter." src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/route-16-plot.png" /&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;Quick Note:&lt;/p&gt;
&lt;p&gt;All the code used to generate these visuals is available in a &lt;a class="reference external" href="https://git.sr.ht/~bryanb/norta"&gt;git repository
here&lt;/a&gt;. If you see any issues, have a
question or idea, please feel free to &lt;a class="reference external" href="https://0x42.sh/connect/"&gt;send me an email&lt;/a&gt;.&lt;/p&gt;
&lt;p class="last"&gt;However the data used here is too large for me to comfortably publish online
directly, so I've published the torrent file in the git repository that you
&lt;a class="reference external" href="https://git.sr.ht/~bryanb/norta/blob/canon/data/bus.log.tar.gz.torrent"&gt;can download here&lt;/a&gt;. If you don't like using BitTorrent
please feel
free to &lt;a class="reference external" href="https://0x42.sh/connect/"&gt;send me an email&lt;/a&gt; and I'll
do my best to send you a copy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-it-works"&gt;
&lt;h2&gt;How it Works&lt;/h2&gt;
&lt;p&gt;The ability to add our base-map to our &lt;a class="reference external" href="https://matplotlib.org/"&gt;matplotlib&lt;/a&gt;
visuals relies on matplotlib's &lt;a class="reference external" href="https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imshow.html"&gt;imshow() function&lt;/a&gt;,
which internally uses the &lt;a class="reference external" href="https://python-pillow.org/"&gt;Pillow library&lt;/a&gt; to
display images, or any other two dimensional scalar data we want (like &lt;a class="reference external" href="https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html"&gt;numpy
arrays&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For example, if we download &lt;a class="reference external" href="https://0x42.sh/pages/hi/profile.png"&gt;my self-portrait&lt;/a&gt;, we can add the image to a plot using code
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;matplotlib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/my/self-portrait.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Resulting in a matplotlib visual that looks like this:&lt;/p&gt;
&lt;img alt="A matplotlib plot of a self portrait (stick figure drawing) of Bryan Brattlof" src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/self-portrait-plot.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-the-map"&gt;
&lt;h2&gt;Creating the Map&lt;/h2&gt;
&lt;p&gt;The easiest way to generate the base-map for &lt;code&gt;plt.imshow()&lt;/code&gt; is to use a
mapping service. These mapping services use enormous amounts of raw computing
power to take the &lt;a class="reference external" href="https://wiki.openstreetmap.org/wiki/Planet.osm"&gt;terabytes of map data&lt;/a&gt; and render a map for us.
Today there are quite a few services available online. The one I enjoy working
with (and the one we will be using in this essay) is a free, community
maintained service called &lt;a class="reference external" href="https://www.openstreetmap.org/about"&gt;OpenStreetMap&lt;/a&gt;.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;Before We Begin:&lt;/p&gt;
&lt;p class="last"&gt;OpenStreetMap uses (expensive) hardware that was amazingly donated by
community members. Please &lt;strong&gt;do not abuse this asset&lt;/strong&gt; or you will have a
great many people angry with you. I encourage you to read the &lt;a class="reference external" href="https://operations.osmfoundation.org/policies/tiles/"&gt;tile server
usage policy&lt;/a&gt; to
avoid this happening.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tile-servers"&gt;
&lt;h2&gt;Tile Servers&lt;/h2&gt;
&lt;p&gt;To make updating and sharing their work easier, OpenStreetMap (and virtually all
other mapping services) have split their maps into billions of tiny (256 pixel)
sections, called tiles, that we can download individually from their tile servers.&lt;/p&gt;
&lt;p&gt;OpenStreetMap has &lt;a class="reference external" href="https://wiki.openstreetmap.org/wiki/Tile_servers"&gt;quite a few tile servers&lt;/a&gt; that style or prioritize
different map features with some having a slightly different
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/API"&gt;API&lt;/a&gt; to request tiles. For example the
&lt;a class="reference external" href="http://maps.stamen.com/toner/#12/29.9722/-90.1167"&gt;Stamen Toner Map&lt;/a&gt; that I
used in the first visual and prefer for it's simple color pallet.&lt;/p&gt;
&lt;p&gt;For this essay though, we'll use the default tile server's API to request tiles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;URL = &amp;quot;https://tile.openstreetmap.org/{z}/{x}/{y}.png&amp;quot;.format
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This string formatting function will replace the &lt;code&gt;{z}&lt;/code&gt;, &lt;code&gt;{x}&lt;/code&gt;, and
&lt;code&gt;{y}&lt;/code&gt; with the tile coordinates and zoom level of the tile we want to
download, where:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code&gt;{z}&lt;/code&gt; is the &amp;quot;zoom&amp;quot; level ranging from 0 to 18. Zoom 0 being the most
&amp;quot;zoomed out&amp;quot; and needs only one tile to depict &lt;a class="reference external" href="https://tile.openstreetmap.org/0/0/0.png"&gt;the entire world&lt;/a&gt; at that level.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{x}&lt;/code&gt; is the number of tiles from the left most tile of the map.&lt;/li&gt;
&lt;li&gt;and &lt;code&gt;{y}&lt;/code&gt; is the number of tiles from the top most tile of the map.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both &lt;code&gt;{x}&lt;/code&gt; and &lt;code&gt;{y}&lt;/code&gt; depend on the zoom level &lt;code&gt;{z}&lt;/code&gt; we've
chosen, with larger zoom levels requiring more tiles to render the map. To
understand how to calculate which tiles we need for our data-set, we'll need to
understand how mapping projections work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="map-projections"&gt;
&lt;h2&gt;Map Projections&lt;/h2&gt;
&lt;p&gt;Without going too deep into mapping projections, OpenStreetMap (along with many
other mapping services) needed a way to convert &lt;span class="formula"&gt;(&lt;i&gt;lat&lt;/i&gt;, &lt;i&gt;lon&lt;/i&gt;)&lt;/span&gt; coordinates
into planer &lt;span class="formula"&gt;(&lt;i&gt;x&lt;/i&gt;, &lt;i&gt;y&lt;/i&gt;)&lt;/span&gt; coordinates which work with their maps. Sadly there is
no perfect way to do this.&lt;/p&gt;
&lt;p&gt;Google (and everyone else eventually) settled on a variant of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Mercator_projection"&gt;Mercator
Projection&lt;/a&gt; called the
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Web_Mercator_projection"&gt;Web Mercator Projection&lt;/a&gt;
which simplifies the conversion by assuming the earth is a perfect sphere (it's
not). This can (and does) lead to confusion in the final visuals and why many
official bodies refuse to accept this standard.&lt;/p&gt;
&lt;p&gt;The advantage of assuming the earth is a perfect sphere is that the equation to
convert our GPS coordinates into Web Mercator coordinates is fairly
straightforward. The &lt;a class="reference external" href="https://wiki.openstreetmap.org/wiki/Main_Page"&gt;OpenStreetMap Wiki&lt;/a&gt; has the algorithm available
in &lt;a class="reference external" href="https://wiki.openstreetmap.org/wiki/Mercator"&gt;multiple programming languages&lt;/a&gt;. Here is the one for Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="n"&gt;TILE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;point_to_pixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;convert gps coordinates to web mercator&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;
    &lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;360.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="downloading-a-tile"&gt;
&lt;h2&gt;Downloading A Tile&lt;/h2&gt;
&lt;p&gt;Now we can use the &lt;code&gt;point_to_pixels()&lt;/code&gt; function to calculate the number
of pixels from the top-left corner of the OSM map from the GPS coordinates in
our data-set at any &lt;code&gt;zoom&lt;/code&gt; level, for example the French Quarter of
New Orleans:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point_to_pixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;90.064279&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;29.95863&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dividing the number of pixels by &lt;code&gt;TILE_SIZE&lt;/code&gt; will then give us the
&lt;code&gt;{x}&lt;/code&gt; and &lt;code&gt;{y}&lt;/code&gt; that we need for the &lt;code&gt;URL()&lt;/code&gt; function we
created &lt;a class="reference internal" href="#tile-servers"&gt;a few sections ago&lt;/a&gt; for the OpenStreetMap API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x_tiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_tiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That we can then use, along with the &lt;a class="reference external" href="https://requests.readthedocs.io/en/master/"&gt;requests&lt;/a&gt; and &lt;a class="reference external" href="https://python-pillow.org/"&gt;Pillow&lt;/a&gt; libraries, to download a tile from the
OpenStreetMap tile servers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PIL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# format the url&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x_tiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_tiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# make the request&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# just in case&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# plot the tile&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Producing a tile of Jackson Square in the French Quarter of New Orleans:&lt;/p&gt;
&lt;img alt="the tile" src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/french-quarter-plot.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="stitching-tiles-together"&gt;
&lt;h2&gt;Stitching Tiles Together&lt;/h2&gt;
&lt;p&gt;To download all the tiles needed for our visual, we'll need to calculate the
limits of the data we'll be using in our visual. There are many ways we can do
this, all of them are valid. For simplicity though, I'll calculate the
&lt;span class="formula"&gt;&lt;i&gt;min&lt;/i&gt;&lt;/span&gt; and &lt;span class="formula"&gt;&lt;i&gt;max&lt;/i&gt;&lt;/span&gt; of both the &lt;code&gt;lat&lt;/code&gt; and &lt;code&gt;lon&lt;/code&gt; columns in
my &lt;a class="reference external" href="https://pandas.pydata.org/"&gt;pandas&lt;/a&gt; DataFrame:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;lef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rgt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This gives us a bounding box (in GPS coordinates) that encompasses our entire
data-set.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;NOTE:&lt;/p&gt;
&lt;p class="last"&gt;This is a good time to adjust our &lt;code&gt;zoom&lt;/code&gt; level to download just enough
tiles. Please do not anger the community by &lt;a class="reference internal" href="#creating-the-map"&gt;downloading large collections of
tiles&lt;/a&gt; at one time.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next, just like we did in &lt;a class="reference internal" href="#downloading-a-tile"&gt;the last section&lt;/a&gt;, we'll use
the &lt;code&gt;point_to_pixels()&lt;/code&gt; function to convert our GPS coordinates into Web
Mercator coordinates.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point_to_pixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point_to_pixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That we can then divide by &lt;code&gt;TILE_SIZE&lt;/code&gt; to calculate the minimum and
maximum number of tiles we'll need to download for both the &lt;code&gt;{x}&lt;/code&gt; and
&lt;code&gt;{y}&lt;/code&gt; arguments for the API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x0_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0_tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x1_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1_tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;NOTE:&lt;/p&gt;
&lt;p class="last"&gt;We use &lt;code&gt;math.ceil()&lt;/code&gt; to round up, assuring small fractions of a tile
will still be downloaded.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As a precaution, we'll add an &lt;code&gt;assert&lt;/code&gt; statement to limit the number of
tiles we can download and save us from the embarrassment of accidentally
burdening OpenStreetMap tile servers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y1_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;That&amp;#39;s too many tiles!&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we've calculated which tiles we need to download from OpenStreetMap, we
can use the built-in &lt;a class="reference external" href="https://docs.python.org/3/library/itertools.html"&gt;itertools&lt;/a&gt; &lt;code&gt;product()&lt;/code&gt; function
to loop through every tile, downloading and saving the tiles to a single large
pillow image using &lt;a class="reference external" href="https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.paste"&gt;Pillow's paste() function&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;itertools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;

&lt;span class="c1"&gt;# full size image we&amp;#39;ll add tiles to&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RGB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y1_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# loop through every tile inside our bounded box&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_tile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x1_tile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y0_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1_tile&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# just in case&lt;/span&gt;
        &lt;span class="n"&gt;tile_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# add each tile to the full size image&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tile_img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_tile&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y0_tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Resulting in a plot like this:&lt;/p&gt;
&lt;img alt="A plot of New Orleans using the script we just developed to stitch multiple tiles together into one continuous map that we can place under our scatter plot in the next section." src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/the-basemap.png" /&gt;
&lt;p&gt;The eagle-eyed among us will notice the image is too large for the visual we
want to create. This is because of the &lt;code&gt;math.ceil()&lt;/code&gt; and &lt;code&gt;int()&lt;/code&gt;
functions we used to round the pixel coordinates into &lt;code&gt;{x}&lt;/code&gt; and
&lt;code&gt;{y}&lt;/code&gt; tiles we used above. To get our image back to size we'll need to
crop out the fractions of tiles not inside our bounding box.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cropping-the-basemap"&gt;
&lt;h2&gt;Cropping the Basemap&lt;/h2&gt;
&lt;p&gt;To help my human-eyed brethren, I added some lines to our previous graphic to
help understand what's going on. Essentially some fraction of each tile we've
downloaded (outlined in black) will be used in our final visual (outlined in
red) that we calculated in &lt;a class="reference internal" href="#stitching-tiles-together"&gt;the last section&lt;/a&gt;. Our
goal for this section is to trim the fraction of tiles outside of our red square.&lt;/p&gt;
&lt;img alt="A plot of New Orleans with black lines outlining each tile we downloaded from the tile servers overlaid with a red line representing the section of the map we wish to keep after we crop the image." src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/basemap-cropping-lines.png" /&gt;
&lt;p&gt;To curtail our oversize image, we'll use pillow's &lt;a class="reference external" href="https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.crop"&gt;Image.crop()&lt;/a&gt;
function, which takes a tuple &lt;code&gt;(left, top, right, bottom)&lt;/code&gt; measured in
pixels from the top left corner to crop our image.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#stitching-tiles-together"&gt;From our work above&lt;/a&gt;, we know the pixel
coordinates of the red square is defined as &lt;code&gt;x0, y0&lt;/code&gt; and &lt;code&gt;x1, y1&lt;/code&gt;.
We can then multiply the tile coordinates &lt;code&gt;x0_tile, y0_tile&lt;/code&gt; by
&lt;code&gt;TILE_SIZE&lt;/code&gt; to find the pixel coordinates for the top-left corner of the
current (oversize) basemap:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0_tile&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0_tile&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;TILE_SIZE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now it's just a simple process of subtracting the edges of our red square from
the pixel coordinates we just calculated for our oversize image to crop it to
our desired size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
    &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# left&lt;/span&gt;
    &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# top&lt;/span&gt;
    &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# right&lt;/span&gt;
    &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# bottom&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Resulting in our final (properly sized) basemap for our visual:&lt;/p&gt;
&lt;img alt="A, now properly sized, plot of New Orleans using the cropping script we just developed to resize our basemap to the proper size for our visual." src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/basemap-cropped.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="plotting-the-data"&gt;
&lt;h2&gt;Plotting The Data&lt;/h2&gt;
&lt;p&gt;Finally, with our basemap created, we can plot our data just like any other
visual with some key exceptions. We can start by setting a &lt;a class="reference external" href="https://matplotlib.org/3.3.1/api/_as_gen/matplotlib.pyplot.subplot.html"&gt;matplotlib
subplots()&lt;/a&gt;
and a &lt;a class="reference external" href="https://matplotlib.org/3.3.1/api/_as_gen/matplotlib.axes.Axes.scatter.html"&gt;scatter()&lt;/a&gt;
plot for the &lt;code&gt;lat&lt;/code&gt; and &lt;code&gt;lon&lt;/code&gt; columns in our pandas DataFrames:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplots&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;red&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we'll add an extra argument to the &lt;code&gt;imshow()&lt;/code&gt; function to properly
locate our image in the final visual. The &lt;code&gt;extent&lt;/code&gt; argument is used to
move a image to a &lt;a class="reference external" href="https://matplotlib.org/3.3.1/tutorials/intermediate/imshow_extent.html"&gt;particular region in dataspace&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rgt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we'll lock down the &lt;span class="formula"&gt;&lt;i&gt;x&lt;/i&gt;&lt;/span&gt; and &lt;span class="formula"&gt;&lt;i&gt;y&lt;/i&gt;&lt;/span&gt; axes to the limits we defined
&lt;a class="reference internal" href="#stitching-tiles-together"&gt;a few sections ago&lt;/a&gt; by using the
&lt;code&gt;set_ylim()&lt;/code&gt; and &lt;code&gt;set_xlim()&lt;/code&gt; functions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rgt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All of this work will produce a simple graphic with a (gorgeous) basemap of
buses servicing New Orleans' Route 16.&lt;/p&gt;
&lt;img alt="The final visual we've been working to depicting the roughly 400,000 position reports of buses as they service route 16 of New Orleans." src="https://0x42.sh/adding-openstreetmaps-to-matplotlib/final-visual.png" /&gt;
&lt;/div&gt;
</content><category term="Tips"/><category term="NORTA"/></entry><entry><title>Removing Git LFS From A Project</title><link href="https://0x42.sh/removing-git-lfs-from-a-project/" rel="alternate"/><published>2020-05-01T00:00:00+00:00</published><updated>2020-05-01T00:00:00+00:00</updated><author><name>bryan brattlof</name></author><id>tag:0x42.sh,2020-05-01:/removing-git-lfs-from-a-project/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://git-lfs.github.com/"&gt;Git Large File Storage (LFS)&lt;/a&gt; is an extension to
Git that separates large, frequently changing, binary files and saves them in a
separate storage location outside of the normal Git project. LFS replaces these
binary files with smaller text files, called pointers, that hold information
about the original file and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://git-lfs.github.com/"&gt;Git Large File Storage (LFS)&lt;/a&gt; is an extension to
Git that separates large, frequently changing, binary files and saves them in a
separate storage location outside of the normal Git project. LFS replaces these
binary files with smaller text files, called pointers, that hold information
about the original file and how to download them. These pointers look something
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;version https://git-lfs.github.com/spec/v1
oid sha256:d7cbc07ce9b78a89764c0ac5e9c8e1b9dbdeb42c30d8396cba8f75aace5ba225
size 145930
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Git-LFS uses &lt;a class="reference external" href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;git hooks&lt;/a&gt; to transparently replace and create these pointers
whenever we make a commit and saves the binary data outside of Git until the
next push where it will sync with your LFS server. When everything is working
correctly, we should never see these pointer files.&lt;/p&gt;
&lt;p&gt;When we say &lt;em&gt;&amp;quot;remove LFS from a project&amp;quot;&lt;/em&gt; we really mean removing the hooks
from our project, which, if not done correctly, will leave these pointer files
all throughout our project's history with no way to download the actual files
they pointed to, preventing us from ever building an older release of our
project again.&lt;/p&gt;
&lt;p&gt;To successfully uninstall LFS, we'll need to rewrite the project's history,
replacing all the LFS pointer files with the actual file they pointed to,
adding them back into our project's history. In the end, the project will look
as if LFS was never installed in the project at all.&lt;/p&gt;
&lt;div class="section" id="step-0-make-a-backup"&gt;
&lt;h2&gt;Step: 0 - Make A Backup&lt;/h2&gt;
&lt;p&gt;Before we begin, rewriting history is &lt;strong&gt;remarkably dangerous&lt;/strong&gt;, with many
opportunities to silently break something you'll only find out about after it's
too late. &lt;strong&gt;Make a backup before you start&lt;/strong&gt; and save it until you're absolutely
sure the project's history is correct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--mirror&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;backup&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;backup
git&lt;span class="w"&gt; &lt;/span&gt;lfs&lt;span class="w"&gt; &lt;/span&gt;fetch&lt;span class="w"&gt; &lt;/span&gt;--all
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Replacing &lt;code&gt;$URL&lt;/code&gt; with the location of the project, these two commands
will:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create a bare copy of the project and navigate into it.&lt;/li&gt;
&lt;li&gt;Download all the files in LFS from the remote LFS server.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This will give you a complete backup of the project, including branches and
other &lt;em&gt;refs&lt;/em&gt; like tags and notes, as well as download all the files from LFS,
ensuring any screw-ups with the next step are recoverable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-1-rewrite-history"&gt;
&lt;h2&gt;Step: 1 - Rewrite History&lt;/h2&gt;
&lt;p&gt;In the working copy of our project, &lt;code&gt;filter-branch&lt;/code&gt; can easily replace
all the LFS pointer files for us. However this step will also change all the
names of the commits &lt;a class="reference external" href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects"&gt;(git objects)&lt;/a&gt; in your project, so references to specific
commits like &lt;code&gt;Fixed in a4749f3&lt;/code&gt; will break. If you have a lot of these
types of commits you might want to use more advanced tools like &lt;a class="reference external" href="https://github.com/newren/git-filter-repo"&gt;filter-repo&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;filter-branch&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;--prune-empty&lt;span class="w"&gt; &lt;/span&gt;--tree-filter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;[ -f .gitattributes ] &amp;amp;&amp;amp;  git rm -f .gitattributes&lt;/span&gt;
&lt;span class="s1"&gt;find . -type f | while read FILE; do&lt;/span&gt;
&lt;span class="s1"&gt;   if head -2 &amp;quot;$FILE&amp;quot; | grep -q &amp;quot;^oid sha256:&amp;quot;; then&lt;/span&gt;
&lt;span class="s1"&gt;      POINTER=$(cat &amp;quot;$FILE&amp;quot;)&lt;/span&gt;
&lt;span class="s1"&gt;      echo -n &amp;quot;$POINTER&amp;quot; | git lfs smudge &amp;gt; &amp;quot;$FILE&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;      git add &amp;quot;$FILE&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;   fi&lt;/span&gt;
&lt;span class="s1"&gt;done&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--tag-name-filter&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--all
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This uses &lt;code&gt;git filter-branch --tree-filter&lt;/code&gt; to go through each commit for
every branch, and use &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt; to list every LFS pointer
file so we can use &lt;code&gt;git lfs smudge&lt;/code&gt; to replace the pointers with the LFS
data, before committing the changes and moving to the next commit in the history.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-2-remove-hooks-filters"&gt;
&lt;h2&gt;Step: 2 - Remove Hooks &amp;amp; Filters&lt;/h2&gt;
&lt;p&gt;If this is the only project that uses LFS, you can remove the &lt;code&gt;--local&lt;/code&gt;
option from the following command to also remove the filters from your global
Git config file &lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git lfs uninstall --local
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once the pointer files have been replaced, we can remove the hooks and filters
LFS used to read the pointer files every time we &lt;em&gt;fetched&lt;/em&gt;, &lt;em&gt;pushed&lt;/em&gt; or
&lt;em&gt;committed&lt;/em&gt; changes to the project, with this simple built-in LFS command.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-3-remove-the-lfs-cache"&gt;
&lt;h2&gt;Step: 3 - Remove the LFS cache&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rm -r .git/lfs
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, if this is your working copy of the project, you can save some disk space
by removing the LFS cache folder inside the git directory of your project.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Tips"/></entry></feed>