Merge lp:~twstd-dev/ubuntu-geonames/search-by-coordinates into lp:ubuntu-geonames

Proposed by twstd
Status: Needs review
Proposed branch: lp:~twstd-dev/ubuntu-geonames/search-by-coordinates
Merge into: lp:ubuntu-geonames
Diff against target: 329 lines (+225/-48)
2 files modified
geoname-modpython.py (+204/-45)
sphinx.conf (+21/-3)
To merge this branch: bzr merge lp:~twstd-dev/ubuntu-geonames/search-by-coordinates
Reviewer Review Type Date Requested Status
Bartosz Kosiorek (community) Needs Information
Ubuntu Geonames Hackers Pending
Review via email: mp+232626@code.launchpad.net

Commit message

Add search by coordinates

Description of the change

Added functionality to search by coordinates.
Simply specify coordinates by adding GET parameters to the server url.
"lat" stands for latitude
"long" stands for longitude

Example: server:port/?lat=40.714270&long=-74.005970

To post a comment you must log in.
32. By twstd

Reduce timeout value

33. By twstd

Fix sphinx client isntance being shared between classes

34. By twstd

Increase connection timeout

35. By twstd

Add wildcard search

36. By twstd

Align attributes so indexer would not throw error

Revision history for this message
Bartosz Kosiorek (gang65) wrote :

Hello twstd.
Could you please merge with trunk your code?

review: Needs Information

Unmerged revisions

36. By twstd

Align attributes so indexer would not throw error

35. By twstd

Add wildcard search

34. By twstd

Increase connection timeout

33. By twstd

Fix sphinx client isntance being shared between classes

32. By twstd

Reduce timeout value

31. By twstd

Add search by coordinates

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'geoname-modpython.py'
--- geoname-modpython.py 2014-08-14 08:49:43 +0000
+++ geoname-modpython.py 2014-09-13 19:25:19 +0000
@@ -2,10 +2,12 @@
2from mod_python import apache2from mod_python import apache
3import sphinxapi3import sphinxapi
4import psycopg24import psycopg2
5from math import pi
6from math import radians
5try:7try:
6 from config import authstring8 from config import authstring
7except ImportError:9except ImportError:
8 authstring = 'dbname=geonames user=geouser password=geopw host=localhost'10 authstring = 'dbname=geonames user=geouser password=geopw host=localhost'
911
10statement = """12statement = """
11SELECT13SELECT
@@ -44,48 +46,205 @@
44jsonheader = '['46jsonheader = '['
45jsonfooter = ']'47jsonfooter = ']'
46jsonentry = '{"name" : "%s", "admin1" : "%s", "admin2" : "%s", "country" : "%s", ' \48jsonentry = '{"name" : "%s", "admin1" : "%s", "admin2" : "%s", "country" : "%s", ' \
47 '"longitude" : "%F", "latitude" : "%F" , '"timezone" : "%s" }'49 '"longitude" : "%F", "latitude" : "%F" , "timezone" : "%s" }'
50
51class RequestVariables:
52 """
53 A very simple wrapper to handle incoming GET parameters.
54 """
55 def __init__( self, request ):
56 self.__fields = util.FieldStorage( request )
57
58 def contains( self, variable_name ):
59 """
60 Checks if parsed field list contains given parameter.
61 """
62 return variable_name in self.__fields
63
64 def get( self, variable_name ):
65 """
66 Returns given variable value.
67 """
68 return self.__fields[ variable_name ] if self.contains( variable_name ) else ""
69
70class SphinxQuery:
71 """
72 Implements a parent class to hold a query to a sphinx engine.
73 """
74 __hostname = "localhost"
75 __port = 3312
76 query_keyword = ""
77 client = None
78 search_index = "geonames"
79
80 def __init__( self ):
81 self.client = sphinxapi.SphinxClient()
82 self.initialize_client()
83
84 def initialize_client( self ):
85 """
86 Prepares sphinx client.
87 """
88 self.client.SetServer( self.__hostname, self.__port )
89
90 def execute( self ):
91 try:
92 result = self.client.Query( self.query_keyword, self.search_index )
93 return result[ "matches" ] if "matches" in result else []
94 except:
95 return []
96
97class TextQuery( SphinxQuery ):
98 """
99 Implements a simple text based query.
100 """
101 def __init__( self, query ):
102 SphinxQuery.__init__( self )
103 self.client.SetSortMode( sphinxapi.SPH_SORT_ATTR_DESC, 'population' )
104 self.query_keyword = "%s*" % query
105
106
107 def execute( self ):
108 return ArrayQueryResult( SphinxQuery.execute( self ) )
109
110class CoordinatesBasedQuery( SphinxQuery ):
111 # default is not enough to search by coordinates
112 __connection_timeout = 10.00
113 # set to 5km
114 __radius = 5000.00
115
116 """
117 Implements a search by latitude and longitude coordinates.
118 """
119 def __init__( self, latitude, longitude ):
120 SphinxQuery.__init__( self )
121 self.search_index = "geonamescoordinates"
122 self.client.SetConnectTimeout( self.__connection_timeout )
123 self.client.SetLimits( 0, 1 )
124 self.client.SetGeoAnchor(
125 "latitude",
126 "longitude",
127 convert_to_radians( latitude ),
128 convert_to_radians( longitude ) )
129 self.client.SetFilterFloatRange( "@geodist", 0.0, self.__radius )
130 self.client.SetSortMode( sphinxapi.SPH_SORT_EXTENDED, "@geodist ASC" )
131
132
133 def execute( self ):
134 return SingleQueryResult( SphinxQuery.execute( self ) )
135
136class QueryResult:
137 __result = []
138
139 """
140 Implements a structure for query results.
141 """
142 def __init__( self, result ):
143 self.__result = result
144
145 def get( self ):
146 return self.__result
147
148class SingleQueryResult( QueryResult ):
149 """
150 Represents result containing only one item.
151 """
152 pass
153
154class ArrayQueryResult( QueryResult ):
155 """
156 Represents result containing a list of items.
157 """
158 pass
159
160
161def prepare_request( request ):
162 """
163 Sets required headers to the given request.
164 """
165 request.content_type = "application/json"
166 return request
167
168def convert_to_radians( degrees ):
169 parsed_value = 0.0
170
171 try:
172 parsed_value = float( degrees )
173 except:
174 pass
175
176 return radians( parsed_value )
177
178def parse_query_result( query_result ):
179 """
180 Returns json ready string formed from the given result.
181 """
182 result = query_result.get()
183
184 ret = []
185 if result:
186 connection = psycopg2.connect(authstring)
187 cursor = connection.cursor()
188 try:
189 # We need at least one value for the sql in operator
190 # and there are no locations with id 0
191 statement_ids = ['0']
192 altstatement_ids = ['0']
193 for x in result:
194 rawid = x['id']
195 idval = rawid / 10
196 idtype = rawid % 10
197 if idtype == 1:
198 statement_ids.append(str(idval))
199 else:
200 altstatement_ids.append(str(idval))
201
202 statement_ids_str = '(' + ','.join(statement_ids) + ')'
203 altstatement_ids_str = '(' + ','.join(altstatement_ids) + ')'
204 fullstatement = statement % (statement_ids_str,
205 altstatement_ids_str)
206 cursor.execute(fullstatement)
207 records = cursor.fetchall()
208 for record in records:
209 record = tuple([f or '' for f in record])
210 # Do not expose population column
211 ret.append(jsonentry % record[:-1])
212 finally:
213 cursor.close()
214 connection.close()
215
216
217 if isinstance( query_result, SingleQueryResult ):
218 return "".join( ret )
219
220 return ( jsonheader + ",".join( ret ) + jsonfooter )
221
222
223
224class QueryFactory:
225 """
226 Returns a query object if known values were found.
227 """
228 @staticmethod
229 def create( request ):
230 variable_list = RequestVariables( request )
231
232 if variable_list.contains( "query" ):
233 return TextQuery( variable_list.get( "query" ) )
234 elif variable_list.contains( "lat" ) and variable_list.contains( "long" ):
235 return CoordinatesBasedQuery( variable_list.get( "lat" ), variable_list.get( "long" ) )
236 else:
237 return None
238
48239
49def handler(req):240def handler(req):
50 fs = util.FieldStorage(req)241 query = QueryFactory.create( req )
51 req.content_type = 'application/json'242
52 if 'query' in fs:243 if query:
53 client = sphinxapi.SphinxClient()244 result = query.execute()
54 client.SetServer('localhost', 3312)245
55 client.SetSortMode(sphinxapi.SPH_SORT_ATTR_DESC, 'population')246 req = prepare_request( req )
56 result = client.Query(fs['query'])247 #req.write( str( result.get() ) )
57 if result:248 req.write( parse_query_result( result ) )
58 result = result['matches']249
59 ret = []250 return apache.OK
60 if result:
61 connection = psycopg2.connect(authstring)
62 cursor = connection.cursor()
63 try:
64 # We need at least one value for the sql in operator
65 # and there are no locations with id 0
66 statement_ids = ['0']
67 altstatement_ids = ['0']
68 for x in result:
69 rawid = x['id']
70 idval = rawid / 10
71 idtype = rawid % 10
72 if idtype == 1:
73 statement_ids.append(str(idval))
74 else:
75 altstatement_ids.append(str(idval))
76
77 statement_ids_str = '(' + ','.join(statement_ids) + ')'
78 altstatement_ids_str = '(' + ','.join(altstatement_ids) + ')'
79 fullstatement = statement % (statement_ids_str,
80 altstatement_ids_str)
81 cursor.execute(fullstatement)
82 records = cursor.fetchall()
83 for record in records:
84 record = tuple([f or '' for f in record])
85 # Do not expose population column
86 ret.append(jsonentry % record[:-1])
87 finally:
88 cursor.close()
89 connection.close()
90 req.write(jsonheader + ', '.join(ret) + jsonfooter)
91 return apache.OK
92251
=== modified file 'sphinx.conf'
--- sphinx.conf 2012-11-21 12:35:46 +0000
+++ sphinx.conf 2014-09-13 19:25:19 +0000
@@ -5,8 +5,10 @@
5sql_user = geouser5sql_user = geouser
6sql_pass = geopw6sql_pass = geopw
7sql_db = geonames7sql_db = geonames
8sql_query = SELECT geoname.geonameid*10+1 AS id, geoname.name AS name, geoname.population AS population, geoname.geonameid AS geonameid FROM geoname WHERE geoname.fclass='P'8sql_query = SELECT geoname.geonameid*10+1 AS id, geoname.name AS name, geoname.population AS population, geoname.geonameid AS geonameid, radians(geoname.latitude) AS latitude, radians(geoname.longitude) AS longitude FROM geoname WHERE geoname.fclass='P'
9sql_attr_uint = population9sql_attr_uint = population
10sql_attr_float = latitude
11sql_attr_float = longitude
10}12}
1113
12source altnamessrc14source altnamessrc
@@ -16,12 +18,15 @@
16sql_user = geouser18sql_user = geouser
17sql_pass = geopw19sql_pass = geopw
18sql_db = geonames20sql_db = geonames
19sql_query = SELECT alternatename.alternatenameId*10+2 AS id, alternatename.alternateName AS name, geoname.population AS population, geoname.geonameid AS geonameid FROM alternatename JOIN geoname on (geoname.geonameid = alternatename.geonameid AND geoname.name != alternatename.alternateName) WHERE (geoname.fclass='P' AND (LENGTH(alternatename.isoLanguage) < 4 OR alternatename.isoLanguage is null))21sql_query = SELECT alternatename.alternatenameId*10+2 AS id, alternatename.alternateName AS name, geoname.population AS population, geoname.geonameid AS geonameid, radians(geoname.latitude) AS latitude, radians(geoname.longitude) AS longitude FROM alternatename JOIN geoname on (geoname.geonameid = alternatename.geonameid AND geoname.name != alternatename.alternateName) WHERE (geoname.fclass='P' AND (LENGTH(alternatename.isoLanguage) < 4 OR alternatename.isoLanguage is null))
20sql_attr_uint = population22sql_attr_uint = population
23sql_attr_float = latitude
24sql_attr_float = longitude
21}25}
2226
23index geonames27index geonames
24{28{
29 enable_star = 1
25 source = geonamessrc30 source = geonamessrc
26 source = altnamessrc31 source = altnamessrc
27 path = /var/lib/sphinxsearch/data/geonames32 path = /var/lib/sphinxsearch/data/geonames
@@ -29,6 +34,19 @@
29 morphology = none34 morphology = none
30 stopwords =35 stopwords =
31 min_word_len = 236 min_word_len = 2
37 min_prefix_len = 3
38 min_infix_len = 0
39 charset_type = utf-8
40 charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z,A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+4E00..U+9FFF, U+3000..U+30FF
41}
42index geonamescoordinates
43{
44 source = geonamessrc
45 path = /var/lib/sphinxsearch/data/geonamescoordinates
46 docinfo = extern
47 morphology = none
48 stopwords =
49 min_word_len = 2
32 min_prefix_len = 050 min_prefix_len = 0
33 min_infix_len = 051 min_infix_len = 0
34 charset_type = utf-852 charset_type = utf-8
@@ -39,7 +57,7 @@
39port = 331257port = 3312
40log = /var/log/sphinxsearch/searchd.log58log = /var/log/sphinxsearch/searchd.log
41query_log = /var/log/sphinxsearch/query.log59query_log = /var/log/sphinxsearch/query.log
42read_timeout = 560read_timeout = 10
43max_children = 3061max_children = 30
44pid_file = /var/run/searchd.pid62pid_file = /var/run/searchd.pid
45max_matches = 100063max_matches = 1000

Subscribers

People subscribed via source and target branches

to all changes: