Introduction
I couple of weeks ago I had an idea for a website where people can collaborate to create the first real Audio Atlas, using the power of the Google Maps API.
The problem was that I do some programming in R but I did know very few things about HTML and javascript.
However, I knew that having a project was a good way to get serious about learning a bit of these two languages.
So I started reading some books about how to use the Google API, I borrowed some code from other, more experienced, programmers
(whom I thank very very much!!!) and in the end I created a website called Audioramio.com.
Here you can come, record your voice and add it to the map. If everyone helps a tiny bit we can try and build the first Audio Atlas!!
While I was writing the code to build this site I realize that it would be very cool to be able to interface the Google maps API with R.
I thought about it because in the past it happened that I created some map of soil properties where on the same location I had multiple data to show.
The classic example is that you perform kriging and you end up with the actual estimation, plus the uncertainty.
Normally, what you do is showing two maps or if you are familiar with webGIS you can produce an interactive website where the user can select which of the two maps to show.
However, it would be good to have a way to perform some kind of analysis on these data “on the fly”.
So I start thinking at a way to do exactly that, to create a website where on one end you have the map, while on the other it shows some plot or at least a summary of the data.
I start looking into it and I found out that the team at RStudio had created a magnificent tool, called Shiny,
which is able to create an interactive webpage in HTML and javascript where the user can change interactively some parameters and the page react accordingly, changing the plot or the summary.
This is just one example: spark.rstudio.com/uafsnap/RV_distributions
To create a Shiny app you simply create two .R scripts, once for the user interface and one for the server side where you specify the analysis you want to perform and the variable the user can tweak.
For more info you can read the tutorial or take a look at this blog, by Matt Leonawicz.
The interesting thing is that Shiny is also able to work with the server.r script, plus a user interface completely created in HTML5 and javascript.
So potentially Shiny can be used for almost everything, and specifically it can be used to plot a Google map on the side of a Histogram, for example.
The problem with this idea is that it is difficult to let the two application share information.
Shiny has been built around an interactive console concept, meaning that the HTML pages are created for the user to interact with R inputs and generate dynamic outputs.
However, with the Google Maps API, as far as I know, it is not possible to extract data from the markers on the map.
You can add an infowindow with lots of information, but it is impossible to export these data to other applications.
I solved this by working around it. I created a .json file with an array of all the coordinates of the points.
Then in javascript I created a simple loop that generates, from the coordinates, a series of markers.
Instead of attaching an infowindow to them, I created a function that for each elements in the loop, when the point is clicked it updates a certain text form in the Shiny interface.
It seems complicated, but it is actually very basic stuff. I attached an image of the console, so that I can better explain.
As you can see, the Shiny console is very basic. There is a single text input and a single plot output.
The text input is connected to a subset call, and the input number is the row of the data file to be subseted. So when I click on the point, its ID goes to update the ID field (in the Shiny interface), and the focus goes on that.
Now, I need to click “enter” 2 times and the plot is updated.
Code Description
Now let’s take a closer look at the code, starting from the interface.
In Red you have the bits of code related to the Google maps API, while in Green you have the bits related to Shiny.
<!DOCTYPE html>
<html>
<head>
<title>Interfacing R and Google maps</title>
<meta charset=utf-8">
<script src="http://code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/slider/css/jquery.slider.min.css"/>
<script src="shared/slider/js/jquery.slider.min.js"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?&sensor=false&language=en">
</script>
<script type="text/javascript" src="http://www.fabioveronesi.net/ShinyApp/Points2.json"></script>
<script type="text/javascript">
var cluster = null;
function SetValue(i) {
document.getElementById("row").value = i;
document.getElementById("row").focus();
}
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(51.781436,-1.03363),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
var Layer0 = new google.maps.KmlLayer("http://www.fabioveronesi.net/ShinyApp/layer0.kml");
var Layer1 = new google.maps.KmlLayer("http://www.fabioveronesi.net/ShinyApp/layer1.kml");
document.getElementById('lay0').onclick = function() {
Layer0.setMap(map);
};
document.getElementById('lay1').onclick = function() {
Layer1.setMap(map);
};
var Gmarkers = [];
var infowindow = new google.maps.InfoWindow();
for (var i = 0; i < Points.length; i++) {
var lat = Points[i][1]
var lng = Points[i][0]
var marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
title: i.toString(),
icon: 'http://www.fabioveronesi.net/ShinyApp/icon.png',
map: map
});
google.maps.event.addListener(marker, 'click',
(function(i) {
return function() {
SetValue(i+1);
}
})(i));
Gmarkers.push(marker);
};
document.getElementById('clear').onclick = function() {
Layer1.setMap(null);
Layer0.setMap(null);
};
};
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<h1>Interfacing R with Google maps</h1>
<label for="row">ID:</label>
<input name="row" id="row" type="number" value="1"/>
<button type="button" id="lay1">Add Mean</button>
<button type="button" id="lay0">Add SD</button>
<button type="button" id="clear">Clear Map</button>
<div id="plot" class="shiny-plot-output"
style="position:absolute;top:20%;right:2%;width: 40%; height: 40%"></div>
<div id="map-canvas" style="position:absolute;top:20%;left:2%;width: 50% ; height: 50%"></div>
</body>
</html>
As you can see I have two Shiny elements: the plot of the right side and and ID text input on the top left. These are the elements that interact directly with R. To these, I simply added a map frame plus three buttons to show my data and clear the frame, if needed. The communication between Shiny and the API is done purely by these lines:
google.maps.event.addListener(marker, 'click', (function(i) { return function() { SetValue(i+1); } })(i));
What this says is that when I click on any marker the custom function SetValue will kick in. This function simply change the value in the ID text field to the one of i, which is the element of the loop and the ID of the marker.
Now, let's see what this ID text field controls. Here is the server.R code:
library(shiny)
data_file<-read.table("http://www.fabioveronesi.net/ShinyApp/point_grid.csv",sep=";",header=T)
shinyServer(function(input,output){
output$plot <- renderPlot({
sub<-data_file[input$row,]
data<-rnorm(10000,mean=sub$Wind_Media,sd=sub$StDev_smoo)
hist(data)
})
})
The R code is extremely simple. I have one input, ID, which is used to subset the data_file data.frame and one output, which is the histogram plot.
Problems with this approach
Now let’s talk about the problems with this approach.
The first is that the plot does not update automatically, the user needs to click “enter” 2 times before this to happen.
However, this may not be a problem after all, simply because as soon as the R script becomes more complex, and its execution time longer, it is good to have a way to avoid updating the plot all the time I accidentally click a point on the map.
The second problem is related to the Google Maps API and the way it shares data. In general, when I plot markers on the map the only way to access their data is clicking on them and look at the infowindow.
This is not true for KMZ map layers. When I plot a raster layer on the map it is treated exactly as an image (it is in fact a .png file georeferenced inWGS84), and it is therefore impossible to access its data.
The only way to plot a map and give the user a way to access it is by plotting a marker layer on top of it, with invisible icons so that they do not disturb the visualization of the map.
When the user clicks of the map, he is clicking on the invisible markers layer that triggers the Id field update. However, if I increase the zoom level the markers get smaller and therefore it becomes more difficult to click on them.
So accessing the map is possible only at the zoom level at which it is presented. A way to solve this would be showing (by using a different icon, maybe a point) the markers, but when they are on a grid their visual impact is not very pretty at all.
Conclusions
I think this approach has the potential to be used for very cool mapping experiments.
It relies directly on all the packages available in R and therefore it can virtually visualize every sort of statistics from the map.
However, it is a bit difficult to set up, because you need to transform all of your data into KML, extract the coordinates of your cells into WGS84, and transform them into a .json array.
For these steps I used ArcGIS and Notepad++. They are not difficult to complete, but they may take half of your working day or more, depending on your dataset.
A possible, quicker, alternative is http://www.jstat.org/. However, I never used it and so I do not know how to set it up for working with the Google Maps API.
In addition, I do not think it has the same potential as R for performing mind blowing statistics.
Download
To run the App on your PC just open R, install the package shiny and run these two lines:library(shiny) runUrl("http://www.fabioveronesi.net/ShinyApp/InterfacingRGoogleMaps.zip")
Thanks for this experimentation, this is really interesting :)
ReplyDeleteFor having runUrl() working, you just have to get your www folder and server.R file in a subdirectory inside your archive :
So, with a InterfacingR_GoogleMaps.zip file, containing a shinyapp folder, which contains server.R and www/, it will work and be easier to use :)
Done!!
ReplyDeleteI also modified the post.
Thank you very much!!
Nice post. You can auto-update the histogram on clicking by writing some additional code, as in the gist here, which you can run using runGist("https://gist.github.com/ramnathv/6144287"). The README should explain how it works.
ReplyDeleteNice post, thanks!!
ReplyDeleteI think I will wrap it up into a "submit button" because if the R script becomes more complex and takes longer to execute, I do not want the user to trigger it by accident.
Does this method works only on a "future version of Shiny"??
Because I tried it and it says the function observeEvent does not exist.
Done!!!!! But Its not working for kriging the markers plotted on the google map
ReplyDeleteThanks for sharing! It's a very interesting and neat app!
ReplyDelete