Google Maps Training in San Francisco
I am excited to announce that I am teaming up with Marakana to provide a Google Maps Training course this August in San Francisco. The three-day course will cover basic map creation, map/server interaction via Ajax, Geocoding, data scraping, optimizing for large data sets, and a variety of advanced tips and tricks. The focus is on the Google Maps API, but we are using Rails so expect to get a dose of Rails goodness in the course too.
We will be looking at DragZoom (my open-source custom map control recently adopted by Google), and GeoKit (my Rails plugin for mapping applications) during the course as well.
Links: Google Maps Training course description or jump right to the registration form.
Continue reading "Google Maps Training in San Francisco"»
GZoom inducted into Google; becomes DragZoom
My GZoom drag-to-zooom control has been incorporated into Google's GMaps Utility Library. Pamela Fox from Google has been working on it with me for some time, and has done a fantastic job with both the code and the documentation. GZoom is now called DragZoom, and it is available directly off of Google's servers. No need to download your own copy, you can source it from Google just like the GMaps API itself.
Pamela also put together a great example page demonstrating the different instantiation options. Other links for the project:
- Packed source: http://gmaps-utility-library.googlecode.com/svn/trunk/dragzoom/1.0/src/dragzoom_packed.js
- Unpacked source: http://gmaps-utility-library.googlecode.com/svn/trunk/dragzoom/1.0/src/dragzoom_packed.js
- DragZoom project Root
- Documentation: examples and reference
Continue reading "GZoom inducted into Google; becomes DragZoom"»
Hpricot + GeoRSS + KML
ShapeWiki now supports import for both KML and GeoRSS. GeoRSS testing is a bit sketchy, as I had a hard time finding GeoRSS feeds with shapes in them.
To import shapes from either GeoRSS or KML, go to the KML Import tab on the top of the page.
With KML import, you can now use Google MyMaps to build shapes, and import the shapes directly into ShapeWiki. Give it a try by 1) creating new map at maps.google.com; 2) using the shape tool to create one or may shapes on your map; 3) copying the URL (via "link to this page" on Google Maps) into the ShapeWiki import.
The import code is straightforward. I use Hpricot for XML parsing, as it makes dealing with XML marginally bearable. I check if the shapes are in folder(s), and if they are, I use the folder names for tags. To make his work, I added an #ancestors method to Hpricot's Elem:
module Hpricot
class Elem
def ancestors
element=self
path=Hpricot::Elements.new
while element.class != Hpricot::Doc do
path.push element
element = element.parent
end
path
end
end
end
. . . which returns a nice Elements array of all the ancestors of the element on which it was called. I use it like so:
shape.tag_list=p.ancestors.search('/folder/name').map{|e|e.inner_html.strip.gsub(' ','_').downcase}.uniq.join(' ')
I was a bit surprised that Hpricot's Elem didn't already have an ancestor method. If I missed something, or there's an easier way to do this, drop me a comment!
Continue reading "Hpricot + GeoRSS + KML"»
ShapeWiki: a collaborative shape repository
http://shapewiki.com is a an open repository for shapes you can use on Google Maps. It includes a point-and-click editor for creating new shapes, and is backed by a tagged, searchable database so you can find shapes made by others.
There are three export formats:
- JSON-- essentially an array of latitude/longitude pairs with some additional metadata
- JavaScript -- the same JSON array, but with some associated JS to easily allow you to add a GPolygon to your map
- XML
Potential future enhancements include: export as encoded polylines, and import from a variety of formats.
When you try out creating a shape, notice the reference image section on the right-hand side. Enter a URL there to pin an image to the map so you can trace neighborhoods, etc. Here's an image of the neighborhoods in San Francisco: http://real-estate-us.com/images/SanFranciscoNeighbourhoodsCA.gif
Continue reading "ShapeWiki: a collaborative shape repository"»
Win a Copy of Google Maps Applications with Rails and Ajax
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!
Continue reading "Win a Copy of Google Maps Applications with Rails and Ajax"»
Latitude and Longitude Columns in Rails Migrations
1. Just use Floats.
This may be the first thing you try. The migration looks like this:class CreatePlaces < ActiveRecord::Migration
def self.up
create_table :places do |t|
t.column :lat, :float
t.column :lng, :float
end
end
def self.down
drop_table :places
end
end
Floats work, but their precision is limited. For example, if you geocode 101 Market st, San Francisco, you'll get latitude=-122.395899, longitude=37.793621. Store those values in the MySql table created by the above migration, and you'll get -122.396 and 37.7936 back out, thereby losing three decimal places of precision on your latitude. Still, they work fine if you're experimenting, and I use floats for some examples in my upcoming Rails/Gmaps book.
By the way, try this link if you want to see Google's XML output for geocoding this address. Yes, you can even use my personal Google API key embedded in this link -- I don't mind:
http://maps.google.com/maps/geo?q=101+market+st,94105.........
2. Alter a column using post-hoc SQL in your migration
Use this approach if you are running Rails 1.1.6, and need better precision than what Float provides. The migration looks like this:def self.upThis gives you full-on decimal precision, but now your migration is MySQL-specific and not very DRY. That's the breaks if you're on 1.1.6. You should really upgrade to Rails 1.2RCx and . . .
create_table :places do |t|
t.column :lat, :float
t.column :lng, :float
end
execute("ALTER TABLE places MODIFY lat numeric(15,10);")
execute("ALTER TABLE places MODIFY lng numeric(15,10);")
end
3. Use proper decimal-type migrations in Rails 1.2
def self.up
create_table :places do |t|
t.column "lat", :decimal, :precision => 15, :scale => 10
t.column "lng", :decimal, :precision => 15, :scale => 10
end
end
Much nicer! This doesn't work in 1.1.6 but was fixed in 1.2. The columns it generates is identical to the post-hoc ALTER TABLE we did earlier, but without the DB-specific ugliness or redundancy. By the way, see here if you want to learn about scale and precision in the DB.
In summary
use Rails 1.2 and :decimal, :precision => 15, :scale => 10 migrations if possible. Use floats if you're on 1.1.6 and just messing around. Use ALTER TABLE if you're stuck on Rails 1.1.6, and can stomach it.Continue reading "Latitude and Longitude Columns in Rails Migrations"»
Location vs Locality
Working with maps-based applications is interesting on a number of levels. One of the interesting aspects is the notion of Location versus the notion Locality.Location is easy and straightforward. Most things you can put on a map occupy a single, discrete point. If you are skeptical at all about the sheer quantity of things that fall into this category, go read up on the GoogleMapsMania blog.
Anyway, lots of things have location. If you have an address but don't know the latitude and longitude, you can find the latitude/longitude easily through the process of geocoding. Geocoding (at least in the united states and Canada) is fast, reliable, and free. Once you've geocoded an address, you have a single, discrete latitude and longitude which you can then plot on your map.
Location in your schema
As you design your schema for a map-based app, the location aspect will typically manifest itself as a couple of columns in your database, for example in your places table:places table
latitude (numeric)
longitude (numeric)
address (string)
What about locality?
Locality is a little more complex. Locality deals with how you will organize the geo-spatial information in your app, and how you will allow users to navigate through it. Here's an example: I live in San Francisco. Geographically, we think of our area as the San Francisco Bay Area, which comprises a lot of cities -- Oakland and Berkeley on the east, Tiburon and Sausalito to the north, San Mateo and Palo Alto to the south, and so on. Going a little more granular, our area as broken up into the "North Bay," "Peninsula," "East Bay" etc. So, if we are looking for ways to let people navigate through our spatial data in terms that are familiar and useful, these are the labels we would like to give people: "North Bay," "Peninsula," and so on.There's the problem, and the reason that locality starts to look complicated. There's no good programatic way to place locations into these familiar, human-oriented notions of locality. When you geocode an address, Google's Geocoder never returns the result, "Yeah, its smack dab in the middle of the peninsula."
And that's the crux of the difference between Location and Locality: Location is a precise, computer-oriented thing. Whether it's represented by an address or a latititude/longitude, there's ultimately no ambiguity about where it is. Locality, on the other hand, is a human-oriented notion. It's based on how people in a region thing about the geographic areas around them, and it's not easy to deal with in a programatic way.
Furthermore, Locality changes with the scope of your data. If you only have data in the city of San Francisco, then individual neighborhoods may be the relevant language of locality. If you have data throughout the Bay Area, the language is different -- North Bay, Peninsula, etc. If your data extends throughout California, or the whole United States, then the notions (and terminology) of Locality change again.
Locality in your Schema
When you're designing your schema, it's easy to mix up location and locality. For example, you could add a city_name (string) to your places table, and it feels like you're just extracting an existing piece of data (the city name from the geocoding process, which you already have in the address field anyway) for easier reference. In fact, you're probably stepping towards locality at this point: the next step is to denormalize the city_name into its own table, and refer to instances of city though a city_id in your places table. In doing so, you just created a city-centric navigational paradigm for your app.Now there's nothing wrong with a city-centric navigational paradigm. It works fine for lots of apps. I've found, however, that being aware of location vs. locality as separate concepts has cleared up a lot of confusion I had with my own location-based schemas. So, if I two tables, places and cities:
Places table
latitude (numeric)
longitude (numeric)
address (string)
city_id
Cities table
id
name
latitude
logitude
The first three columns in places represent location. The city_id column, and the cities table to which it refers, represent locality.
Locality beyond city-centric
One of the problems with city-centric navigation is that if two cities are butted right up against one another (as is common in metro areas), a search for places in one city might not show a place in a nearby city, even though the actual geographic distance is small (say, half a mile).My approach to the locality problem has been to provide city-based navigation, but also provide links to nearby cities (ordered by distance) to let users navigate in a semi-spatially matter when needed.
Some other approaches are:
- organize cities into more human-centric groups where appropriate. An example is to group San Jose, Santa Clara, and Sunnyvale together as "South Bay" cities. If you only have a few cities, you might be able to do this manually. If you have more than a few cities, or if users can create cities on the fly, you'll have to figure out some other way of establishing such groupings. You might be able to do it programatically (through a clustering algorithm), or in web 2.0 style (by asking users to tag according to metro area). Both approaches have challenges, and if you come up with a good way of doing this, let me know.
- punt on locality by ONLY providing a maps-based interface, and having users enter a zip code or address to jump to different areas on the map. A lot of mashups use this approach, but you'll run into limitations pretty quickly.
- dig into the "official" data available for this. The U.S. Office of Management and Budget (OMB) defines "Metropolitan and micropolitan statistical areas," and makes the data available for download: http://www.census.gov/population/www/estimates/metrodef.html. Can this be parsed into something which is useful for navigation through a geo-spatial dataset? I don't know if the data corresponds well enough to the way people think about areas to make it worthwhile. If you've worked with this data, drop me a note.
In summary
- location and locality are two different things. Location is precise, locality is human-oriented.
- cities are a natural starting point for working with locality.
- the notion of locality in your application will vary depending upon the breadth and density of your data. Your scale of locality might be neighborhoods in New York, or it might be countries in Europe.
- there are several options for providing navigation though geo-spatial data, ranging from simple (provide links to nearby cities, ordered by distance) to complex (dig into government data on metropolitan areas)
Continue reading "Location vs Locality"»
Speaking at SDForum next week
I will be presenting "Ruby and Google Maps" at this month's SDForum Ruby SIG, which is next Thursday (Dec 7th). If you're in the area, I'd love to see you there!
I plan on making this a pretty interactive session. I've got three broad topics, and we'll spend time on whatever you think is most interesting. The topics are:
- ImageMagick and geographic data: creating custom Google Map overlays with RMagick
- Google Maps controls: creating a better (or at least different) zoom control
- Demystifying the geocoder: you, too, can create a geocoder from scratch
Hope to see you there!
Continue reading "Speaking at SDForum next week"»
GZoom enhancements
I added a few things to GZoom today. What's new:
- GZoom now works on maps that move or are resized on the page
- new "Sticky" mode -- can stay in zoom mode for multiple zooms
- more flexible zoom button, can use images, arbitrary text, etc.
- tested and confirmed to work with GMaps new Marker Manager
- you can specify optional callback functions for the following events: buttonClick,dragStart,dragging,dragEnd
See the GZoom original post, the new example, or read on for details of what's updated. Or just grab the new code:
gzoom.js -- right-click --> save asgzoom_uncompressed.js -- right-click --> save as
Read on much more detail . . .
Continue reading "GZoom enhancements"»
Tools and Resources for Mapping on Rails
I've come across two projects which aim to simplify the map creation process for us Rails users: Cartographer and YM4R. Cartographer has been around longer (according to Rubyforge), but YM4R is more mature and full-featured. YM4R is also under more active use and development; it's currently ranked #264 in Rubyforge.
Cartographer
Cartographer gives you helpers to easily create Google Maps and add markers to it. The project recently went through a major revision for compatability with the Google Maps API version 2, and unfortunately the documentation hasn't been updated in step. The project includes code for geocoding via both Geocoder.us and Ontok (a geocoder which I haven't encountered before). Something which looks good in the code is an abstraction layer for the Google Maps key -- it will let you specify different GMaps keys for different combinations of hosts/controllers/actions.
YM4R
Next up is YM4R. YM4R packs a lot of functionality for both Google Maps and Yahoo maps (via the Mapstraction library). The project is broken into four parts:
- The YM4R/GM plugin for Rails provides helpers for interacting with the main Google Maps API. It is engineered to facilitate updates to the map via RJS. The plugin also includes some functionality not directly available through the GMaps API: Clusterer, GeoRSS, WMS layer. Read up in the documentation do see if these extensions are useful for your project.
- The YM4R/Mapstraction plugin for Rails provides rails helpers to interact with the Mapstraction API. As with YM4R, it facilitates updates to the maps via RJS.
- The YM4R gem provides ruby helpers for the Google Maps geocoding API and the Yahoo! Maps Building Block API’s: Geocoding, Traffic, Map Image and Local Search v3. The gem plugs into both Yahoo and Google geocoders, but not any 3rd party geocoders like geocoder.us.
- The Tools package provides command-line tools to generate tiles in order to create custom map types for use in Google Maps.
The YM4R author has been posting regular updates and tutorials on using the packages, so there's a lot of information available. He has the RDocs for all the packages online as well.
The Tools package for custom tiles is interesting. Read through the docs at http://thepochisuperstarmegashow.com/ProjectsDoc/ym4r_tools-doc/ and check out an example of the result at http://open.atlas.free.fr/GMapsTransparenciesImgOver.php
Helpers or no Helpers?
Cartographer and YM4R definitely provide some food for thought. If you're doing serious map-based development, you have a choice to make: do you want to program directly in JavaScript (the native tongue of all the web-based mapping APIs), or do you want an a Ruby-based intermediary package (as these two projects provide). There are pros and cons to both approaches, and I think the answer will depend on the kind of project you're doing. I'll write more on this in a future post.
Links/References
- Cartographer
- YM4R
- Mapstraction is an intermediary library which aims to abstract out the differences among GMaps, Yahoo maps, and Microsoft Virtural Earth. The above-mentioned YM4R/Mapstraction plugin utilizes the Mapstraction code. Mapstraction currently talks to v2 Google, v3 Yahoo and v2 VE.
- Google Maps API
- Basic Rails, Geocoding, and Google Maps howto
Continue reading "Tools and Resources for Mapping on Rails"»
GZoom uncompressed Source released
Continue reading "GZoom uncompressed Source released"»
GZoom drag-to-zoom Google Maps control
Update #1: I've posted the uncompressed source code here: gzoom_uncompressed.js . The code is released under an MIT-style license. Enjoy, and let me know if you make enhancements to the code that others might find useful -- I'll try to roll useful enhancements back into the core.
Update #2: version 0.2 released 11/21/06. What's new:
- handles maps that move or are resized on the page
- more flexible activation button, can use images, arbitrary text, etc.
- tested and confirmed to work with GMaps new Marker Manager
- you can specify optional callback functions for the following events: buttonClick,dragStart,dragging,dragEnd
- Here's a new example demonstrating some of the new options and the callbacks.
Update #3: GZoom has been incorporated into Google's own Google Maps Utility Library, and renamed DragZoom. From now on, you should use the code from Google's repository. Get DragZoom here.
Continue reading "GZoom drag-to-zoom Google Maps control"»
Rails, Geocoding, and Google Maps
Why the lookup web service? The Google Maps API doesn't just let you map an address. You need to supply the Maps API with a longitude and latitude to place a marker on a map (or even to center a map on a city). The geocoding web service translates a valid address into longitude and latitude; you can pass the result to the Maps API.
Continue reading "Rails, Geocoding, and Google Maps"»
