Create World Pixel Maps in R

Cross-posted: Medium

Today, I’m going to show you how to make pixel maps in R. Why pixel maps? Because they look awesome!

I was searching on the web for a while, but couldn’t find a good tutorial. Being stubborn as I am, I eventually figured out a way to get what I want. You know, if you torture your code enough, it might give you what you need.

Setup

First, of course, loading required packages. These days, I don’t bother with discrete packages and get the entire tidyverse right away. Aside from that, you may need the ggmap package, which I used in the earlier iterations of this script (more on that later). You’ll also need the maps package.

# Library -----------------------------------------------------------------

library(tidyverse)
library(googlesheets)
library(maps)
library(here)

Next, we’ll need our data points. You can do anything you want here: load a google sheet with data, reach to you Google Maps data, import a csv file, whatever your heart desires.

Initially, I created a data frame with places I’ve been to, and then grabbed their coordinates with mutate_geocode() function from a ggmap package. That piece of code takes a while to run, and the list doesn’t really change that much, so I ended up saving it as a separate Google sheet, and now I simply import it. But you do as you wish.

You’ll obviously need to replace this chunk with your own data. I include tail of my tibble to give you an idea about the data structure

# Get data -------------------------------------------------------------
url <- 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQoRxmeOvIAQSqOtr2DMOBW_P4idYKzRmVtT7lpqwoH7ZWAonRwOcKR2GqE-yqUOhb5Ac_RUs4MBICe/pub?output=csv'
destfile <- "locations.csv"
curl::curl_download(url, destfile)
locations <- read_csv(destfile)
tail(locations)
## # A tibble: 6 x 5
##   city                  status    lon   lat family
##   <chr>                 <chr>   <dbl> <dbl>  <int>
## 1 Vancouver, BC         been   -123.   49.3      3
## 2 Khmelnitskiy, Ukraine been     27.0  49.4      1
## 3 Lviv, Ukraine         been     24.0  49.8      2
## 4 Kyiv, Ukraine         lived    30.5  50.5      3
## 5 Warsaw, Poland        been     21.0  52.2      2
## 6 Moscow, Russia        been     37.6  55.8      1

Rounding the coordinates

As I’m creating a pixel map - I need dots in the right places. I’m going to plot a dot for each degree, and therefore I need my coordinates rounded to the nearest degree

locations <- locations %>% 
        mutate(long_round = round(lon, 0),
               lat_round = round(lat,0))
tail(locations)
## # A tibble: 6 x 7
##   city                  status    lon   lat family long_round lat_round
##   <chr>                 <chr>   <dbl> <dbl>  <int>      <dbl>     <dbl>
## 1 Vancouver, BC         been   -123.   49.3      3      -123.       49.
## 2 Khmelnitskiy, Ukraine been     27.0  49.4      1        27.       49.
## 3 Lviv, Ukraine         been     24.0  49.8      2        24.       50.
## 4 Kyiv, Ukraine         lived    30.5  50.5      3        31.       50.
## 5 Warsaw, Poland        been     21.0  52.2      2        21.       52.
## 6 Moscow, Russia        been     37.6  55.8      1        38.       56.

Generate a pixel grid

The next step is the key to getting a pixel map. We’ll fill the entire plot with a grid of dots - 180 dots from south to north, and 360 dots from east to west, but then only keep the dots that are on land. Simple!

# Generate a data frame with all dots -----------------------------------------------

lat <- data_frame(lat = seq(-90, 90, by = 1))
long <- data_frame(long = seq(-180, 180, by = 1))
dots <- lat %>% 
        merge(long, all = TRUE)

## Only include dots that are within borders. Also, exclude lakes.
dots <- dots %>% 
        mutate(country = map.where('world', long, lat),
               lakes = map.where('lakes', long, lat)) %>% 
        filter(!is.na(country) & is.na(lakes)) %>% 
        select(-lakes)

head(dots)
##   lat long                   country
## 1 -83 -173                Antarctica
## 2 -83 -172                Antarctica
## 3 -83 -171                Antarctica
## 4  60 -167 USA:Alaska:Nunivak Island
## 5  60 -166 USA:Alaska:Nunivak Island
## 6  65 -166                USA:Alaska

Plot

And now the easy part. Plotting.

Please note that this post, just like this entire site, runs on blogdown, and the post is created via Rmarkdown. When the plots render here - they look ugly-ish due to the fact that geom_point doesn’t scale down along with the plot. The output on your machine will look better. Take a look at the head image to understand how your output may look like

theme <- theme_void() +
        theme(panel.background = element_rect(fill="#212121"),
              plot.background = element_rect(fill="#212121"),
              plot.title=element_text(face="bold", colour="#3C3C3C",size=16),
              plot.subtitle=element_text(colour="#3C3C3C",size=12),
              plot.caption = element_text(colour="#3C3C3C",size=10),  
              plot.margin = unit(c(0, 0, 0, 0), "cm"))

plot <- ggplot() +   
        #base layer of map dots
        geom_point(data = dots, aes(x=long, y = lat), col = "grey45", size = 0.7) + 
        #plot all the places I've been to
        geom_point(data = locations, aes(x=long_round, y=lat_round), color="grey80", size=0.8) + 
        #plot all the places I lived in, using red
        geom_point(data = locations %>% filter(status == 'lived'), aes(x=long_round, y=lat_round), color="red", size=0.8) +
        #an extra layer of halo around the places I lived in
        geom_point(data = locations %>% filter(status == 'lived'), aes(x=long_round, y=lat_round), color="red", size=6, alpha = 0.4) +
        #adding my theme
        theme
plot

You probably want to save the map, too.

ggsave('map_full.jpg', 
       device = 'jpg', 
       path = getwd(), 
       width = 360, 
       height = 180, 
       units = 'mm',
       dpi = 250)

Looking at the map of the entire world can be overwhelming and sad, especially if you, just like me, are not much of a traveler. Look at it! There aren’t many dots! WTF?! Sad!

You can zoom in on an area you did cover (e.g. include USA only), either computationally (calculate you westernmost, easternmost, southernmost and northernmost points and pass them as xlim and ylim), or excluding continents from the map with dplyr (excluding Antarctica at least would be a good idea). You can also use a different map to start with - World map may not be necessary for some tasks. I used it because I was fortunate enough to live on 2 continents, but your mileage may vary.

In all of these cases, you may want to reconsider the grain of the map: if you zoom in on USA only, you may want to choose to plot a dot for every 0.5 degrees, and then would need to adjust your coordinate rounding respectively (round to the nearest half of degree). Why do it? The finer your grain - the more details you’ll get. For instance, with a grain of 1 degree, San Francisco, San Mateo, San Rafael and Oakland are all be one same dot.

I could definitely program my way though this scaling issue and create a parameter, and make other variables depend on it… I don’t find this exercise to be particularly useful in this case. If you get it done - awesome!

For my case, I wanted a wide banner, so I chose some specific arbitrary limits that looked good to me.

plot + scale_y_continuous(limits = c(10, 70), expand = c(0,0)) +
        scale_x_continuous(limits = c(-150,90), expand = c(0,0))

plot + scale_y_continuous(limits = c(10, 60), expand = c(0,0)) +
        scale_x_continuous(limits = c(-250,-40), expand = c(0,0))

ggsave('header2.png', 
       device = 'png', 
       path = "../../../static/img",
       width = 1710/6, 
       height = 570/6, 
       units = 'mm',
       dpi = 250)

Outro

Obviously, there is so much more to do with this. The possibilities are endless. The basic idea is pretty simple - generate a point grid and plot rounded coordinates on top of the grid.

Let me know if you find new implementations of this code!

Repo

As this blog is rendered with blogdown, all the source code is on Github for your pleasure. taraskaduk.com repo is at https://github.com/taraskaduk/taraskaduk/ and the Rmd file for this post should be located at https://github.com/taraskaduk/taraskaduk/tree/master/content/post (unless I mess it all up and relocate it. I’m really not good at your whole github and blogdown thing. But I’m learning)

Related

comments powered by Disqus