Posterous theme by Cory Watilo

Geocoding part 1 - Getting the longitude and latitude of all australian postcodes from google maps

As part of a grand plan to be able to map a bunch of addresses onto a graphical map of sydney or australia, firstly you need all the australian postcodes and their longitude and latitude. Now in this article i describe how to screen scrape this list from google maps (please forgive me Mr Google!). Firstly you'll need the list of all postcodes from Australia Post. You can get it here - it is a zip of a CSV file. It contains a whole bunch of non-physical addresses such as PO boxes - if you're inclined, open it in Excel and remove these. What you want to end up with is a file called 'pc-full.csv' which we'll use later. For the next step you'll need Curl installed. You can get it here. If you're behind a firewall, there's some hints here on how to get Curl/Wget to play nicely with firewalls (even though the instructions are for wget, it still applies to curl). Once you think you've got it sorted, try this from the command prompt:
curl "http://maps.google.com.au/maps?f=q&hl=en&geocode=&q=2768+australia&output=js"
You should get a printout of a whole bunch of javascript with 'center:{lat:xxx,lng:yyy}' in it somewhere. Sweet - you've got curl working. Next up we want to write a script to do all the work. I've used ruby, because it's awesome, but you can use whatever you want. The following code opens up the 'pc-full.csv' file, reads the list of distinct postcodes, runs curl against each one, parses the longitude and latitude from each, and outputs a nice CSV file with 3 columns: Postcode, Latitude, Longitude. Now i'll have to apologise for the sloppy code, but it does work (updated to fix wordpress' curly quotes):
def IsGood(fname)
  # Does a file contain the longitude eg did it connect to gmaps correctly?
  # Ironically, in C# this code would be a one liner: File.ReadAllText(fname).Contains("center:{lat:");
  r = "Missing"
  if File.exist?(fname)
    f = File.new(fname)
    lines = f.read
    f.close
    if lines.include?("center:{lat:")
      r = 'Good'
    else
      r = 'Bad'
    end
  end
  r
end
 
# get the list of unique postcodes from the CSV file downloaded from australia post
@postcodes = []
File.new('pc-full.csv').readlines.each {|l|
  x = l.gsub(/[\",]/,"").to_i
 @postcodes 0 && !@postcodes.include?(x)
}
puts "Total #{@postcodes.length} unique postcodes"
 
# Scrape them all from google maps
@postcodes.each {|postcode|
  fname = "data_#{postcode}.txt"
  if !File.exist?(fname)
   system "curl -o #{fname} \"http://maps.google.com.au/" + 
     "maps?f=q&hl=en&geocode=&q=#{"%04d" % postcode}+australia&output=js\""
   puts "Any good? #{IsGood(fname)}"
  end
}
 
# Go through the resultant files, parsing the longitude and latitude
@results = ["Postcode,Lat,Lng"]
@postcodes.each {|postcode|
  fname = "data_#{postcode}.txt"
 
  status = IsGood(fname)
  puts "#{fname} : #{status}"
 
  # Grab the long & lat
  if status=='Good'
    f = File.new(fname)
    lines = f.read
    f.close
    m = /center:\{lat:([\-.0-9]*),lng:([\-.0-9]*)\}/.match(lines)
    @results 

Note: Put curl.exe in the same folder as your ruby file above, if it's not in the path.
And your output from running all that should be a 'PostcodeLatLng.csv' file with the contents something like this:

Postcode,Lat,Lng
2000,-33.869027000000003,151.21024499999999
2001,-37.808776999999999,144.94928899999999
2002,-25.335448,135.74507600000001
2004,-33.891787999999998,151.17625100000001

Bob's your uncle! You should now have the longitude and latitude of all australian postcodes. In the next article, i'll show how i use this to make a map of australian post offices overlaying a map of australia, for instance. Here: Geocoding part 2

Converting HSV to RGB colour using C#

The other day i had to figure out how to convert HSV to RGB using C#, and after browsing the net i could only find C sample code to do it with, so i had to convert to C#. The code may be useful to someone out there (please let me know if it is):
/// <summary>
/// Convert HSV to RGB
/// h is from 0-360
/// s,v values are 0-1
/// r,g,b values are 0-255
/// Based upon http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space#HSV_Transformation_C_.2F_C.2B.2B_Code_2
/// </summary>
void HsvToRgb(double h, double S, double V, out int r, out int g, out int b)
{
  // ######################################################################
  // T. Nathan Mundhenk
  // mundhenk@usc.edu
  // C/C++ Macro HSV to RGB

  double H = h;
  while (H < 0) { H += 360; };
  while (H >= 360) { H -= 360; };
  double R, G, B;
  if (V <= 0)
    { R = G = B = 0; }
  else if (S <= 0)
  {
    R = G = B = V;
  }
  else
  {
    double hf = H / 60.0;
    int i = (int)Math.Floor(hf);
    double f = hf - i;
    double pv = V * (1 - S);
    double qv = V * (1 - S * f);
    double tv = V * (1 - S * (1 - f));
    switch (i)
    {

      // Red is the dominant color

      case 0:
        R = V;
        G = tv;
        B = pv;
        break;

      // Green is the dominant color

      case 1:
        R = qv;
        G = V;
        B = pv;
        break;
      case 2:
        R = pv;
        G = V;
        B = tv;
        break;

      // Blue is the dominant color

      case 3:
        R = pv;
        G = qv;
        B = V;
        break;
      case 4:
        R = tv;
        G = pv;
        B = V;
        break;

      // Red is the dominant color

      case 5:
        R = V;
        G = pv;
        B = qv;
        break;

      // Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.

      case 6:
        R = V;
        G = tv;
        B = pv;
        break;
      case -1:
        R = V;
        G = pv;
        B = qv;
        break;

      // The color is not defined, we should throw an error.

      default:
        //LFATAL("i Value error in Pixel conversion, Value is %d", i);
        R = G = B = V; // Just pretend its black/white
        break;
    }
  }
  r = Clamp((int)(R * 255.0));
  g = Clamp((int)(G * 255.0));
  b = Clamp((int)(B * 255.0));
}

/// <summary>
/// Clamp a value to 0-255
/// </summary>
int Clamp(int i)
{
  if (i < 0) return 0;
  if (i > 255) return 255;
  return i;
}
And here's how you'd use it:
int r,g,b;
HsvToRgb(110, 1, 1, out r, out g, out b);

Opening a TCP connection in C# with a custom timeout

The TcpClient class in C# is great for opening a TCP connection, I must say that it's one of the nicest TCP libraries i've used. You just have to watch out for the occasional bug and you'll be right.

One limitation is a frustration though: the inability to set a timeout when opening a connection to a remote server. The default timeout is 60 seconds, which is quite a while to have the user strumming their fingers waiting for things to happen.

My solution to this is to spawn a thread which opens the TCP connection, while the original thread waits up to a user-specified timeout for it to connect. If it hasn't connected by then, it kills the thread and gives up. Notice the use of the thread's Join function which allows the original thread to stop waiting if the connection is quicker than the timeout. Without further ado, the TcpClientWithTimeout.cs class:

using System;
using System.Net.Sockets;
using System.Threading;

/// <summary>
/// TcpClientWithTimeout is used to open a TcpClient connection, with a 
/// user definable connection timeout in milliseconds (1000=1second)
/// Use it like this:
/// TcpClient connection = new TcpClientWithTimeout('127.0.0.1',80,1000).Connect();
/// </summary>
public class TcpClientWithTimeout
{
  protected string _hostname;
  protected int _port;
  protected int _timeout_milliseconds;
  protected TcpClient connection;
  protected bool connected;
  protected Exception exception;

  public TcpClientWithTimeout(string hostname,int port,int timeout_milliseconds)
  {
    _hostname = hostname;
    _port = port;
    _timeout_milliseconds = timeout_milliseconds;
  }
  public TcpClient Connect()
  {
    // kick off the thread that tries to connect
    connected = false;
    exception = null;
    Thread thread = new Thread(new ThreadStart(BeginConnect));
    thread.IsBackground = true; // So that a failed connection attempt 
    // wont prevent the process from terminating while it does the long timeout
    thread.Start();

    // wait for either the timeout or the thread to finish
    thread.Join(_timeout_milliseconds);

    if (connected == true) 
    {
      // it succeeded, so return the connection
      thread.Abort();
      return connection;
    }
    if (exception != null) 
    {
      // it crashed, so return the exception to the caller
      thread.Abort();
      throw exception;
    }
    else
    {
      // if it gets here, it timed out, so abort the thread and throw an exception
      thread.Abort();
      string message = string.Format("TcpClient connection to {0}:{1} timed out",
        _hostname, _port);
      throw new TimeoutException(message);
    }
  }
  protected void BeginConnect()
  {
    try
    {
      connection = new TcpClient(_hostname, _port);
      // record that it succeeded, for the main thread to return to the caller
      connected = true;
    }
    catch (Exception ex)
    {
      // record the exception for the main thread to re-throw back to the calling code
      exception = ex; 
    }
  }
}

And here's a little example of how to use this to open a connection, send 10 bytes, and receive 10 bytes:

// connect with a 5 second timeout on the connection
TcpClient connection = new TcpClientWithTimeout("www.google.com", 80, 5000).Connect();
NetworkStream stream = connection.GetStream();

// Send 10 bytes
byte[] to_send = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa};
stream.Write(to_send, 0, to_send.Length); 

// Receive 10 bytes
byte[] readbuf = new byte[10]; // you must allocate space first
stream.ReadTimeout = 10000; // 10 second timeout on the read
stream.Read(readbuf, 0, 10); // read

// Disconnect nicely
stream.Close(); // workaround for a .net bug: http://support.microsoft.com/kb/821625
connection.Close();

Cheers all, please let me know if this is useful or if you have any constructive criticism.