« January 2007 | Main | March 2007 »

Four mongrels, digg, and lifehacker

Feb 27 by Andre in Ruby on Rails » , Wifi Cafes »

Hotspotr had a record-breaking day yesterday, with a total of 61,000 pageviews according to Google Analytics. The peak hour was 5pm, shortly after it went onto the digg homepage, with just under 15,000 pageviews in one hour.

Since a frequently-expressed concern with Rails is it's ability to handle traffic, I'm happy to say the site survived without a hicup. If you are building a Rails app and wondering about scalability, here are some points of reference:

  • the backend is Apache 2.2 with mod_balancer going to a cluster of four mongrels.
  • the server is a Rimu hosting dedicated server, with a few other Rails apps running alongside it (none very highly trafficked yesterday though).
  • I don't have quantitative measurements on its responsiveness during the peak load times, but qualitatively it seemed pretty snappy -- I hit the site myself repeatedly during the peak time to see how it was holding up.
  • the hotspotr homepage has four database queries. The main map page (for any given city) has five queries.
  • I've done very little to optimize hotspotr. The basics are there: no iterative retrievals, appropriate indexes on tables, and sessions in the database.
  • beyond that, I haven't optimized much at all -- there's no caching of either ActiveRecord objects or HTML fragments.

You can see there is still quite a bit of room for improvement. The fact that the site survived a 15K hour certainly makes me more confident about deploying Rails in a high-traffic environment.

Thanks to Josh at Webware Rick at lifehacker for writing up hotspotr and sparking all the traffic yesterday!

Win a Copy of Google Maps Applications with Rails and Ajax

Feb 27 by Andre in Google Maps » , Speaking / writing »

My book, Google Maps Applications with Rails and Ajax, hit the streets yesterday. I've had a lot of questions about its availability in eBook / PDF format. Apress has assured me that the PDF will be available soon.

We are having a contest to win a hard-copy version of the book. See here to enter!

jQuery and yui-ext

Feb 19 by Andre in jQuery »

As a self-professed JavaScript geek, I love jQuery. The library just keeps getting better, and the community that John Resig has built is incredible. John's announcement today -- that jQuery is teaming up with yui-ext -- is great news. If you haven't seen Jack Slocum's yui-ext library, go check out the demos for some inspiring examples.

GeoKit: all about the :origin argument

Feb 15 by Andre in GeoKit » , Ruby on Rails »

The whole point of the acts_as_mappable mixin is to let you find things by location:

class Store < ActiveRecord::Base
    acts_as_mappable
    ...
end

...

Store.find(:all, :origin=>[37.792,-122.393])

But, what all can be supplied in the :origin argument? There are three possibilities.

1. Pass in a two-element array with latitudes/longitude values

Store.find(:all, :origin=>[37.792,-122.393])

Whenever :origin is an array, the code assumes in an array of two numbers representing latitude and longitude. If you pass in an array of anything else, you'll get an error.

2. Pass in a geocodeable string

Store.find(:all, :origin=>'100 Spear st, San Francisco, CA')

Again, the detection is based on the type of thing passed in under origin. So, if you expect GeoKit to geocode it, it better be in the format of a string. Zip codes are fine, but it better be a string. So, this will work:

Store.find(:all, :origin=>'94111') # it's a string, so it gets geocoded

But this will not:

# WON'T WORK -- it's not a string, so it doesn't get geocoded
Store.find(:all, :origin=>94111)

3. pass an object

Specifically, an object that has lat and lng methods, or latitude and longitude methods, or whatever methods you have specified for lng_column_name and lat_column_name.

Store.find(:all, :origin=>an_object_with_the_right_methods)

So, what sort of objects have lat and lng methods? Well, our own GeoKit::LatLng objects for starters. So, this will certainly work:

ll=LatLng.new(37.792,-122.393])
Store.find(:all, :origin=>ll)

GeoKit::GeoLoc also has lat and lng methods, so it will work too. By the way, GeoLoc is what gets returned when you make a geocoding call:

loc = GeoKit::Geocoders.GoogleGeocoder.geocode('94117')
Store.find(:all, :origin=>loc)

So far so good. What else has the appropriate methods? Well, anything that mixes in actsasmappable needs to have some sort of latitude/longitude methods. Say you already have an instance of Store -- you can pass the instance in as the origin:

starbucks=Store.find_by_name('Starbucks')
Store.find(:all, :origin=>starbucks)

In Summary . . .

There are three things you can pass into the :origin argument: 1) a two-element array containing latitude and longitude; 2) a geocodable string; 3) an object which will respond to lat/lng or latitude/longitude calls. Typically, you'll find yourself passing in LatLng or GeoLoc objects, or possibly other instances of whatever model you've mixed ActsAsMappable into.

"The Business of Rails" panel at RailsConf

Feb 12 by Andre in Entrepreneurial » , Ruby on Rails » , Speaking / writing »

I'm pleased to be a part of the "Business of Rails" panel at RailsConf this year. I'll be speaking alongside some major players in Rails consulting -- Justin Gehtland, Geoffrey Grosenbach, Joe O'Brien, Robby Russell, and Nathaniel Talbott. The panel is meant to answer your questions about the challenges, rewards, and dynamics of consulting with Rails.

As a one-man shop, I represent the small end of spectrum. I hope my contribution will be useful to others who are contemplating taking a similar approach, i.e, splitting your time between paid work and your own speculative projects. And, importantly, making a living doing what you enjoy with people you enjoy working with.

Again, I'm pleased to be on a panel with an esteemed group like this, and thanks to Nathaniel for pulling it together!

In-memory and in-database distance calculations

Feb 11 by Andre in GeoKit » , Ruby on Rails »

Following up on our recent release of GeoKit, Bill and I are working on some posts highlighting some of GeoKit's cool features. This post is on distance calculations. GeoKit can calculate distance in memory, and it can calculate distance as part of a database query.

In-memory distance calculation

The basic syntax for in-memory distance calculations is: d = first_loc.distance_to(second_loc)

What class is first_loc and second_loc? They should be either LatLng (Geokit::LatLng), or GeoLoc. If you open up the mappable.rb file, you're see that GeoLoc inherits from LatLng. If you're looking for the distance_to method, you'll find it in the Mappable module (the code for which is also in mappable.rb). The Mappable module is mixed in to LatLng, and imbues it with the distance_to method.

Distance in miles and kilometers

By default, distance_to yields a result in miles. If you want a distance in kilometers instead, just pass the optional units argument:

>>d = first_loc.distance_to(second_loc, :units=>:kilos)

A full example

Here we are just instantiating two LatLng objects using raw latitudes and longitudes (which correspond to San Francisco, CA, and Irving, TX).
>ruby script/console
Loading development environment. >> include GeoKit >> first_loc=LatLng.new(37.775,-122.418) >> second_loc=LatLng.new(32.813,-96.948) >> first_loc.distance_to(second_loc) => 1473.29189341352

. . . and with geocoding

Here we are starting with just the addresses, geocoding them through Google's geocoding service, and then calculating the distance. Calls to any of the Geocoders in GeoKit return the GeoLoc object. Recall that GeoLoc inherits from LatLng though, so it has all of LatLngs methods, including distance_to.

>>ruby script/console Loading development environment. 
>> include GeoKit::Geocoders 
>> sf=GoogleGeocoder.geocode('San Francisco,CA') 
>> puts sf.ll 
37.775,-122.418333 
>> irving=GoogleGeocoder.geocode('Irving,TX') 
>> puts irving.ll 
32.813889,-96.948611 
>> sf.distance_to(irving) 
=> 1473.25508451219 
>> sf.distance_to(irving, :units=>:kilometers)
=> 2370.46743098012

Flat vs. Spherical distances

If it turns out the earth is actually flat, GeoLoc still has us covered:

sf.distance_to(irving, :formula=>:flat)
=> 1206.13692230874

In case you're wondering, the spherical formula uses the Haversine method, and flat formula just uses the pythagorian therorum. You can find both implementations in the Mappable module in mappable.rb.

So there you have in-memory distance calculations provided by Geokit. Next, let's look at distance calculations in your database through ActiveRecord finder extensions provided by ActsAsMappable.

Database Distance Calculations

I have a model named Shop, which mixes in the ActsAsMappable module:

class Shop < ActiveRecord::Base 
acts_as_mappable 
... end 
Let's find the 10 closest shops to the center of the '94117' zipcode:
>> loc=GoogleGeocoder.geocode('94117')
>> puts loc.full_address
"San Francisco, CA 94117, USA" >> shops=Shop.find :all, :origin=>loc, :order=>'distance asc', :limit=>10

Now, each of the shops that has a bonus attribute, 'distance'. This represents the distance from the origin provided in the query.

>> puts shops[0].distance
0.22292201411219
>> puts shops[9].distance
0.61426029124168
>> shops.each {|shop| puts "#{shop.name} -- #{shop.distance} miles from #{loc.full_address}"}
Coffee to the People -- 0.22292201411219 miles from San Francisco, CA 94117, USA
Central Coffee, Tea & Spice -- 0.24258501247455 miles from San Francisco, CA 94117, USA
Bean Bag Cafe -- 0.33106086315379 miles from San Francisco, CA 94117, USA
Cafe Abir -- 0.42131954288221 miles from San Francisco, CA 94117, USA
Cole Valley Cafe -- 0.53454381452665 miles from San Francisco, CA 94117, USA
Jumping Java -- 0.5419605766772 miles from San Francisco, CA 94117, USA
Cafe Reverie -- 0.59076205971555 miles from San Francisco, CA 94117, USA
Cafe Du Soleil -- 0.60029433169774 miles from San Francisco, CA 94117, USA
Tully's (Cole Valley) -- 0.61426029124168 miles from San Francisco, CA 94117, USA

Since distance is being calculated in the database, you can use it in the conditions like any other column:

>> shops=Shop.find :all, :origin=>loc, :order=>'distance asc', 
                         :conditions=>'distance>20 AND distance <25'

Database and memory distance calculations together

Since my Shop class mixes in the Mappable module (through the acts_as_mappable call), I can also calculate its distance from any arbitrary location:

shops=Shop.find :all, :origin=>loc, :order=>'distance asc',
                         :conditions=>'distance>20 AND distance <25'
>> other_loc=GoogleGeocoder.geocode('100 Spear St, San Francisco, CA')
>> puts shops[0].distance_to(other_loc) # distance to an arbitrary location
=> 19.6377479942219
>> puts shops[0].distance # distance to the origin given in the query
22.581680094216

In case you're wondering, the math happening behind the scenes is essentially the same as outlined in sorting your queries by geographic distance -- they math is now nicely hidden, thanks to Bill Eisenhaurer's work in abstracting it away.

Summary

GeoKit supports both in-memory and in-database distance calculations. You can use units of miles or kilometers, and a flat or spherical formula.

Coming up next, I’m going to talk more about the find’s :origin argument.

Bill is also putting up some more GeoKit documentation and examples -- check out his live example of IP-based geocoding. It’s very cool stuff which allows you to determine roughly where your visitors are based on their IP address. GeoKit makes it easy for any Rails controller to employ the functionality.

GeoKit: a plugin for location-based Rails apps

Feb 09 by Andre in GeoKit » , Ruby on Rails »

I am happy to release GeoKit over at RubyForge. GeoKit has been a collaboration between myself and Bill Eisenhauer

What is GeoKit?

Geokit is a Rails plugin for building location-based apps. It provides geocoding, location finders, and distance calculation in one cohesive package. If you have any tables with latitude/longitude oolumns in your database, or if you every wanted to easily query for "all the stores within a 50 mile radius," then GeoKit is for you.

What can GeoKit do for you?

  • Distance calculations between two points on the earth. Calculate the distance in miles or KM, with all the trigonometry abstracted away by GeoKit.
  • ActiveRecord distance-based finders. For example, you can find all the points in your database within a 50-mile radius.
  • Geocoding from multiple providers. It currently supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response structure from all of them. It also provides a fail-over mechanism, in case your input fails to geocode in one service.
  • IP-based location lookup utilizing hostip.info. Provide an IP address, and get city name and latitude/longitude in return.
  • A before_filter helper to geocode the user's location based on IP address, and retain the location in a cookie.

Examples?

Find near latitude and longitude:

Store.find(:all, :origin =>  [37.792, -122.393] :conditions=>'distance<10')

Find near an address:

Store.find(:all, :origin=>'100 Spear st, San Francisco, CA', :conditions=>'distance<10')

Geocode an address:

res=GeoKit::Geocoders::GoogleGeocoder.geocode('100 Spear st, San Francisco, CA') 
puts res.lat 

Find distance:

distance=first_location.distance_from(second_location, :units=>:miles)

Where can you get it?

Head over to GeoKit at RubyForge for the plugin source and API docs.

Or, skip right to installing it as a plugin:

ruby script/plugin install svn://rubyforge.org/var/svn/geokit/trunk