Changeset 403


Ignore:
Timestamp:
Dec 3, 2006, 5:29:41 PM (13 years ago)
Author:
Nick Burch
Message:

Support nearest postcodes, and nearby areas

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/npemap.org.uk/cgi/geocoder.fcgi

    r402 r403  
    22# Looks up a location for a given postcode, returning the closest we have,
    33#  and an idea of how accurate a match we found.
    4 # Can return plain text, or XML
     4# Looks up the nearest postcode for a given location, optionally returning
     5#  the several nearest (rather than just the nearest)
     6# Looks up the nearest postcode area for a given location, optionally
     7#  returning the several nearest (rather than just the nearest)
     8#
     9# Can return plain text, JS, JS callback, or XML
    510#
    611# TODO: Make me more generic, so I can work on the portal site
    712#
    8 # Copyright (c) 2006 Dominic Hargreaves
     13# Copyright (c) 2006 Dominic Hargreaves, Nick Burch and David Sheldon
    914# See accompanying file "LICENCE" for licence details
    1015
     
    7176        }
    7277
     78        # How many results did they want?
     79        # (Not all searches support more than one result)
     80        my $limit_results = 1;
     81        if(defined $cgi->param("results")) {
     82                $limit_results = $cgi->param("results");
     83                unless($limit_results =~ /^\d+$/) {
     84                        print_err ("Invalid maximum number of results '$limit_results' supplied");
     85                        next REQUEST;
     86                }
     87        }
     88
     89
    7390        # Did they want to do postcode -> location, or location -> postcode?
     91        my %request;
     92        my @results;
    7493
    7594        # What postcode were they asking about?
    76         my $postcode;
    77         my $outward;
    78         my $inward;
    7995        if(defined $cgi->param("postcode")) {
    80                 $postcode = uc($cgi->param("postcode"));
    81                 ($outward,$inward) = ($postcode =~ /^([A-Z]+\d+[A-Z]?)\s*(\d[A-Z][A-Z])?$/);
    82                 unless($outward) {
    83                         print_err ("Invalid postcode '$postcode' supplied");
     96                $limit_results = 1;
     97                $request{type} = "location";
     98
     99                $request{postcode} = uc($cgi->param("postcode"));
     100                ($request{outward},$request{inward}) =
     101                                ($request{postcode} =~ /^([A-Z]+\d+[A-Z]?)\s*(\d[A-Z][A-Z])?$/);
     102
     103                unless($request{outward}) {
     104                        print_err ("Invalid postcode '".$request{postcode}."' supplied");
    84105                        next REQUEST;
    85106                }
     107        } elsif(defined $cgi->param("easting") && defined $cgi->param("northing")) {
     108                $request{type} = "postcode";
     109                $request{easting} = $cgi->param("easting");
     110                $request{northing} = $cgi->param("northing");
     111
     112                if( (defined $cgi->param("area") && $cgi->param("area")) ||
     113                    (defined $cgi->param("areas") && $cgi->param("areas")) ) {
     114                        $request{type} = "area";
     115                }
     116
     117                unless($request{easting} =~ /^\d+$/) {
     118                        print_err ("Invalid easting '".$request{easting}."' supplied");
     119                        next REQUEST;
     120                }
     121                unless($request{northing} =~ /^\d+$/) {
     122                        print_err ("Invalid northing '".$request{northing}."' supplied");
     123                        next REQUEST;
     124                }
    86125        } else {
    87                 print_err ("Required parameter 'postcode' not supplied");
     126                print_err ("You must either supply the parameter 'postcode', or the two parameters 'easting' and 'northing'");
    88127                next REQUEST;
    89128        }
    90129
    91130
    92         my ($easting,$northing,$matched,$pc);
    93 
    94         # Work out until we find a match
    95         ($easting,$northing,$matched,$pc) = execute($full_matcher,$outward,$inward);
    96         unless($matched) {
    97                 ($easting,$northing,$matched,$pc) =
    98                                 execute($outer1_matcher,$outward,substr($inward,0,1));
    99         }
    100         unless($matched) {
    101                 ($easting,$northing,$matched,$pc) =
    102                                 execute($outer_matcher,$outward);
    103         }
    104         unless($matched) {
    105                 my ($mpart) = ($outward =~ /^([A-Z]+)\d/);
    106                 my $mlen = length($mpart);
    107 
    108                 ($easting,$northing,$matched,$pc) =
    109                                 execute($area_matcher,$mpart,$mlen,$mpart,$mlen);
    110 
     131        # Find what the user was after
     132        if($request{type} eq "area") {
     133                @results = execute_lt($pc_area_matcher, $request{easting}, $request{northing}, $limit_results);
     134        }
     135        elsif($request{type} eq "postcode") {
     136                @results = execute_lt($postcode_matcher, $request{easting}, $request{northing}, $limit_results);
     137        }
     138        else {
     139                my ($easting,$northing,$matched,$pc);
     140                my ($outward,$inward) = ($request{outward},$request{inward});
     141
     142                # Work out until we find a match
     143                ($easting,$northing,$matched,$pc) = execute_ptl($full_matcher,$outward,$inward);
    111144                unless($matched) {
    112                         print_err "Postcode area '$mpart' not found, postcode probably invalid";
    113                         next REQUEST;
    114                 }
    115         }
     145                        ($easting,$northing,$matched,$pc) =
     146                                        execute_ptl($outer1_matcher,$outward,substr($inward,0,1));
     147                }
     148                unless($matched) {
     149                        ($easting,$northing,$matched,$pc) =
     150                                        execute_ptl($outer_matcher,$outward);
     151                }
     152                unless($matched) {
     153                        my ($mpart) = ($outward =~ /^([A-Z]+)\d/);
     154                        my $mlen = length($mpart);
     155
     156                        ($easting,$northing,$matched,$pc) =
     157                                        execute_ptl($area_matcher,$mpart,$mlen,$mpart,$mlen);
     158
     159                        unless($matched) {
     160                                print_err "Postcode area '$mpart' not found, postcode probably invalid";
     161                                next REQUEST;
     162                        }
     163                }
     164
     165                # Save the results
     166                my %res;
     167                $res{easting} = $easting;
     168                $res{northing} = $northing;
     169                $res{matched} = $matched;
     170                $res{postcode} = $pc;
     171                push @results, \%res;
     172        }
     173
     174
     175        # Format the easting and northing, if we have them
     176        foreach my $res (@results) {
     177                if($res->{easting}) {
     178                        $res->{ieasting} = int($res->{easting});
     179                }
     180                if($res->{northing}) {
     181                        $res->{inorthing} = int($res->{northing});
     182                }
     183        }
     184
    116185
    117186        # Render
     
    121190                print "<geocoder>\n";
    122191                print "  <request>\n";
    123                 print "     <postcode>$postcode</postcode>\n";
    124                 print "     <outward>$outward</outward>\n";
    125                 print "     <inward>$inward</inward>\n";
     192                if($request{postcode}) {
     193                        print "     <postcode>$request{postcode}</postcode>\n";
     194                        print "     <outward>$request{outward}</outward>\n";
     195                        print "     <inward>$request{inward}</inward>\n";
     196                } else {
     197                        print "     <easting>$request{easting}</easting>\n";
     198                        print "     <northing>$request{northing}</northing>\n";
     199                }
    126200                print "  </request>\n\n";
    127                 print "  <easting>".int($easting)."</easting>\n";
    128                 print "  <northing>".int($northing)."</northing>\n";
    129                 print "  <postcode>$pc</postcode>\n";
     201
     202                foreach my $res (@results) {
     203                        if(scalar @results > 1) {
     204                                print "  <result>\n";
     205                        }
     206                        print "    <easting>".$res->{ieasting}."</easting>\n";
     207                        print "    <northing>".$res->{inorthing}."</northing>\n";
     208                        print "    <postcode>$res->{postcode}</postcode>\n";
     209                        if(scalar @results > 1) {
     210                                print "  </result>\n";
     211                        }
     212                }
     213
    130214                print "</geocoder>\n";
    131215        } elsif ($output eq "js") {
    132216                print header("text/javascript");
    133         my ($e, $n) = (int($easting), int($northing));
    134           if(defined $cgi->param("callback")) {
    135           print $cgi->param("callback") . "(";
    136       }
    137       print "{ pc: \"$pc\", e: $e, n: $n }";
    138           if(defined $cgi->param("callback")) {
    139           print ");";
    140       }
    141       print "\n";
    142        
     217
     218                if(defined $cgi->param("callback")) {
     219                        print $cgi->param("callback") . "(";
     220                }
     221                # TODO:
     222                #  make me play nicely if there are several results
     223                #  (need to do some array magic probably)
     224                foreach my $res (@results) {
     225                        print "{ pc: \"$res->{postcode}\", e: $res->{ieasting}, n: $res->{inorthing} }";
     226                }
     227                if(defined $cgi->param("callback")) {
     228                        print ");";
     229                }
     230                print "\n";
    143231    } else {
    144232                print header("text/plain");
    145233                print "# Easting,Northing,Matched Postcode\n";
    146                 print int($easting).",".int($northing).",'$pc'\n";
     234
     235                foreach my $res (@results) {
     236                        print $res->{ieasting}.",".$res->{inorthing}.",'".$res->{postcode}."'\n";
     237                }
    147238        }
    148239}
     
    151242$dbh->disconnect;
    152243
    153 # Run a matcher query
    154 sub execute {
     244# Run a postcode to location matcher
     245sub execute_ptl {
    155246        my $matcher = shift;
    156247        my @args = @_;
     
    165256        return ($easting,$northing,$matched,$pc);
    166257}
     258
     259# Run a location to postcode/area matcher
     260sub execute_lt {
     261        my $matcher = shift;
     262        my @args = @_;
     263
     264        my @results;
     265
     266        $matcher->execute(@args);
     267        while( my @row = $matcher->fetchrow_array ) {
     268                my %res = (
     269                        outward  => $row[0],
     270                        inward   => $row[1],
     271                        easting  => $row[2],
     272                        northing => $row[3]
     273                );
     274
     275                if($res{inward}) {
     276                        $res{postcode} = $res{outward}." ".$res{inward};
     277                } else {
     278                        $res{postcode} = $res{outward};
     279                }
     280
     281                push @results, \%res;
     282        }
     283
     284        return @results;
     285}
     286
    167287
    168288# Build our various matcher prepared statements
     
    217337        my $dbh = shift;
    218338
    219         my $sql = "SELECT outward, avg_e AS easting, avg_n AS northing ".
     339        my $sql = "SELECT outward, '' AS inward, avg_e AS easting, avg_n AS northing ".
    220340                  "FROM ( ".
    221341                  "   SELECT outward, ".
Note: See TracChangeset for help on using the changeset viewer.