Archive

Author Archive

Nominatim (Reverse Geocoder) via PL/PYTHON for RubyOnRails Mapping App

January 6th, 2012 rupert Comments off

Even google maps enterprise have restrictions on their geocoding/reverse-geocoding services, 100k if my memory serves me correctly. So, I have to rollout our own service to allow millions of lonlats for reverse geocoding. Have a look at Nominatim, yes it’s opensource. If you need to get it up and running, have a read of my nominatim installation via homebrew on OSX.

The nominatim www interface which spits out xml/json depending on the format parameter is done in php.

Anyway, I wanted to expose/use this webservice for our Rails3 app. It will also be good if we don’t use the nominatim webservice all the time if the lonlat was already requested–caching.

Python-Nominatim https://github.com/rdeguzman/python-nominatim

This project was forked from Austin’s Gabels python-nominatim. I added the ability to pass a base_url to the classes and added reverse_geocode.py. So assuming you have Python installed, you can do a reverse geocode like this…

from nominatim import ReverseGeocoder
client = ReverseGeocoder("http://127.0.0.1/nominatim/reverse.php?format=json")
response = client.geocode(-37.856206, 145.233980)
 
print response['full_address']
#Amesbury Avenue, Wantirna, City of Knox, 3152, Australia

PL/PYTHON
Now we wrap this python code via PL/PYTHON so Postgres can call it. Checkout setup.sql

CREATE PROCEDURAL LANGUAGE 'plpythonu' HANDLER plpython_call_handler;
 
CREATE OR REPLACE FUNCTION reverse_geocode(geocoding_url text, latitude float, longitude float) RETURNS
  text
  AS
  $$
    import nominatim
    client = nominatim.ReverseGeocoder(geocoding_url)
    response = client.geocode(latitude, longitude)
    RETURN response['full_address']
  $$
  LANGUAGE 'plpythonu';

With the snippet above, we can now call this with a regular SELECT statement…

SELECT reverse_geocode('http://127.0.0.1/nominatim/reverse.php?format=json', -37.856206, 145.233980); 
                       reverse_geocode
  ----------------------------------------------------------
   Amesbury Avenue, Wantirna, City of Knox, 3152, Australia
  (1 row)

Rails ActiveRecord

    create_table :locations do |t|
      t.float    :latitude
      t.float    :longitude
      t.text     :address
    end

In AR, I created a location model above and exposed a reverse_geocode method below

class Location < ActiveRecord::Base
 
  def self.reverse_geocode(geocode_url, lat, lon)
    sql_string = "SELECT reverse_geocode('#{geocode_url}', #{lat}, #{lon}) as address, #{lat} as latitude, #{lon} as longitude"
    loc_array = self.find_by_sql sql_string
    loc_array[0]
  end
 
end

So now, in one of my models, I could simply do..

class ActiveSession < ActiveRecord::Base
...
  def location_address
    if self.has_gps?
      loc = Location.reverse_geocode('http://path/to/reverse.php?format=json', self.gps_latitude, self.gps_longitude)
      loc.address
    else
      nil
    end
  end
...
end

In the view, we can simple call model.location_address to retrieve the location details. Below is a code snippet which creates a google marker and adds the location details in the infoWindow.

<% location = active_session.location_address %>
 
var latlong = new google.maps.LatLng(<%= active_session.gps_latitude %>, <%= active_session.gps_longitude %>);
 
var content = '<div style="width: 300px;">';
content = content + '<p><%= escape_javascript location %></p>';
content = content + '<p><%= active_session.gps_longitude %>,<%= active_session.gps_latitude %></p>';

marker.png

Caching
Our last step is to improve performance via caching. I have opted to do this from the PL/PYTHON end but using a Rails activerecord model/table. This way, the Rails activerecord has no idea that it is cached when it calls model.location_address. Below, I wrap the new reverse_geocode PL/PYTHON function in a rails migration.

class CreateFunctionReverseGeocoder < ActiveRecord::Migration
  ActiveRecord::Base.connection.schema_search_path = "public"
 
  def self.up
    execute 'CREATE OR REPLACE FUNCTION reverse_geocode(geocoding_url text, latitude float, longitude float) RETURNS
      text
      AS
      $$
        plan = plpy.prepare("SELECT address FROM locations WHERE latitude = $1 AND longitude = $2", [ "float", "float" ])
        rv = plpy.execute(plan, [ latitude, longitude ], 1)
 
        if rv.nrows() > 0:
          result = rv[0]["address"]
        else:
          import nominatim
          client = nominatim.ReverseGeocoder(geocoding_url)
          response = client.geocode(latitude, longitude)
          result = response["full_address"]
          insert_plan = plpy.prepare("INSERT INTO locations(latitude, longitude, address) VALUES($1, $2, $3)", ["float", "float", "text"])
          plpy.execute(insert_plan, [ latitude, longitude, result ])
 
        return result
      $$
      language plpythonu;'
  end
 
  def self.down
    execute 'DROP FUNCTION IF EXISTS reverse_geocode(text, double precision, double precision);'
  end
end

Benchmarks
I plotted 1000 records on my MBP (old core2duo early 2009 4GB RAM). Initial launch takes 108 seconds to load, ~ 2 minutes? But subsequent requests loads < 2 secs.

For 1000 records:
Completed 200 OK in 110478ms (Views: 1608.8ms | ActiveRecord: 108674.6ms)
Completed 200 OK in 1744ms (Views: 1110.7ms | ActiveRecord: 443.3ms)

Below is an architecture diagram of how the systems talk to each other. The locations cache is inside the geo_app_development db. Ofcourse, the nominatim database (gazetteer_au) is separate from our domain so it goes into a different db/server whereever.
archi.png

Categories: geocoding, google Tags: , ,

homebrew + python

December 30th, 2011 rupert Comments off

Read http://docs.python-guide.org/en/latest/starting/installation/

Installation

/usr/local/Cellar% brew install python --framework
==> Downloading http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tar.bz2
File already downloaded in /Users/rupert/Library/Caches/Homebrew
==> Patching
patching file Lib/whichdb.py
Hunk #1 succeeded at 91 with fuzz 1.
==> ./configure --prefix=/usr/local/Cellar/python/2.7.2 --enable-framework=/usr/local/Cellar/python/2.7.2/Frameworks
==> make
==> make install
==> Downloading http://pypi.python.org/packages/source/d/distribute/distribute-0.6.24.tar.gz
File already downloaded in /Users/rupert/Library/Caches/Homebrew
==> /usr/local/Cellar/python/2.7.2/bin/python setup.py install
==> Caveats
A "distutils.cfg" has been written to:
  /usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils
specifing the install-scripts folder as:
  /usr/local/share/python
 
If you install Python packages via "python setup.py install", easy_install, pip,
any provided scripts will go into the install-scripts folder above, so you may
want to add it to your PATH.
 
Distribute has been installed, so easy_install is available.
To update distribute itself outside of Homebrew:
    /usr/local/share/python/easy_install pip
    /usr/local/share/python/pip install --upgrade distribute
 
See: https://github.com/mxcl/homebrew/wiki/Homebrew-and-Python
 
Framework Python was installed to:
  /usr/local/Cellar/python/2.7.2/Frameworks/Python.framework
 
You may want to symlink this Framework to a standard OS X location,
such as:
    mkdir ~/Frameworks
    ln -s "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework" ~/Frameworks
==> Summary
/usr/local/Cellar/python/2.7.2: 4808 files, 77M, built in 86 seconds
brew install python --framework  76.78s user 21.49s system 112% cpu 1:27.00 total

Symlinks

The following links were created below to ensure that 2.7 is the latest Python picked up during installation of other software (i.e postgresql)

sudo ln -s /usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7 /System/Library/Frameworks/Python.framework/Versions/2.7
sudo ln -s /usr/local/share/python/easy_install /usr/bin/easy_install
sudo ln -s /usr/local/share/python/easy_install-2.7 /usr/bin/easy_install-2.7
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 /usr/bin/python2.7
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7-config /usr/bin/python2.7-config
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/2.7/bin/pydoc2.7 /usr/bin/pydoc2.7
sudo ln -s /usr/bin/python2.7 /usr/bin/python
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/2.7 /System/Library/Frameworks/Python.framework/Versions/Current
mkdir -p /Library/Python/2.7
ln -s /usr/local/lib/python2.7/site-packages /Library/Python/2.7/site-packages

Site-Packages. Where?

Note that site-packages will be installed in

/usr/local/lib/python2.7/site-packages
~% ls -la /Library/Python/2.6/site-packages
total 136
drwxrwxr-x  14 root    admin    476 30 Dec 18:13 ./
drwxrwxr-x   4 root    admin    136 30 Dec 18:11 ../
-rw-r--r--@  1 rupert  admin   6148 30 Dec 18:13 .DS_Store
-rw-rw-r--   1 root    admin    119 11 Feb  2010 README
-rw-r--r--   1 rupert  admin    241 30 Dec 18:01 easy-install.pth
-rw-r--r--   1 rupert  admin   3129 30 Dec 18:02 googlemaps-1.0.2-py2.6.egg-info
-rw-r--r--   1 rupert  admin  19703 16 Oct  2009 googlemaps.py
-rw-r--r--   1 rupert  admin  19153 30 Dec 18:02 googlemaps.pyc
drwxr-xr-x  39 root    admin   1326  7 Feb  2010 mod_python/
-rw-r--r--   1 root    admin    267  7 Feb  2010 mod_python-3.3.2_dev_20080819-py2.6.egg-info
drwxr-xr-x   8 rupert  admin    272 30 Dec 18:13 nominatim/
-rw-r--r--   1 rupert  admin   4462 30 Dec 18:08 nominatim-0.90-py2.6.egg
drwxr-xr-x  15 rupert  admin    510 30 Dec 18:01 simplejson/
drwxr-xr-x   4 rupert  admin    136 30 Dec 18:13 simplejson-2.3.1-py2.6.egg/
~% ls -la /Library/Python/2.7/site-packages
lrwxr-xr-x  1 root  admin  38 30 Dec 18:23 /Library/Python/2.7/site-packages@ -> /usr/local/lib/python2.7/site-packages
~% which python
/usr/local/bin/python
~% python --version
Python 2.7.2

Installing PHP on OSX

December 28th, 2011 rupert Comments off

I need wordpress + nominatim running on OSX and since it uses php…

http://www.php.net/manual/en/install.unix.apache2.php

1. Installation

./configure --prefix=/usr/local/php-5.3.8 --with-mysql --with-pgsql=/usr/local/postgresql --with-apxs2=/usr/local/apache2/bin/apxs
make
sudo make install
rupert-mbp~/Desktop/php-5.3.8% sudo make install
Password:
Installing PHP SAPI module:       apache2handler
/usr/local/apache2.2.14/build/instdso.sh SH_LIBTOOL='/usr/local/apache2.2.14/build/libtool' libs/libphp5.so /usr/local/apache2.2.14/modules
/usr/local/apache2.2.14/build/libtool --mode=install cp libs/libphp5.so /usr/local/apache2.2.14/modules/
cp libs/libphp5.so /usr/local/apache2.2.14/modules/libphp5.so
Warning!  dlname not found in /usr/local/apache2.2.14/modules/libphp5.so.
Assuming installing a .so rather than a libtool archive.
chmod 755 /usr/local/apache2.2.14/modules/libphp5.so
[activating module `php5' in /usr/local/apache2.2.14/conf/httpd.conf]
Installing PHP CLI binary:        /usr/local/php-5.3.8/bin/
Installing PHP CLI man page:      /usr/local/php-5.3.8/man/man1/
Installing build environment:     /usr/local/php-5.3.8/lib/php/build/
Installing header files:          /usr/local/php-5.3.8/include/php/
Installing helper programs:       /usr/local/php-5.3.8/bin/
  program: phpize
  program: php-config
Installing man pages:             /usr/local/php-5.3.8/man/man1/
  page: phpize.1
  page: php-config.1
Installing PEAR environment:      /usr/local/php-5.3.8/lib/php/
[PEAR] Archive_Tar    - installed: 1.3.7
[PEAR] Console_Getopt - installed: 1.3.0
[PEAR] Structures_Graph- installed: 1.0.4
[PEAR] XML_Util       - installed: 1.2.1
[PEAR] PEAR           - installed: 1.9.4
Wrote PEAR system config file at: /usr/local/php-5.3.8/etc/pear.conf
You may want to add: /usr/local/php-5.3.8/lib/php to your php.ini include_path
/Users/rupert/Desktop/php-5.3.8/build/shtool install -c ext/phar/phar.phar /usr/local/php-5.3.8/bin
ln -s -f /usr/local/php-5.3.8/bin/phar.phar /usr/local/php-5.3.8/bin/phar
Installing PDO headers:          /usr/local/php-5.3.8/include/php/ext/pdo/
sudo make install  5.52s user 9.35s system 79% cpu 18.819 total

2. Configuration
Specify php.ini per virtualhost

PHPINIDir /etc

Categories: Uncategorized Tags: ,

freebsd + apache + rvm + rails + passenger

November 22nd, 2011 rupert No comments

1. Install apache22

% cd /usr/ports/www/apache22
% make config
% make install clean
% vim /etc/rc.conf
apache22_enable=YES
/usr/local/etc/rc.d/apache22 start

2. Install rvm via multi user install from http://beginrescueend.com/rvm/install/

Login as root.
%  bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
Downloading RVM from wayneeseguin branch stable
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  799k  100  799k    0     0   109k      0  0:00:07  0:00:07 --:--:--  199k
 
Installing RVM to /usr/local/rvm/
    RVM system user group 'rvm' exists, proceeding with installation.
 
# RVM:  Shell scripts enabling management of multiple ruby environments.
# RTFM: https://rvm.beginrescueend.com/
# HELP: http://webchat.freenode.net/?channels=rvm (#rvm on irc.freenode.net)
# Screencast: http://screencasts.org/episodes/how-to-use-rvm
 
# In case of any issues read output of 'rvm requirements' and/or 'rvm notes'
 
Installation of RVM in /usr/local/rvm/ is almost complete:
 
  * First you need add all users that will be using rvm to 'rvm' group,
    anyone using rvm will be operating with `umask g+w`.
 
  * To start using RVM you need to run `source /etc/profile.d/rvm.sh`
    in all your open shell windows, in rare cases you need to reopen all shell windows.
 
  * Optionally you can run `rvm tools rvm-env ruby bash` which will generate 
    shebang wrappers for easier selecting ruby in scripts.
 
# root,
#
#   Thank you for using RVM!
#   I sincerely hope that RVM helps to make your life easier and more enjoyable!!!
#
# ~Wayne

3. Add your users to rvm group

# vim /etc/group
....
rvm:*:1003:root,rupert

Load rvm script to current shell by calling source /etc/profile.d/rvm.sh or add it to /etc/profile and re-login

#vim /etc/profile
source /etc/profile.d/rvm.sh

After relogging in, simply type rvm and it should spit out something. If the command is not found, then you are doing something wrong.

4. Install 1.9.3

% rvm install 1.9.3
Installing Ruby from source to: /usr/local/rvm/rubies/ruby-1.9.3-p0, this may take a while depending on your cpu(s)...
 
ruby-1.9.3-p0 - #fetching 
ruby-1.9.3-p0 - #downloading ruby-1.9.3-p0, this may take a while depending on your connection...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  3 8604k    3  340k    0     0  27173      0  0:05:24  0:00:12  0:05:12 64100

Use ruby1.9.3

% rvm --default use 1.9.3-p0
% gem install bundler -V

5. Install passenger3

This will install system wide passenger gem which is installed on 1.9.3-p0 gemset.

% gem install passenger -V

You should have at least the ff gems installed:

bundler (1.1.3)
daemon_controller (1.0.0)
fastthread (1.0.7)
passenger (3.0.12)
rack (1.4.1)
rake (0.9.2.2)
% cd /usr/local/rvm/gems/ruby-1.9.3-p0/gems/passenger-3.0.11/bin
% ./passenger-install-apache2-module
--------------------------------------------
The Apache 2 module was successfully installed.
 
Please edit your Apache configuration file, and add these lines:
 
   LoadModule passenger_module /usr/local/rvm/gems/ruby-1.9.3-p0/gems/passenger-3.0.11/ext/apache2/mod_passenger.so
   PassengerRoot /usr/local/rvm/gems/ruby-1.9.3-p0/gems/passenger-3.0.11
   PassengerRuby /usr/local/rvm/wrappers/ruby-1.9.3-p0/ruby
 
After you restart Apache, you are ready to deploy any number of Ruby on Rails
applications on Apache, without any further Ruby on Rails-specific
configuration!
 
Press ENTER to continue.
 
 
--------------------------------------------
Deploying a Ruby on Rails application: an example
 
Suppose you have a Rails application in /somewhere. Add a virtual host to your
Apache configuration file and set its DocumentRoot to /somewhere/public:
 
   <VirtualHost *:80>
      ServerName www.yourhost.com
      DocumentRoot /somewhere/public    # <-- be sure to point to 'public'!
      <Directory /somewhere/public>
         AllowOverride all              # <-- relax Apache security settings
         Options -MultiViews            # <-- MultiViews must be turned off
      </Directory>
   </VirtualHost>

6. Edit httpd.conf to load the passenger module

To use a rvm gemset by passenger read “Rails app with custom .rvmrc and a system wide passenger install” https://rvm.beginrescueend.com/integration/passenger/

LoadModule passenger_module /usr/local/rvm/gems/ruby-1.9.3-p0/gems/passenger-3.0.11/ext/apache2/mod_passenger.so
PassengerRoot /usr/local/rvm/gems/ruby-1.9.3-p0/gems/passenger-3.0.11
PassengerRuby /usr/local/rvm/wrappers/ruby-1.9.3-p0/ruby
<VirtualHost 192.168.69.3:80>
   ServerAdmin rupert@2rmobile.com
   ServerName myserver
   ServerAlias myserver
 
   DocumentRoot "/path/to/myapp/public"
   <Directory "/path/to/myapp/public">
 
   #DocumentRoot "/path/to/myapp/current/public"
   #<Directory "/path/to/myapp/current/public">
      Options Indexes MultiViews
      AllowOverride None 
      Order allow,deny
      Allow from all 
   </Directory>
 
   CustomLog /var/log/apache22/myapp-error.log combinedio
   LogLevel warn 
</VirtualHost>

We need a railsapp/config/setup_load_paths.rb to use our custom .rvmrc

if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm')
   begin
      rvm_path     = File.dirname(File.dirname(ENV['MY_RUBY_HOME']))
      rvm_lib_path = File.join(rvm_path, 'lib')
      $LOAD_PATH.unshift rvm_lib_path
      require 'rvm'
      RVM.use_from_path! File.dirname(File.dirname(__FILE__))
   rescue LoadError
      # RVM is unavailable at this point.
      raise "RVM ruby lib is currently unavailable."
   end
end
 
# Pick the lines for your version of Bundler
# If you're not using Bundler at all, remove all of them
 
# Require Bundler 1.0 
ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__))
require 'bundler/setup'
 
# Require Bundler 0/9
# if File.exist?(".bundle/environment.rb")
#   require '.bundle/environment'
# else
#   require 'rubygems'
#   require 'bundler'
#   Bundler.setup
# end

Visit this stackoverflow question http://stackoverflow.com/questions/5680341/how-to-load-passenger-from-apache-with-rvm-and-unique-gem-sets

% git clone rupert@server:/path/to/your/myapp.git
% cd myapp

9. Run bundle

% bundle install -V
Fetching source index for http://rubygems.org/
....

Errors that you may encounter

Note: The rvmrc located in ‘../releases/20120208034235′ could not be loaded, likely due to trust mechanisms. Please run ‘rvm rvmrc {trust,untrust} “../releases/20120208034235″‘ to continue, or set rvm_trust_rvmrcs_flag to 1. (RVM::ErrorLoadingRVMRC)

% vim /etc/rvmrc
umask g+w
rvm_trust_rvmrcs_flag=1
Categories: freebsd Tags: , ,

freebsd + git server + gitweb

November 22nd, 2011 rupert Comments off

1. install git server

$ cd /usr/ports/devel/git
$ make install clean

Note: Include gitweb as an option

2. Modify /etc/rc.conf

git_daemon_enable="YES"
git_daemon_directory="/var/db/git/repo"
git_daemon_flags="--export-all --syslog --enable=receive-pack --listen=ip_address --verbose"

3. Create git user

$ pw user add git
$ passwd git
$ chsh git
Login: git
Password: *******************
Uid [#]: 1002
Gid [# or name]: 1002
Change [month day year]:
Expire [month day year]:
Class:
Home directory: /var/db/git
Shell: /usr/local/bin/git-shell
Full Name: User &
Office Location:
Office Phone:
Home Phone:
Other information:

“git” user would create repositories. We could also add “rupert” to the git group.

pw user mod rupert -G git

4. Let’s create a repository

$ cd /var/db/git/repo
$ sudo mkdir cws-rails.git
$ cd cws-rails.git/
$ sudo git --bare init
Initialized empty Git repository in /var/db/git/repo/cws-rails.git/
$ cd ..
$ ls -l
total 4
drwxr-xr-x  7 root  git  512 Nov 16 12:54 cws-rails.git
drwxrwxr-x  7 git   git  512 Nov 16 11:52 myproject.git
$ sudo chown -Rf git:git cws-rails.git
$ sudo chmod -Rf 775 cws-rails.git

Make sure to change the ownership to git. We also make it writable to the group so users like rupert will have write access.

5. Let’s clone, commit and push
So now in my MBP, i will clone myproject.git, change some file and push.

$ git clone rupert@rupert-bsd:/var/db/git/repo/myproject.git
Cloning into myproject...
Password:
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 12 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (12/12), done.
~/Desktop/myproject[master]% gc -m "Updated CHANGELOG" CHANGELOG 
[master d9fd0f7] Updated CHANGELOG
 1 files changed, 1 insertions(+), 0 deletions(-)
~/Desktop/myproject[master]% git push
Password:
Counting objects: 5, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 332 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To rupert@rupert-bsd:/var/db/git/repo/myproject.git
   5d16498..d9fd0f7  master -> master

Note: So that we don’t need to specify the password all the time, generate an ssh-keygen -t rsa for id_rsa.pub and append it to rupert@rupert-bsd:/home/rupert/.ssh/authorized_keys

6. Setup Git, Configure and Restart Apache2

# Copy the gitweb directory to your apache22 directory
$ sudo cp -Rf /usr/local/share/examples/git/gitweb /usr/local/www/apache22/data/
 
# Edit gitweb.cgi to point to your repo
$ vim /usr/local/www/apache22/data/gitweb/gitweb.cgi
our $projectroot = "/var/db/git/repo";
# Allow apache to execute gitweb.cgi
Alias /gitweb /usr/local/www/apache22/data/gitweb
 
<Directory /usr/local/www/apache22/data/gitweb>
  Options FollowSymLinks +ExecCGI
  AddHandler cgi-script .cgi
</Directory>

Browse http://servername/gitweb/gitweb.cgi

Categories: freebsd Tags: ,