source: subversion/applications/utils/where_are_they/mini_osm.py @ 30589

Last change on this file since 30589 was 7204, checked in by nickburch, 12 years ago

Update to support osm v0.5

  • Property svn:eol-style set to native
File size: 10.6 KB
Line 
1#!/usr/bin/python
2#
3#                   Mini OSM Module
4#                   ---------------
5#
6# Provides OSM functionality from a local (normally planet.osm powered)
7#  OSM database, in Python.
8#
9# Supports OSM v0.5
10#
11# GPL
12#
13# Nick Burch
14#               v0.03  (27/03/2008)
15
16import os
17import sys
18import math
19from geo_helper import calculate_distance_and_bearing
20
21class mini_osm:
22        dbh = None
23
24        #def __init__(self, type):
25        #       if type == "pgsql" or type == "postgres":
26        #               self = mini_osm_pgsql()
27        #       else:
28        #               raise "Un-supported type '%s'" % type
29        def __init__(self):
30                raise "Must create child classes"
31
32        def connect(self):
33                raise "Implementation must provide a connect method"
34
35        # ######################################################################
36
37        def getBoundingBox(self, lat, long, distance):
38                """For a given lat, long and distance, get the bounding box"""
39
40                lat_rad = lat / 360.0 * 2.0 * math.pi
41                long_rad = long / 360.0 * 2.0 * math.pi
42
43                # Calculate how far the distance is in lat+long
44                # Values should be as right as possible, whilst being compatible
45                #  with the answers from geo_helper.calculate_distance_and_bearing()
46                deg_lat_km = 110.574235
47                deg_long_km = 110.572833 * math.cos(lat_rad)
48                #deg_lat_km = 111.133 - 0.559*math.cos(2.0*lat_rad)
49                delta_lat = distance / 1000.0 / deg_lat_km
50                delta_long = distance / 1000.0 / deg_long_km
51
52                # Compute the min+max lat+long
53                min_lat = lat - delta_lat
54                min_long = long - delta_long
55                max_lat = lat + delta_lat
56                max_long = long + delta_long
57
58                return (min_lat,min_long,max_lat,max_long)
59
60        # ######################################################################
61
62        def _getNodesInAreaSQL(self):
63                """SQL to get all the nodes in an area"""
64                sql = "SELECT id, latitude, longitude, name, value        \
65                       FROM nodes                                         \
66                       LEFT OUTER JOIN node_tags                          \
67                            ON (id = node)                                \
68                       WHERE (latitude BETWEEN %s AND %s)                 \
69                       AND (longitude BETWEEN %s AND %s)"
70                return sql
71
72        def _getNodesInAreaWithTagSQL(self,tag_name,tag_value):
73                """SQL to get all the nodes in an area with the given tag name, and optionally also tag value"""
74                tag_match = "name = %s"
75                if not tag_value == None:
76                        tag_match += " AND value = %s"
77
78                sql = "SELECT id, latitude, longitude, name, value       \
79                       FROM nodes                                        \
80                       INNER JOIN node_tags                              \
81                            ON (id = node)                               \
82                       WHERE id IN (                                     \
83                          SELECT id                                      \
84                          FROM nodes                                     \
85                          INNER JOIN node_tags                           \
86                                ON (id = node AND " + tag_match + ")     \
87                          WHERE (latitude BETWEEN %s AND %s)             \
88                          AND (longitude BETWEEN %s AND %s)              \
89                       )"
90                return sql
91
92        def _getNodesWithTagNameAndTypeSQL(self,name,tag_type,tag_values):
93                """SQL to get all the nodes in the DB with the given name, and one of the given other tags, eg name=London, tag_type=place, tag_values=city,town"""
94
95                in_sql = ""
96                for val in (tag_values):
97                        if len(in_sql) > 0:
98                                in_sql += ","
99                        in_sql += "%s"
100
101                sql = "SELECT id, latitude, longitude, name, value       \
102                       FROM nodes                                        \
103                       INNER JOIN node_tags AS tags                      \
104                            ON (id = tags.node)                          \
105                       WHERE id IN (                                     \
106                          SELECT nt.node                                 \
107                          FROM node_tags AS nt                           \
108                          INNER JOIN node_tags AS ot                     \
109                            ON (nt.name = 'name' AND nt.value = %s       \
110                                AND nt.node = ot.node                    \
111                                AND ot.name = %s AND ot.value IN (" + in_sql + ") \
112                            )                                            \
113                       )"
114                return sql
115
116        def _processNodesQuery(self, sth):
117                """Processes the given nodes query, and return a dict of the nodes"""
118
119                nodes_db = sth.fetchall()
120
121                nodes = {}
122                last_node = { "id":-1 }
123                for node in (nodes_db):
124                        if last_node["id"] != node.id:
125                                if last_node["id"] != -1:
126                                        nodes[last_node["id"]] = last_node
127                                last_node = { "id":node.id, "lat":node.latitude, "long":node.longitude, "tags":[] }
128                        if node.name != None and len(node.name):
129                                last_node["tags"].append( (node.name,node.value) )
130                if last_node["id"] != -1:
131                        nodes[last_node["id"]] = last_node
132
133                return nodes
134
135        def getNodesInArea(self, lat, long, distance):
136                """Fetch all the nodes within a given distance of the lat+long"""
137
138                # Calculate how far the distance is in lat+long
139                (min_lat,min_long,max_lat,max_long) = self.getBoundingBox(lat,long,distance)
140                return self.getNodesInBBox(min_lat,min_long,max_lat,max_long)
141
142        def getNodesInBBox(self, min_lat, min_long, max_lat, max_long):
143                """Find all the nodes within the given bounding box"""
144                # Find nodes
145                sql = self._getNodesInAreaSQL()
146                sth = self.dbh.cursor()
147                sth.execute(sql, min_lat, max_lat, min_long, max_long)
148
149                return self._processNodesQuery(sth)
150
151        def getNodesWithTagNameAndType(self,name,tag_type,tag_values):
152                """Fetch all the nodes with the given name, and given other tag having one of the supplied values"""
153                if len(tag_values) == 0:
154                        return {}
155                sql = self._getNodesWithTagNameAndTypeSQL(name,tag_type,tag_values)
156                sth = self.dbh.cursor()
157                params = [name, tag_type]
158                for val in tag_values:
159                        params.append(val)
160                sth.execute(sql,params)
161
162                return self._processNodesQuery(sth)
163
164        def getNodesInAreaWithTag(self, lat, long, distance, tag_name, tag_value=None):
165                """Fetch all the nodes within a given distance of the lat+long, with the given tag name (and optionally tag value)"""
166
167                # Calculate how far the distance is in lat+long
168                (min_lat,min_long,max_lat,max_long) = self.getBoundingBox(lat,long,distance)
169                return self.getNodesInBBoxWithTag(min_lat,min_long,max_lat,max_long,tag_name,tag_value)
170
171        def getNodesInBBoxWithTag(self, min_lat, min_long, max_lat, max_long, tag_name, tag_value=None):
172                """Fetch all the nodes within the given bounding, with the given tag name (and optionally tag value)"""
173                params = [tag_name]
174                if not tag_value == None:
175                        params.append(tag_value)
176                params.append(min_lat)
177                params.append(max_lat)
178                params.append(min_long)
179                params.append(max_long)
180
181                # Find nodes
182                sql = self._getNodesInAreaWithTagSQL(tag_name,tag_value)
183                sth = self.dbh.cursor()
184                sth.execute(sql, params)
185
186                return self._processNodesQuery(sth)
187
188        # ######################################################################
189
190        def _getWaysForNodesSQL(self,node_ids):
191                "Returns all the different ways, and their tags, but not nodes"
192                sql = """SELECT way_tags.way AS id, name, value               
193                 FROM way_nodes   
194                 LEFT OUTER JOIN way_tags
195                    ON (way_nodes.way = way_tags.way)
196                 WHERE node IN (%s)""" % (node_ids)
197                return sql
198
199        def _getWaysNodesForWaysSQL(self,way_ids):
200                "Returns all the different nodes for the supplied ways"
201                sql = """SELECT way, node
202                 FROM way_nodes   
203                 WHERE way IN (%s)
204                 ORDER BY node_order""" % (way_ids)
205                return sql
206
207        def getWaysForNodes(self,nodes):
208                """Fetch all the ways for the given list of nodes"""
209                global dbh
210
211                # Get list of node ids
212                ids = ",".join( [str(n) for n in nodes.keys() if n] )
213
214                # Find the ways and their tags
215                sql = self._getWaysForNodesSQL(ids)
216                sth = self.dbh.cursor()
217                sth.execute(sql)
218
219                ways_db = sth.fetchall()
220
221                ways = {}
222                for way in (ways_db):
223                        if not ways.has_key(way.id):
224                                ways[way.id] = { "id":way.id, "nodes":[], "tags":[] }
225                        if way.name != None:
226                                ways[way.id]["tags"].append( (way.name,way.value) )
227
228                # Now find their tags
229                ids = ",".join( [str(w) for w in ways.keys() if w] )
230
231                if len(ids):
232                        sql = self._getWaysNodesForWaysSQL(ids)
233                        sth = self.dbh.cursor()
234                        sth.execute(sql)
235
236                        ways_db = sth.fetchall()
237                else:
238                        ways_db = []
239
240                last_way = { "id":-1 }
241                for way in (ways_db):
242                        ways[way.way]["nodes"].append( way.node )
243
244                return ways
245
246        # ######################################################################
247
248        def splatTagsOntoObjects(self,objects,want_tags):
249                """Splats certain tags down onto the main objects, setting them to None if not found"""
250                for obj in (objects.values()):
251                        # Set to None, so always there
252                        for tag in (want_tags):
253                                obj[tag] = None
254                        # Now search for them
255                        for tag in obj["tags"]:
256                                (tagname,tagvalue) = tag
257                                if want_tags.__contains__(tagname):
258                                        obj[tagname] = tagvalue
259
260        def calculateDistanceToWays(self,lat,long,ways,nodes):
261                """Calculates the distances to the ways"""
262
263                for way in (ways):
264                        # Get their nodes from inside the bbox
265                        f_nodes = []
266                        for node_id in way["nodes"]:
267                                if nodes.has_key(node_id):
268                                        f_nodes.append( nodes[node_id] )
269                        # Now find the distance of each node-node gap
270                        for node_pos in range(len(f_nodes) - 1):
271                                node_a = f_nodes[node_pos]
272                                node_b = f_nodes[node_pos+1]
273
274                                avg_lat = 0.5 * (node_a["lat"] + node_b["lat"])
275                                avg_long = 0.5 * (node_a["long"] + node_b["long"])
276
277                                dist_bearing = calculate_distance_and_bearing(lat,long,avg_lat,avg_long)
278                                if not way.has_key("distance"):
279                                        way["distance"] = dist_bearing[0]
280
281                                if way["distance"] <= dist_bearing[0]:
282                                        way["distance"] = dist_bearing[0]
283                                        way["bearing"] = dist_bearing[1]
284
285        def filterWaysByTags(self,ways,nodes,want_tag_names,want_tag_values=None):
286                """Filters the ways, only returning ones with one of the given tags, optionally also by tag value"""
287
288                wanted = []
289                for way in (ways.values()):
290                        # Exclude any sections that pass out of the boundary
291                        for node in way["nodes"]:
292                                if not nodes.has_key(node):
293                                        continue
294
295                        # Filter by tags
296                        for tag in way["tags"]:
297                                (tagname,tagvalue) = tag
298                                if want_tag_names.__contains__(tagname):
299                                        # Check value, if required
300                                        if want_tag_values == None:
301                                                wanted.append(way)
302                                        else:
303                                                if want_tag_values.__contains__(tagvalue):
304                                                        wanted.append(way)
305                return wanted
306
307# ######################################################################
308
309class mini_osm_pgsql(mini_osm):
310        """PostGreSQL Specific Mini OSM Implementation"""
311        from pyPgSQL import PgSQL
312
313        # Database settings
314        dbname = "planetosm"
315        dbhost = ""
316        dbuser = "nick"
317        dbpass = ""
318
319        dbh = None
320
321        def __init__(self):
322                self.connect()
323
324        def connect(self):
325                """ Connect to the database"""
326                if len(self.dbhost):
327                        self.dbh = self.PgSQL.connect(database=self.dbname, host=self.dbhost, user=self.dbuser, password=self.dbpass)
328                else:
329                        self.dbh = self.PgSQL.connect(database=self.dbname, user=self.dbuser, password=self.dbpass)
Note: See TracBrowser for help on using the repository browser.