DEV Community

orio
orio

Posted on

How to generate random cities in Ruby

In true blog fashion, I'm going to give you my life story before getting into the true meaning of this blog. My name's Austin and I'm currently attending the Flatiron bootcamp in San Fransisco. To finish off the 3rd week, we were assigned a project. We were tasked with creating a Command Line CRUD app that used a database to persist information. My partner and I decided build an app that would generate a random city and allow users to store a list of cities (travel destinations) and provide some data based around that location (eg: weather and events). Basically, an app for the wanderlust user looking for ideas for a spontaneous vacation. Similar to throwing a dart on a map. One of my jobs was to build the part of the app was would generate the random travel destination.

I figured, it would be easy. I should be able to find some blog article or Stack Overflow post on someone trying to do something similar with a solution provided. 3hrs later... I was still looking. I found some solutions that kind of worked. One example was using a series rand()s to generate random latitude and longitude coordinates then using the Google Maps API to find out where that location was. However, annoyingly, the Earth is over 70% water. Meaning 70% of the time, it would tell our users to go to a random location in the ocean.

I knew that wasn't going to work. At first, I figured, I could try to check against the API data if the coordinates was an actual location. Then just keep generating new coordinates until actual place a user could visit was found. But I figured, that probably wasn't the most efficient solution. Not only would it make the app unbearably slow (theoretically, it could never actually find a location not in the ocean), but it would also call the API each time we generated a new location. While Google's $200 in free credits it more than enough for this project. Calling an API numerous times, each time the user searched for a new travel destination, can become extremely costly if we had a user base with hundreds of thousands of users. So I knew I had to find a better way.

After giving another round of Googling a go. I found something that worked well for me, and I want to share my solution.

1.Prerequisites

a. Unless you have and know how to use Wget. I suggest following this guide on how to setup and use it. Setting up and using Wget

b. Here's a quick list of gems we'll be using. (This guide assumes you have all the gems installed and required)

gem 'countries'
gem 'cities'

2. Getting our data

To get all of our data, I used a the cities gem. The cities gem was built as an extension of the counties gem using the MaxMind database. You'll need to install and require both gems. Now about that database.

To download our data, we're going to run in our terminal...
$ wget https://s3-us-west-2.amazonaws.com/cities-gem/cities.tar.gz

Then run.. $ tar -xzf cities.tar.gz to exact the data we just downloaded.

3. Building the method

#requires

Cities.data_path = 'path/to/cities'

def generate_city
    country = ISO3166::Country.all.sample
    city = country.cities[country.cities.keys.sample]
end

=> #<Cities::City:0x00007fac7a559938
 @data=
  {"city"=>"tsekelewu",
   "accentcity"=>"Tsekelewu",
   "region"=>"17",
   "population"=>nil,
   "latitude"=>"5.9833333",
   "longitude"=>"4.9833333"}>

So what's going here? Lets go over each line...

Cities.data_path = 'path/to/cities'

All we are doing here is setting the relative path to where our folder of cities we downloaded is located on our system.

country = ISO3166::Country.all.sample

Here we're setting the country variable to a random country from the country gem.

city = country.cities[country.cities.keys.sample]

Since country.cities returns a hash of city objects...

country.cities

 => {"canque"=>#<Cities::City:0x00007fdf17082250 @data={"city"=>"canque", "accentcity"=>"Canque", "region"=>"04", "population"=>nil, "latitude"=>"-44.5333333", "longitude"=>"-68.3666667"}>,
 "canquel"=>#<Cities::City:0x00007fdf17082200 @data={"city"=>"canquel", "accentcity"=>"Canquel", "region"=>"04", "population"=>nil, "latitude"=>"-44.5333333", "longitude"=>"-68.3666667"}>,
 "cantadero"=>#<Cities::City:0x00007fdf170821b0 @data={"city"=>"cantadero", "accentcity"=>"Cantadero", "region"=>"12", "population"=>nil, "latitude"=>"-29.2166667", "longitude"=>"-66.8666667"}>,
 "cantera aguirre"=>#<Cities::City:0x00007fdf17082160 @data={"city"=>"cantera aguirre", "accentcity"=>"Cantera Aguirre", "region"=>"01", "population"=>nil, "latitude"=>"-37.35", "longitude"=>"-59.0"}
}
# this is just a small segment

we can't just chain method .sample on. We'll have to use the .keys method to return an array of keys, then .sample to return a random key from that array.

Just search for the that random key in the country.cities[key we just sampled] hash and we get returned a random city from that country.

city = country.cities[country.cities.keys.sample]

=> #<Cities::City:0x00007fac7ad72188
 @data=
  {"city"=>"fatiau",
   "accentcity"=>"Fatiau",
   "region"=>"00",
   "population"=>nil,
   "latitude"=>"-19.1166667",
   "longitude"=>"-169.9"}>

And finally, we are able to generate a random city for our users to travel to. This was a fairly basic run down on how I figured out how I can generate random cities for our project. If you're interested, be sure to check out the references I left below for the documentation on all the gems. Also, if you'd like to see a working example, I've included the link to the projects GitHub repository in the References section below.

Some issues I ran into

  • Returning the city name I had 1 small issue when trying to return the city's name. At first I tried using city.city, since that's what the hash was returning. I figured out after using .methods I was able to use .name to return the city name.

References

Latest comments (0)