Skip to content

Commit

Permalink
Use solr-spatial-light instead of LocalSolr
Browse files Browse the repository at this point in the history
After some incidental changes to the Solr schema during testing,
discovered that LocalSolr searches weren't actually using the dismax
query parser, even when requested. So, wrote a lightweight replacement
called solr-spatial-light that exposes distance filtering and sorting
using lucene-spatial without getting in the way of Solr's other
operations. Since that seems to be working fine, integrating it into
Sunspot.
  • Loading branch information
Mat Brown committed Dec 31, 2009
1 parent 5a5b104 commit 2a92f1d
Show file tree
Hide file tree
Showing 14 changed files with 65 additions and 561 deletions.
14 changes: 11 additions & 3 deletions lib/sunspot/dsl/query.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -152,10 +152,18 @@ def adjust_solr_params( &block )
# Scope the search by geographical distance from a given point. # Scope the search by geographical distance from a given point.
# +coordinates+ should either respond to #first and #last (e.g. a # +coordinates+ should either respond to #first and #last (e.g. a
# two-element array), or to #lat and one of #lng, #lon, or #long. # two-element array), or to #lat and one of #lng, #lon, or #long.
# +miles+ is the radius around the point for which to return documents. # +options+ should be one or both of the following:
# #
def near(coordinates, miles) # :distance:: The maximum distance in miles from which results can come
@query.add_location_restriction(coordinates, miles) # :sort::
# Whether to sort by distance from these coordinates. If other sorts are
# specified, they take precedence over distance sort.
#
def near(coordinates, options)
if options.respond_to?(:to_f)
options = { :distance => options }
end
@query.add_location_restriction(coordinates, options)
end end
end end
end end
Expand Down
2 changes: 1 addition & 1 deletion lib/sunspot/field_factory.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def populate_document(document, model)
if coordinates = @data_extractor.value_for(model) if coordinates = @data_extractor.value_for(model)
coordinates = Util::Coordinates.new(coordinates) coordinates = Util::Coordinates.new(coordinates)
document << Solr::Field.new(:lat => coordinates.lat) document << Solr::Field.new(:lat => coordinates.lat)
document << Solr::Field.new(:long => coordinates.lng) document << Solr::Field.new(:lng => coordinates.lng)
end end
end end
end end
Expand Down
21 changes: 10 additions & 11 deletions lib/sunspot/query/local.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ module Query
# generates the appropriate parameters. # generates the appropriate parameters.
# #
class Local #:nodoc: class Local #:nodoc:
def initialize(coordinates, radius) def initialize(coordinates, options)
if radius < 1 @coordinates, @options = Util::Coordinates.new(coordinates), options
raise ArgumentError, "LocalSolr does not seem to support a radius of less than 1 mile."
end
@coordinates, @radius = Util::Coordinates.new(coordinates), radius
end end


def to_params def to_params
{ local_params = [
:qt => 'geo', [:radius, @options[:distance]],
:lat => @coordinates.lat, [:sort, @options[:sort]]
:long => @coordinates.lng, ].map do |key,value|
:radius => @radius "#{key}=#{value}" if value
} end.compact.join(" ") #TODO Centralized local param builder
query = "{!#{local_params}}#{@coordinates.lat},#{@coordinates.lng}"
{ :spatial => query }
end end
end end
end end
Expand Down
491 changes: 6 additions & 485 deletions solr/solr/conf/schema.xml

Large diffs are not rendered by default.

34 changes: 7 additions & 27 deletions solr/solr/conf/solrconfig.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@
will be used. will be used.
--> -->
<requestHandler name="standard" class="solr.SearchHandler" default="true"> <requestHandler name="standard" class="solr.SearchHandler" default="true">
<arr name="last-components">
<str>spatial</str>
</arr>
<!-- default values for query parameters --> <!-- default values for query parameters -->
<lst name="defaults"> <lst name="defaults">
<str name="echoParams">explicit</str> <str name="echoParams">explicit</str>
Expand Down Expand Up @@ -643,6 +646,10 @@
</arr> </arr>
--> -->


<!-- solr-spatial-light search component -->

<searchComponent name="spatial" class="me.outofti.solrspatiallight.SpatialQueryComponent" />

<!-- The spell check component can return a list of alternative spelling <!-- The spell check component can return a list of alternative spelling
suggestions. --> suggestions. -->
<searchComponent name="spellcheck" class="solr.SpellCheckComponent"> <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
Expand Down Expand Up @@ -1029,31 +1036,4 @@
<healthcheck type="file">server-enabled</healthcheck> <healthcheck type="file">server-enabled</healthcheck>
--> -->
</admin> </admin>

<!-- configuration for LocalSolr -->
<updateRequestProcessorChain>
<processor class='com.pjaol.search.solr.update.LocalUpdateProcessorFactory'>
<str name='latField'>lat</str>
<str name='lngField'>long</str>
<int name='startTier'>9</int>
<int name='endTier'>16</int>
</processor>
<processor class='solr.RunUpdateProcessorFactory'></processor>
<processor class='solr.LogUpdateProcessorFactory'></processor>
</updateRequestProcessorChain>
<searchComponent class='com.pjaol.search.solr.component.LocalSolrQueryComponent' name='localsolr'>
<str name='latField'>lat</str>
<str name='lngField'>long</str>
</searchComponent>
<requestHandler class='org.apache.solr.handler.component.SearchHandler' name='geo'>
<str name="defType">lucene</str>
<arr name='components'>
<str>localsolr</str>
<str>facet</str>
<str>mlt</str>
<str>highlight</str>
<str>debug</str>
</arr>
</requestHandler>

</config> </config>
Binary file removed solr/solr/lib/geoapi-nogenerics-2.1-M2.jar
Binary file not shown.
Binary file removed solr/solr/lib/gt2-referencing-2.3.1.jar
Binary file not shown.
Binary file removed solr/solr/lib/jsr108-0.01.jar
Binary file not shown.
Binary file removed solr/solr/lib/localsolr.jar
Binary file not shown.
Binary file added solr/solr/lib/solr-spatial-light-0.0.1.jar
Binary file not shown.
6 changes: 3 additions & 3 deletions spec/api/indexer/attributes_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@


it 'should index latitude and longitude as a pair' do it 'should index latitude and longitude as a pair' do
session.index(post(:coordinates => [40.7, -73.5])) session.index(post(:coordinates => [40.7, -73.5]))
connection.should have_add_with(:lat => 40.7, :long => -73.5) connection.should have_add_with(:lat => 40.7, :lng => -73.5)
end end


[ [
Expand All @@ -83,13 +83,13 @@
session.index(post( session.index(post(
:coordinates => OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5) :coordinates => OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5)
)) ))
connection.should have_add_with(:lat => 40.7, :long => -73.5) connection.should have_add_with(:lat => 40.7, :lng => -73.5)
end end
end end


it 'should index latitude and longitude from a block' do it 'should index latitude and longitude from a block' do
session.index(Photo.new(:lat => 30, :lng => -60)) session.index(Photo.new(:lat => 30, :lng => -60))
connection.should have_add_with(:lat => 30.0, :long => -60.0) connection.should have_add_with(:lat => 30.0, :lng => -60.0)
end end


it 'should correctly index an attribute field with block access' do it 'should correctly index an attribute field with block access' do
Expand Down
30 changes: 11 additions & 19 deletions spec/api/query/local_spec.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,25 +1,25 @@
require File.join(File.dirname(__FILE__), 'spec_helper') require File.join(File.dirname(__FILE__), 'spec_helper')


describe 'local query' do describe 'local query' do
it 'sets query type to geo when geo search performed' do it 'sends lat and lng, and distance when geo search is performed' do
session.search Post do session.search Post do
near [40.7, -73.5], 5 near [40.7, -73.5], :distance => 5
end end
connection.should have_last_search_with(:qt => 'geo') connection.should have_last_search_with(:spatial => "{!radius=5}40.7,-73.5")
end end


it 'sets lat and lng when geo search is performed' do it 'sets lat, lng, and sort flag when sorted geo search is performed' do
session.search Post do session.search Post do
near [40.7, -73.5], 5 near [40.7, -73.5], :sort => true
end end
connection.should have_last_search_with(:lat => 40.7, :long => -73.5) connection.should have_last_search_with(:spatial => "{!sort=true}40.7,-73.5")
end end


it 'sets radius when geo search is performed' do it 'sets radius and sort when both are specified' do
session.search Post do session.search Post do
near [40.7, -73.5], 5 near [40.7, -73.5], :distance => 5, :sort => true
end end
connection.should have_last_search_with(:radius => 5) connection.should have_last_search_with(:spatial => "{!radius=5 sort=true}40.7,-73.5")
end end


[ [
Expand All @@ -30,17 +30,9 @@
].each do |lat_attr, lng_attr| ].each do |lat_attr, lng_attr|
it "sets coordinates using #{lat_attr.inspect}, #{lng_attr.inspect}" do it "sets coordinates using #{lat_attr.inspect}, #{lng_attr.inspect}" do
session.search Post do session.search Post do
near OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5), 5 near OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5), :distance => 5
end end
connection.should have_last_search_with(:lat => 40.7, :long => -73.5) connection.should have_last_search_with(:spatial => "{!radius=5}40.7,-73.5")
end end
end end

it 'raises ArgumentError if radius is less than 1' do
lambda do
session.search Post do
near [40, -70], 0.5
end
end.should raise_error(ArgumentError)
end
end end
6 changes: 0 additions & 6 deletions spec/api/query/ordering_pagination_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@
connection.should have_last_search_with(:sort => 'score desc') connection.should have_last_search_with(:sort => 'score desc')
end end


it 'orders by geo distance' do
session.search Post do
order_by :distance, :asc
end
end

it 'throws an ArgumentError if a bogus order direction is given' do it 'throws an ArgumentError if a bogus order direction is given' do
lambda do lambda do
session.search Post do session.search Post do
Expand Down
22 changes: 16 additions & 6 deletions spec/integration/local_search_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -17,38 +17,48 @@
end end


it 'should find all the posts within a given radius' do it 'should find all the posts within a given radius' do
search = Sunspot.search(Post) { |query| query.near(ORIGIN, 20) } search = Sunspot.search(Post) { |query| query.near(ORIGIN, :distance => 20) }
search.results.to_set.should == @posts[0..2].to_set search.results.to_set.should == @posts[0..2].to_set
end end


it 'should perform a radial search with fulltext matching' do it 'should perform a radial search with fulltext matching' do
search = Sunspot.search(Post) do |query| search = Sunspot.search(Post) do |query|
query.keywords 'teacup' query.keywords 'teacup'
query.near(ORIGIN, 20) query.near(ORIGIN, :distance => 20)
end end
search.results.should == [@posts[1]] search.results.should == [@posts[1]]
end end


it 'should use dismax for fulltext matching in local search' do
lambda do
search = Sunspot.new_search(Post)
search.build do |query|
query.keywords 'teacup['
query.near(ORIGIN, :distance => 20)
end
search.execute
end.should_not raise_error
end

it 'should perform a radial search with attribute scoping' do it 'should perform a radial search with attribute scoping' do
search = Sunspot.search(Post) do |query| search = Sunspot.search(Post) do |query|
query.near(ORIGIN,20) query.near(ORIGIN, :distance => 20)
query.with(:title, 'teacup') query.with(:title, 'teacup')
end end
search.results.should == [@posts[1]] search.results.should == [@posts[1]]
end end


it 'should order by arbitrary field' do it 'should order by arbitrary field' do
search = Sunspot.search(Post) do |query| search = Sunspot.search(Post) do |query|
query.near(ORIGIN, 20) query.near(ORIGIN, :distance => 20)
query.order_by(:blog_id) query.order_by(:blog_id)
end end
search.results.should == @posts[0..2].reverse search.results.should == @posts[0..2].reverse
end end


it 'should order by geo distance' do it 'should order by geo distance' do
search = Sunspot.search(Post) do |query| search = Sunspot.search(Post) do |query|
query.near(ORIGIN, 20) query.near(ORIGIN, :distance => 20, :sort => true)
query.order_by(:distance)
end end
search.results.should == @posts[0..2] search.results.should == @posts[0..2]
end end
Expand Down

0 comments on commit 2a92f1d

Please sign in to comment.