Matthew Henderson

college station, tx

Mapping shapefile polygons

May 10, 2014

Static image of map showing warning polygons

Above is a static image captured of a map produced using code in the exercise below, which displays warning polygons. The image above also showed locations of tweets within that same thirty minute timeframe, which mentioned certain weather terms. The map produced in this exercise will only deal with displaying the warnings, and will be interactive.


The Goal

The goal of this exercise is to produce an embedded, interactive map on a web page, which will display the latest warnings issued by the National Weather Service. These warnings are updated by the NWS every minute, 24 hours a day, 365 days a year. You can read more about these files here.

There are two parts needed to produce the map. First, the web page itself. Second, we need a script which will download the latest files (issued in ESRI shapefile format), extract any existing polygons describing the areas currently under an issued warning, and produce a small javascript file which will then display them on a map.

For this example, we will be using Ruby, HTML and Javascript.

How it Works

Running the ruby script will:

  1. Download the latest warnings issued by the National Weather Service.
  2. Extract any warning polygons of the type you specify. There are four available: SVR (Severe Thunderstorm), TOR (Tornado), FFW (Flash Flood), and SMW (Special Marine Warnings).
  3. Create a file called polygons.js, which will be used to display them on a map embedded in a web page.

The web page will display a map – in this example centered on the continental US – and show any warnings that might exist at the time the ruby script was run to generate them.

In the end, an automated process could be set up so that the ruby script was executed every n minutes (ie. with launchd on a Mac), and the map would be periodically updated with the latest weather information.

Prerequisites

There are a few things you will need to have installed on your system before we can begin. The instructions for this are assuming a user on OS X.

First, you should install GEOS(Geometry Engine, Open Source). If you have brew installed on your system, this is a simple process:

brew install GEOS 

Next, you will want to install a few necessary gems if they are not already part your system. You should begin with rubyzip, which will allow us to work with our downloaded zip file.

gem install rubyzip

Next, you will want to install the two gems which will allow us to extract the polygon information we need for displaying on the web. You will want to specify where GEOS was installed. The following examples are for our installation with brew.

gem install rgeo -- --with-geos-dir=/usr/local/lib
gem install rgeo-shapefile -- --with-geos-dir=/usr/local/lib

Note: if your geos is installed in another location, you can check where by using:

sudo find / -name 'libgeos*'

Note: depending on your setup, you might be required to sudo when installing the gems.

The Code to Process Current Warnings

Now, below is the Ruby code for the script which will be used to build the polygon.js file to display any current warnings on our map.

require 'rubygems'
require 'open-uri'
require 'zip'
require 'rgeo'
require 'rgeo-shapefile'

def getcurrentwarnings(warningtype)
  tempfile = Tempfile.new(['tempfile','.zip'])
  tempfile.binmode
  tempfile.write(open("http://www.nws.noaa.gov/regsci/gis/shapefiles/current_warnings.zip").read)  
  tempfile.close
 
  Zip::File.open(tempfile.path) do |zip_file|
    zip_file.each do |entry|
      fname = entry.name
      if fname.include?(warningtype)
        ## passing true overwrites existing files
        ## if same named file already exists
        entry.extract("#{@targetshapefiles}#{fname}"){ true }
      end    
    end
  end
end

def readwarningpolygons(warningtype)
  RGeo::Shapefile::Reader.open("#{@targetshapefiles}#{warningtype}.shp") do |file|    
    if file.num_records.to_i > 0 
      puts "File contains #{file.num_records} records."
      file.each do |record|
        record.geometry.each do |polygon|          
          File.open(@targetploygonfile, 'a') {|f| f.write("polygon = L.polygon([\n") }      
          puts polygon.as_text
          polygon.exterior_ring.points.each do |point|
            line = "[#{point.y}, #{point.x}],\n"
            File.open(@targetploygonfile, 'a') {|f| f.write(line) }            
          end
          line = "],{ color: 'red', fillColor: '#05f', fillOpacity: 0.9 }).addTo(map);\n\n"
          File.open(@targetploygonfile, 'a') {|f| f.write(line) }                                 
        end
      end
      file.rewind
    end
  end
end

def initpolyfile()
  File.open(@targetploygonfile, 'w') {|f| f.write("function drawPolygons() { \n\t var polygon \n") }  
end

def finalizepolyfile()
  File.open(@targetploygonfile, 'a') {|f| f.write("\n\n}") }
end

## ***************************************
## EXECUTE *******************************
## ***************************************

## ********* SET THREE THINGS BELOW ***************
## Set the target locations for saving the files
@targetshapefiles = "/path/to/your/dir/"
@targetploygonfile = "/path/to/your/dir/polygons.js"

## Set the type of warnings you want to display:
## SVR (Severe Thunderstorm); TOR (Tornado); 
## FFW (Flash Flood); SMW (Special Marine Warnings)
warningtype = "SVR"
## ********* SET THREE THINGS ABOVE ***************

getcurrentwarnings(warningtype)
initpolyfile()
readwarningpolygons(warningtype)
finalizepolyfile()

Near the bottom of the code, you will find a section noted where there are three items you must set before you run it:

  1. @targetshapefiles - the location where you want to unzip the warning files
  2. @targetploygonfile - the location of your web page where the javascript file should be saved
  3. warningtype – choose the type of warnings you are interested in displaying

The Code to Display the Map

To display the map, we will be using tiles from Open Street Maps, and a Javascript library called leaflet.

Download leaflet from their website here: http://leafletjs.com/download.html

Here is the code for the HTML page. You may have to alter the references to resources as needed.

<!DOCTYPE html>
	<html lang="en">
	<head>
	<meta charset="utf-8"/>
	<title>Live Weather Warnings</title>
	<link rel="stylesheet" type="text/css" href="leaflet.css" />
	<script src="leaflet.js"></script>
	<script src="polygons.js"></script>

	<script>
	var map;
	function initmap() {
		//set up the map
		map = new L.Map('map');
		var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
		var osmAttrib='Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors';
		var osm = new L.TileLayer(osmUrl, {minZoom: 4, maxZoom: 22, attribution: osmAttrib});								
		map.setView(new L.LatLng(37.5,-95.35),5	); // center map over US
		map.addLayer(osm);

		drawPolygons();
	}
	</script>

	<style>
		body { background: #444;}
		#map { width:1000px; height:670px; margin: 20px auto;}
	</style>
	</head>

	<body onload="initmap();">
	    <div id="map"></div>
	</body>
	</html>

That’s it. You’re now ready to display your map. Enjoy!