From e6aeaa5741ac9e8526ae5b03c0380ef0311ca3c8 Mon Sep 17 00:00:00 2001 From: Schuyler Erle Date: Wed, 31 Aug 2011 01:42:14 +0200 Subject: [PATCH 1/5] Add new feature alternate name types. --- gazetteer/places/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gazetteer/places/models.py b/gazetteer/places/models.py index 1efc1de..7b36802 100644 --- a/gazetteer/places/models.py +++ b/gazetteer/places/models.py @@ -138,6 +138,9 @@ NAME_TYPE_CHOICES = ( ('official', 'official'), ('historic', 'historic'), ('colloquial', 'colloquial'), + ('icao', 'ICAO code'), + ('iata', 'IATA code'), + ('postcode', 'ZIP code'), ) From 6c7fee843c3ed01ff395d9243d53e07abc600836 Mon Sep 17 00:00:00 2001 From: Schuyler Erle Date: Wed, 31 Aug 2011 01:53:53 +0200 Subject: [PATCH 2/5] Add languages --- gazetteer/places/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gazetteer/places/models.py b/gazetteer/places/models.py index 7b36802..367de31 100644 --- a/gazetteer/places/models.py +++ b/gazetteer/places/models.py @@ -131,6 +131,8 @@ class Feature(models.Model): LANGUAGE_CHOICES = ( ('en', 'English'), ('es', 'Spanish'), + ('un', 'Unknown'), + ('xx', '(code)'), ) NAME_TYPE_CHOICES = ( From a9c5064c957674f7ffeb05fcf90bc3c5e8b6e656 Mon Sep 17 00:00:00 2001 From: Schuyler Erle Date: Tue, 30 Aug 2011 17:43:00 -0700 Subject: [PATCH 3/5] Revise overlaps() function to be find() function, support searching by string alone. --- gazetteer/places/models.py | 27 +++++++++++++++++---------- gazetteer/places/views.py | 21 ++++++++++++++------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/gazetteer/places/models.py b/gazetteer/places/models.py index 367de31..826a69a 100644 --- a/gazetteer/places/models.py +++ b/gazetteer/places/models.py @@ -24,16 +24,23 @@ class FeatureSearchManager(models.GeoManager): cursor = connection.cursor() cursor.execute("""SELECT set_limit(%f)""" % threshold) - def overlaps(self, (minx, miny, maxx, maxy), text=None, srid=4326): - bbox = Polygon(((minx,miny),(minx,maxy),(maxx,maxy),(maxx,miny),(minx,miny)),srid=srid) - if srid != 4326: bbox.transform(4326) # convert to lon/lat - qset = models.GeoManager.get_query_set(self).filter(geometry__bboverlaps=bbox) + def find(self, bbox=None, text=None, adm1=None, adm2=None srid=4326): + qset = self.get_query_set() + if bbox: + (minx, miny, maxx, maxy) = bbox + bbox = Polygon(((minx,miny),(minx,maxy),(maxx,maxy),(maxx,miny),(minx,miny)),srid=srid) + if srid != 4326: bbox.transform(4326) # convert to lon/lat + qset = qset.filter(geometry__bboverlaps=bbox) if text: text = text.replace("'", "''") # escape the ' # use the pg_trgm index - qset = qset.extra(select={"similarity":"similarity(preferred_name, '%s')" % text}, - where=["preferred_name %%%% '%s'" % text], + qset = qset.extra(select={"similarity":"similarity(preferred_name, '%s')"}, + select_params=[text], + where=["preferred_name %% '%s'"], + params=[text], order_by=["-similarity"]) + if adm1: qset = qset.filter(admin1__exact=adm1) + if adm2: qset = qset.filter(admin2__exact=adm2) return qset class Feature(models.Model): @@ -41,8 +48,8 @@ class Feature(models.Model): url = models.CharField(max_length=512, unique=True, verbose_name="URI") preferred_name = models.CharField(max_length=512) feature_type = models.ForeignKey("FeatureType", null=True, blank=True) - admin1 = models.CharField(max_length=2, blank=True, verbose_name="State") - admin2 = models.CharField(max_length=255, blank=True, verbose_name="County") + admin1 = models.CharField(max_length=2, blank=True, verbose_name="State", db_index=True) + admin2 = models.CharField(max_length=255, blank=True, verbose_name="County", db_index=True) geometry = models.GeometryField() is_primary = models.BooleanField(default=True) time_frame = models.ForeignKey("TimeFrame", null=True, blank=True) @@ -60,8 +67,8 @@ class Feature(models.Model): return "%s: %s" % (self.feature_type.code, self.feature_type.name,) feature_type_name.short_description = "Feature Type" - def get_geojson(self): - geom = json.loads(self.geometry.transform(3785, True).geojson) + def get_geojson(self, srid=4326): + geom = json.loads(self.geometry.transform(srid, True).geojson) if self.feature_type is None: feature_type = '' diff --git a/gazetteer/places/views.py b/gazetteer/places/views.py index 69f5b2a..f097f7f 100644 --- a/gazetteer/places/views.py +++ b/gazetteer/places/views.py @@ -11,14 +11,21 @@ def search(request): def search_json(request): search_term = request.GET.get("search", "") bbox = request.GET.get("bbox", False) - if not bbox: - return render_to_json_response({'error': 'no bbox parameter'}) - - bs = bbox.split(",") - bs_param = (float(bs[0]), float(bs[1]), float(bs[2]), float(bs[3]),) + country = request.GET.get("adm0", "US") # right now, unused + adm1 = request.GET.get("adm1", "") + adm2 = request.GET.get("adm2", "") + srid = int(request.GET.get("srid", 4326)) - features_qset = Feature.search.overlaps(bs_param, text=search_term, srid=3785)[0:20] - features = [f.get_geojson() for f in features_qset] + if bbox: + try: + bbox = map(float, bbox.split(",")) + except ValueError: + bbox = None + if not bbox and not search_term: + return render_to_json_response({'error': 'must supply either a valid `bbox` or a `search` parameter'}) + + features_qset = Feature.search.find(bbox=bbox, text=search_term, adm1=adm1, adm2=adm2, srid=srid)[0:20] + features = [f.get_geojson(srid) for f in features_qset] d = { 'type': 'FeatureCollection', 'features': features From da73dfa1b337a75ecdbae03ba019502484e9f7a6 Mon Sep 17 00:00:00 2001 From: Schuyler Erle Date: Tue, 30 Aug 2011 17:47:57 -0700 Subject: [PATCH 4/5] Add threshold to find(). --- gazetteer/places/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/gazetteer/places/views.py b/gazetteer/places/views.py index f097f7f..b0e625b 100644 --- a/gazetteer/places/views.py +++ b/gazetteer/places/views.py @@ -10,21 +10,30 @@ def search(request): def search_json(request): search_term = request.GET.get("search", "") + threshold = request.GET.get("threshold", None) bbox = request.GET.get("bbox", False) country = request.GET.get("adm0", "US") # right now, unused adm1 = request.GET.get("adm1", "") adm2 = request.GET.get("adm2", "") srid = int(request.GET.get("srid", 4326)) + if threshold: + try: + threshold = float(threshold) + except ValueError: + return render_to_json_response({'error': 'threshold must be a float'}) + if bbox: try: bbox = map(float, bbox.split(",")) except ValueError: bbox = None + return render_to_json_response({'error': 'bbox must be in the form: minx,miny,maxx,maxy'}) + if not bbox and not search_term: return render_to_json_response({'error': 'must supply either a valid `bbox` or a `search` parameter'}) - features_qset = Feature.search.find(bbox=bbox, text=search_term, adm1=adm1, adm2=adm2, srid=srid)[0:20] + features_qset = Feature.search.find(bbox=bbox, text=search_term, threshold=threshold, adm1=adm1, adm2=adm2, srid=srid)[0:20] features = [f.get_geojson(srid) for f in features_qset] d = { 'type': 'FeatureCollection', From 9906e46b71efe34f90f2a814940fbef7d5e56e14 Mon Sep 17 00:00:00 2001 From: Schuyler Erle Date: Wed, 31 Aug 2011 11:16:06 +0200 Subject: [PATCH 5/5] Refer to feature types by their names alone. --- gazetteer/places/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gazetteer/places/models.py b/gazetteer/places/models.py index 367de31..fe6f715 100644 --- a/gazetteer/places/models.py +++ b/gazetteer/places/models.py @@ -57,7 +57,7 @@ class Feature(models.Model): return self.preferred_name def feature_type_name(self): - return "%s: %s" % (self.feature_type.code, self.feature_type.name,) + return self.feature_type.name.title() feature_type_name.short_description = "Feature Type" def get_geojson(self):