Javascript

Interactive zip areas map with Google Maps


Hey there, this time I'm going to show you how to build an interactive zip code areas map, using the Google Maps API and a simple Rails application. I want to highlight that Rails is not a requirement, I'm just using it to automatically generate Javascript objects from a YAML file. So I believe you should be able to do it using any framework or web language.

You can check what I'm talking about in this demo.

Setting up the project

I'm assuming that you already have a Rails application, so I won't start explaining from the very beginning. If you have any trouble, please leave a comment and I'll be happy to help you.

The first step to start working with the Google Maps API is to add the following line in your layout app/views/layouts/application.html.haml:

= javascript_include_tag "https://maps.googleapis.com/maps/api/js?key=[YOUR_KEY]8&sensor=true"

It is important to add that line above the following line:

= javascript_include_tag "application", "data-turbolinks-track" => true

If you are using ERB templates, you should change the syntax. Not a big deal.

Getting the zip areas boundaries

The next step is to generate a YAML file which will contain the boundaries for each zip code area. As you may notice, I'm using New York's zip codes as an example. The format I'm using on the YAML file is the following:

-
  name: '10001'
  boundaries:
    -
      longitude: '-73.987733'
      latitude: '40.744149'
    -
      longitude: '-73.983973'
      latitude: '40.748934'

I got the data from this Geocommons document. The precision increases with the number of boundaries.

Generating polygon objects from the YAML file

In order to show the zip areas on the map, we will be using polygons, which are objects from the Google Maps API used to draw areas. Here is the code which generates an array of ZipCodes and its polygons, then I will draw those polygons on the map elsewhere. I called this file zip_area_polygons.js.coffee.erb:

window.ZipAreasMap ||= {}

# Array of ZipCodes
ZipAreasMap.zipCodes = []

# Opening the YAML file.
<% zip_codes = YAML.load_file('config/ny_zip_areas.yml') %>

# Iterating over each Zip Code.
<% zip_codes.each do |zp| %>

# Defining the color of the Polygon
color = RandomColor.generate()

ZipAreasMap.zipCodes.push(
  # The boundaries for a Polygon
  boundaries: [
<% zp['boundaries'].each do |b| %>
    new google.maps.LatLng(<%= b['latitude'] %>, <%= b['longitude'] %>),
<% end %>
  ]

  # This is the Polygon corresponding to each ZipCode.
  polygon: ->
    new google.maps.Polygon(
      paths: @boundaries
      strokeColor: color
      strokeOpacity: 0.8
      strokeWeight: 2
      fillColor: color
      fillOpacity: @isCurrent('<%= zp["name"] %>', 'opacity')
      zipCode: '<%= zp["name"] %>'
      active: @isCurrent('<%= zp["name"] %>', 'active')
    )

  # This is for handling the status of the Polygon
  # Filled if it is active
  # Empty if it is not active
  isCurrent: (zipCode, property) ->
    isCurrent = ZipAreasMap.selectedZipCodes.filter (zip) ->
      zip is zipCode

    return 0.5 if isCurrent.length and property is 'opacity'
    return 0 if property is 'opacity'
    return true if isCurrent.length and property is 'active'
    return false
)
<% end %>

Random color generator

Doing a bit of research online, I found this method to generate random colors, check it out, I used it on the random_color.js.coffee file:

window.RandomColor ||= {}

RandomColor.generate = ->
  '#' + ('000000' + Math.floor(Math.random()*16777215).toString(16)).slice(-6)

The markup

Working with Google Maps is very easy, for HTML markup we just need the following:

.col-lg-8
  #zip-area-map-canvas
  %input#zip-codes{type: 'hidden', value: "#{@zip_codes}"}
.col-lg-4
  %p You have selected the following zip codes:
  %p#selected-zip-codes

I have a demo controller with an index action, so this markup is inside of app/views/demo/index.html.haml

Add that markup wherever you want the map to show, the important element is the empty div with id #zip-area-map-canvas.

One last important thing is to add the following CSS:

#zip-area-map-canvas {
  height: 500px;
}

We need to specify the height and width of our map. I have already specified the width of the map with the .col-lg-8 class from Twitter's Bootstrap.

The map initializer

Here is the code which will initialize the map with all the zip area polygons. The file is called map.js.coffee

ZipAreasMap.initialize = ->
  canvas = $('#zip-area-map-canvas').get(0)

  if canvas
    mapOptions =
      center: new google.maps.LatLng(40.75532,-73.983677)
      zoom: 12

    map = new google.maps.Map(canvas, mapOptions)

    ZipAreasMap.polygons = []

    ZipAreasMap.zipCodes.forEach (zipArea) ->
      ZipAreasMap.polygons.push zipArea.polygon()

    ZipAreasMap.setActiveZipCodes = ->
      activePolygons = ZipAreasMap.polygons.filter (polygon) ->
        polygon.active is true

      ZipAreasMap.selectedZipCodes = activePolygons.map (polygon) -> polygon.zipCode

      $('#selected-zip-codes').text(ZipAreasMap.selectedZipCodes.join(', '))

    ZipAreasMap.polygons.forEach (polygon) ->
      polygon.setMap map

      google.maps.event.addListener polygon, "click", ->
        fillOpacity = (if @fillOpacity is 0 then 0.5 else 0)
        @setOptions
          fillOpacity: fillOpacity
        this.active = !this.active
        ZipAreasMap.setActiveZipCodes()

Pre-selecting zip areas

We can pre-select zip areas from the map, I made it work just by adding the following code in the controller that renders the map's view:

class DemoController < ApplicationController
  def index
    @zip_codes = params[:zip_codes]
  end
end

Remember that I used that instance variable on the #zip-codes hidden input.

Finishing up

We are ready to see it working, you just need to add this code, I called the file demo.js.coffee:

window.ZipAreasMap ||= {}

$ ->
  # Splitting the zip codes from the hidden input.
  ZipAreasMap.selectedZipCodes = $('#zip-codes').val().split(',')

  # Initializing the Map.
  ZipAreasMap.initialize()

  $('#selected-zip-codes').text(ZipAreasMap.selectedZipCodes)

  # Change color button
  $('#change-color').click ->
    color = RandomColor.generate()
    ZipAreasMap.polygons.forEach (polygon) ->
      polygon.setOptions
        fillColor: color
        strokeColor: color

As you can notice, I am initializing the map when the document is ready!

I hope you could find this useful! Thanks for reading and see you next time!

Best Practices
De Código, Café y Cervezas 07 – ¿Somos profesionales?
Community
De Código, Café y Cervezas 06 – ActiveModel::Serializer
eCommerce
Adding Payment Method VTex Store