Changeset 401

Show
Ignore:
Timestamp:
06/13/04 15:07:18 (5 years ago)
Author:
kake
Message:

Added ability to search by distance from an arbitrary point specified in lat/long.

Location:
trunk
Files:
1 added
9 modified

Legend:

Unmodified
Added
Removed
  • trunk/Build.PL

    r395 r401  
    229229        'CGI::Wiki::Plugin::Diff'         => 0, 
    230230        'CGI::Wiki::Plugin::GeoCache'     => 0, 
    231         'CGI::Wiki::Plugin::Locator::UK'  => '0.06', 
     231        'CGI::Wiki::Plugin::Locator::UK'  => '0.09', 
    232232        'CGI::Wiki::Plugin::RSS::ModWiki' => '0.02', 
    233233        'CGI::Wiki::Plugin::RSS::Reader'  => '1.3',  # earlier versions don't support RSS 2.0 
  • trunk/Changes

    r390 r401  
    66          links being displayed for nodes with no address data (spotted 
    77          by Steve Jolly). 
    8         Remove inline style from recent_changes.tt. You will need to add 
     8        Removed inline style from recent_changes.tt. You will need to add 
    99          the styles table#recentchanges, td.recentchanges_meta, 
    1010          td.recentchanges_user, td.recentchanges_node_name and 
    1111          td.recentchanges_comment to your stylesheets. 
     12        Added searching by distance from an arbitrary point (click on 
     13          Advanced Search). 
     14        Internal rejigging - extracted some methods from wiki.cgi to 
     15          OpenGuides.pm. 
    1216 
    13170.32    7 June 2004 
  • trunk/MANIFEST

    r392 r401  
    5656t/31_supersearch.t 
    5757t/32_supersearch_simple_metadata.t 
     58t/33_supersearch_advanced_search.t 
    5859t/41_deletion.t 
    5960t/51_display_node.t 
  • trunk/PREREQUISITES

    r361 r401  
    1111CGI::Wiki::Plugin::Diff 
    1212CGI::Wiki::Plugin::GeoCache 
    13 CGI::Wiki::Plugin::Locator::UK (version 0.06 or later) 
     13CGI::Wiki::Plugin::Locator::UK (version 0.09 or later) 
    1414CGI::Wiki::Plugin::RSS::ModWiki (version 0.02 or later) 
    1515CGI::Wiki::Plugin::RSS::Reader (version 1.3 or later) 
  • trunk/lib/OpenGuides.pm

    r400 r401  
    1313use vars qw( $VERSION ); 
    1414 
    15 $VERSION = '0.33_05'; 
     15$VERSION = '0.33_06'; 
    1616 
    1717=head1 NAME 
     
    303303    my $metres = $args{metres}; 
    304304    my $formatter = $self->wiki->formatter; 
    305  
    306305    my @finds = $self->locator->find_within_distance( 
    307306                                                      node   => $node, 
     
    333332                                   id       => "index", # KLUDGE 
    334333                                   template => "site_index.tt", 
    335                                    vars     => \%tt_vars, 
     334                                   tt_vars  => \%tt_vars, 
    336335                                 ); 
    337336} 
  • trunk/lib/OpenGuides/SuperSearch.pm

    r341 r401  
    11package OpenGuides::SuperSearch; 
    22use strict; 
    3 our $VERSION = '0.04'; 
     3our $VERSION = '0.05'; 
    44 
    55use CGI qw( :standard ); 
     6use CGI::Wiki::Plugin::Locator::UK; 
    67use File::Spec::Functions qw(:ALL); 
    78use OpenGuides::Template; 
     
    5354    $self->{css}      = $config->{_}{stylesheet_url}; 
    5455    $self->{head}     = $config->{_}{site_name} . " Search"; 
     56 
     57    my $locator = CGI::Wiki::Plugin::Locator::UK->new; 
     58    $wiki->register_plugin( plugin => $locator ); 
     59    $self->{locator} = $locator; 
    5560 
    5661    return $self; 
     
    9297    $tt_vars{ss_info_url} = 'http://london.openguides.org/?Search_Script'; 
    9398 
     99    # Strip out any non-digits from dist; lat and long also allowed '-' and '.' 
     100    $vars{lat} =~ s/[^-\.0-9]//g; 
     101    $vars{long} =~ s/[^-\.0-9]//g; 
     102    $vars{distance_in_metres} =~ s/[^0-9]//g; 
     103 
    94104    # Do we have an existing search? if so, do it. 
    95     if ( $vars{search} ) { 
     105    my $doing_search; 
     106    if ( $vars{search} 
     107         or ( defined $vars{lat} 
     108              && defined $vars{long} 
     109              && defined $vars{distance_in_metres} ) 
     110    ) { 
     111        $doing_search = 1; 
    96112        $tt_vars{search_terms} = $vars{search}; 
     113        $tt_vars{lat} = $vars{lat}; 
     114        $tt_vars{long} = $vars{long}; 
     115        $tt_vars{dist} = $vars{distance_in_metres}; 
    97116        $self->_perform_search( vars => \%vars ); 
    98117    } 
     
    100119    if ( $self->{error} ) { 
    101120        $tt_vars{error_message} = $self->{error}; 
    102     } elsif ( $vars{search} ) { 
    103         my %results = %{ $self->{results} || {} }; 
    104         my $numres = scalar keys %results; 
     121    } elsif ( $doing_search ) { 
     122        my @results = values %{ $self->{results} || {} }; 
     123        my $numres = scalar @results; 
    105124 
    106125        # For 0 or many we display results, for 1 we redirect to that page. 
    107126        if ( $numres == 1 && !$self->{return_tt_vars}) { 
    108             my ($node) = each %results; 
     127            my $node = $results[0]{name}; 
    109128            my $output = CGI::redirect( $self->{wikimain} . "?" 
    110129                                        . CGI::escape($node) ); 
     
    127146            } 
    128147 
    129             # Sort the results - first index of array in HoA is the score. 
    130             my @res_selected = sort 
    131                                { $results{$b}[0] <=> $results{$a}[0] } 
    132                                keys %results; 
     148            # Sort the results - by distance if we're searching on that 
     149            # or by score otherwise. 
     150            if ( $vars{distance_in_metres} ) { 
     151                @results = sort { $a->{distance} <=> $b->{distance} } @results; 
     152            } else { 
     153                @results = sort { $a->{score} <=> $b->{score} } @results; 
     154            } 
    133155 
    134156            # Now snip out just the ones for this page.  The -1 is 
     
    136158            my $from = $tt_vars{first_num} ? $tt_vars{first_num} - 1 : 0; 
    137159            my $to   = $tt_vars{last_num} - 1; # kludge to empty arr for no res 
    138             @res_selected = @res_selected[ $from .. $to ]; 
    139  
    140             my @result_urls; 
    141             foreach ( @res_selected ) { 
    142                 my @summary = grep { defined $_ } @{$results{$_}}[1 .. 6]; 
    143                 push @result_urls, 
    144                   { 
    145                     name    => $_, 
    146                     url     => $self->{wikimain} . "?" . CGI::escape($_), 
    147                     summary => join "\n", @summary, 
    148                   }; 
    149             } 
    150             $tt_vars{results} = \@result_urls; 
     160            @results = @results[ $from .. $to ]; 
     161 
     162            $tt_vars{results} = \@results; 
    151163        } 
    152164    } 
     
    262274    my ($self, %args) = @_; 
    263275    my %vars = %{ $args{vars} || {} }; 
     276 
    264277    my $srh = $vars{search}; 
    265278 
    266     # Check for only valid characters in tainted search param 
    267     # (quoted literals are OK, as they are escaped) 
    268     if ( $srh !~ /^("[^"]*"|[\w \-',()!*%\[\]])+$/i) { #" 
    269         $self->{error} = "Search expression contains invalid character(s)"; 
    270         return; 
    271     } 
    272  
    273     $self->_build_parser && exists($self->{error}) && return; 
    274     $self->_apply_parser($srh); 
     279    # Perform text search if search terms entered, otherwise collect up 
     280    # all nodes to check distance. 
     281    if ( $srh ) { 
     282        # Check for only valid characters in tainted search param 
     283        # (quoted literals are OK, as they are escaped) 
     284        if ( $srh !~ /^("[^"]*"|[\w \-',()!*%\[\]])+$/i) { #" 
     285            $self->{error} = "Search expression contains invalid character(s)"; 
     286            return; 
     287        } 
     288        $self->_build_parser && exists($self->{error}) && return; 
     289        $self->_apply_parser($srh); 
     290    } else { 
     291        my @all_nodes = $self->{wiki}->list_all_nodes; 
     292        my $formatter = $self->{wiki}->formatter; 
     293        my %results = map { 
     294              my $name = $formatter->node_name_to_node_param( $_ ); 
     295              $name => { 
     296                         name    => $name, 
     297                         score   => 0, 
     298                         summary => "FIXME", 
     299                       } 
     300                          } @all_nodes; 
     301        $self->{results} = \%results; 
     302    } 
     303 
     304    # Now filter by distance if required. 
     305    my ($lat, $long, $dist) = @vars{ qw( lat long distance_in_metres ) }; 
     306    if ( defined $lat && defined $long && $dist ) { 
     307        my %results = %{ $self->{results} || {} }; 
     308        my @close = $self->{locator}->find_within_distance( 
     309                                                            lat    => $lat, 
     310                                                            long   => $long, 
     311                                                            metres => $dist, 
     312                                                          ); 
     313        my %close_hash = map { $_ => 1 } @close; 
     314        my @nodes = keys %results; 
     315        foreach my $node ( @nodes ) { 
     316            my $unmunged = $node; # KLUDGE 
     317            $unmunged =~ s/_/ /g; 
     318            if ( exists $close_hash{$unmunged} ) { 
     319                my $distance = $self->{locator}->distance( 
     320                                                 from_lat  => $lat, 
     321                                                 from_long => $long, 
     322                                                 to_node   => $unmunged, 
     323                                                 unit      => "metres" 
     324                                                         ); 
     325                $results{$node}{distance} = $distance;                 
     326            } else { 
     327                delete $results{$node}; 
     328            } 
     329        } 
     330        $self->{results} = \%results; 
     331    }       
    275332} 
    276333 
     
    341398    my $op = shift @tree_arr; 
    342399    my $meth = 'matched_'.$op; 
    343  
    344400    return $self->can($meth) ? $self->$meth(@tree_arr) : undef; 
    345401} 
     
    382438 
    383439sub matched_AND { 
    384     my $self = shift; 
    385  
    386     # Do all the searches 
    387     my @comby_res = map { 
    388                           my %match_hash = $self->_matched_items(tree => $_); 
    389                           \%match_hash 
    390                         } @_; 
    391  
    392     # Use the first one's results as a basis for the output hash 
    393     my @out = keys %{$comby_res[0]}; 
    394     my %out; 
    395  
    396     # Zap out any entries which do not appear in one of the other searches. 
    397     PAGE: 
    398     for my $page (@out) { 
    399         for (@comby_res[1..$#comby_res]) { 
    400             (delete $out{$page}),next PAGE if !exists $_->{$page}; 
    401         } 
    402          
    403         $out{$page} = $self->intersperse($page, @comby_res); 
    404     } 
    405      
    406     return %out; 
     440    my ($self, @subsearches) = @_; 
     441 
     442    # Do the first subsearch. 
     443    my %results = $self->_matched_items( tree => $subsearches[0] ); 
     444 
     445    # Now do the rest one at a time and remove from the results anything 
     446    # that doesn't come up in each subsearch.  Results that survive will 
     447    # have a score that's the sum of their score in each subsearch. 
     448    foreach my $tree ( @subsearches[ 1 .. $#subsearches ] ) { 
     449        my %subres = $self->_matched_items( tree => $tree ); 
     450        my @pages = keys %results; 
     451        foreach my $page ( @pages ) { 
     452            if ( exists $subres{$page} ) { 
     453                $results{$page}{score} += $subres{$page}; 
     454            } else { 
     455                delete $results{$page}; 
     456            } 
     457        } 
     458    } 
     459 
     460    return %results; 
    407461} 
    408462 
     
    420474 
    421475sub matched_OR { 
    422     my $self = shift; 
    423  
    424     # Do all the searches 
    425     my @list_res = map { 
    426                          my %match_hash = $self->_matched_items(tree => $_); 
    427                          \%match_hash 
    428                        } @_; 
    429  
    430     # Apply union of hashes, merging any duplicates. 
    431     my %union; 
    432     for (@list_res) { 
    433         while (my ($k,$v) = each %$_) { 
    434             $union{$k}++; 
     476    my ($self, @subsearches) = @_; 
     477 
     478    # Do all the searches.  Results will have a score that's the sum 
     479    # of their score in each subsearch. 
     480    my %results; 
     481    foreach my $tree ( @subsearches ) { 
     482        my %subres = $self->_matched_items( tree => $tree ); 
     483        foreach my $page ( keys %subres ) { 
     484            if ( $results{$page} ) { 
     485                $results{$page}{score} += $subres{$page}{score}; 
     486            } else { 
     487                $results{$page} = $subres{$page}; 
     488            } 
    435489        } 
    436490    } 
    437      
    438     my %out; 
    439      
    440     $out{$_} = $self->intersperse($_, @list_res) for keys %union; 
    441      
    442     return %out; 
    443 } 
    444  
     491    return %results; 
     492} 
    445493 
    446494=item B<NOT searches> 
     
    513561    # context either side. 
    514562    my $wexp = qr/\b.{0,60}\b$wmatch\b.{0,60}\b/is; 
    515     my %res; 
     563    my %results; 
    516564 
    517565    # Search every wiki page for matches 
     
    528576        $temp =~ s/_/ /g; 
    529577 
    530         # Compute score and prepend to array of matches 
    531         my $score = @out; 
     578        # Compute score and create summary. 
     579        my $score = scalar @out; 
    532580        $score +=10 if $temp =~ /$wexp/; 
    533         $res{$k} = unshift(@out,$score) && \@out if @out; 
    534     } 
    535      
    536     return %res; 
     581        $results{$k} = { 
     582                         score   => $score, 
     583                         summary => join( "", @out ), 
     584                         name    => $k, 
     585                       } 
     586          if $score; 
     587    } 
     588     
     589    return %results; 
    537590} 
    538591 
  • trunk/templates/navbar.tt

    r397 r401  
    3838      </form> 
    3939    </div> 
     40    <div class="navbar_item"> 
     41      <a href="supersearch.cgi">Advanced Search</a> 
     42    </div> 
    4043  </div> 
    4144 
  • trunk/templates/supersearch.tt

    r343 r401  
    2424 
    2525  <form method="get" action="supersearch.cgi"> 
    26     <label for="search_upper">Search:</label>  
     26    <label for="search_upper">Search text:</label>  
    2727    <input type="text" name="search" value="[% search_terms %]" size="50" maxlength="80" id="search_upper" value="Search text" onclick="this.value=''" /> 
     28    <br /> 
     29    Located within 
     30    <input type="text" name="distance_in_metres" value="[% dist %]" size="4" maxlength="4" /> 
     31    metres of 
     32    latitude <input type="text" name="lat" value="[% lat %]" size="10" maxlength="10" /> 
     33    longitude <input type="text" name="long" value="[% long %]" size="10" maxlength="10" /> 
     34    <br /> 
    2835    <input type="submit" name="go" class="form_button" value="Go" /> 
    2936  </form> 
     
    3845        [% FOREACH result = results %] 
    3946          <li> 
    40             <a href="[% result.url %]"><b>[% result.name.replace('_', ' ') %]</b></a> 
     47            <a href="[% full_cgi_url %]?[% result.name %]"><b>[% result.name.replace('_', ' ') %]</b></a> 
     48            [% IF result.distance %] 
     49              ([% result.distance %] metres away) 
     50            [% END %] 
    4151            <br /> 
    4252            [% result.summary %] 
  • trunk/wiki.cgi

    r393 r401  
    55 
    66use vars qw( $VERSION ); 
    7 $VERSION = '0.33_05'; 
     7$VERSION = '0.33_06'; 
    88 
    99use CGI qw/:standard/;