Test Failed
Push — master ( a24d69...1c0f42 )
by Dominik
01:38
created

  A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 3
c 0
b 0
f 0
dl 0
loc 188
rs 10
1
# ~*~ coding: utf-8 ~*~
2
3
""" Simple representation of OpenStreetMap's conceptual data model.
4
(cf. http://wiki.openstreetmap.org/wiki/Elements)
5
6
This implementation of the model assumes that only current data is used,
7
not historic data.
8
"""
9
10
import datetime
11
from sqlalchemy import Column, ForeignKey, Integer, Float, String, DateTime, Bool
12
from sqlalchemy.ext.declarative import declarative_base
13
from sqlalchemy.ext.associationproxy import association_proxy
14
from sqlalchemy.ext.orderinglist import ordering_list
15
from sqlalchemy.orm import relationship, backref
16
from sqlalchemy.orm.collections import attribute_mapped_collection
17
18
class OSMAlchemy(object):
19
    """ Wrapper class for the OSMAlchemy model and logic
20
21
    This class holds all the SQLAlchemy classes and logic that make up
22
    OSMAlchemy. It is contained in a separate class because it is a
23
    template that can be modified as needed by users, e.g. by using a
24
    different table prefix or a different declarative base.
25
    """
26
27
    def __init__(self, base=None, prefix="osm_"):
28
        """ Initialise the table definitions in the wrapper object
29
30
        This function generates the OSM element classes as SQLAlchemy table
31
        declaratives. If called without an argument, it uses a newly created
32
        declarative base.
33
34
        The base argument, if provided, can be either a declarative base or
35
        a Flask-SQLAlchemy object.
36
        """
37
38
        # Check what we got as declarative base
39
        if base is None:
40
            # Nothing, so create one
41
            base = declarative_base()
42
        elif hasattr(base, "Model"):
43
            # Unwrap Flask-SQLAlchemy object if we got one
44
            base = base.Model
45
46
        class OSMElement(base):
47
            """ Base class for all the conceptual OSM elements. """
48
49
            # Name of the table in the database, prefix provided by user
50
            __tablename__ = prefix + "elements"
51
52
            # The internal ID of the element, only for structural use
53
            element_id = Column(Integer, primary_key=True)
54
55
            # Track element modification for OSMAlchemy caching
56
            osmalchemy_updated = Column(DateTime, default=datetime.datetime.now,
57
                                        onupdate=datetime.datetime.now)
58
59
            # The type of the element, used by SQLAlchemy for polymorphism
60
            type = Column(String(256))
61
62
            # Tags belonging to the element
63
            # Accessed as a dictionary like {'name': 'value', 'name2': 'value2',…}
64
            # Uses proxying across several tables to OSMTag
65
            tags = association_proxy(prefix+"elements_tags", "tag_value",
66
                                     creator=lambda k, v: OSMElementsTags(tag_key=k, tag_value=v))
67
68
            # Metadata shared by all element types
69
            version = Column(Integer)
70
            changeset = Column(Integer)
71
            user = Column(String)
72
            uid = Column(Integer)
73
            visible = Column(Bool)
74
            timestamp = Column(DateTime)
75
76
            # Configure polymorphism
77
            __mapper_args__ = {
78
                'polymorphic_identity': prefix + 'elements',
79
                'polymorphic_on': type,
80
                'with_polymorphic': '*'
81
            }
82
83
        class OSMNode(OSMElement):
84
            """ An OSM node element.
85
86
            A node hast a latitude and longitude, which are non-optional,
87
            and a list of zero or more tags.
88
            """
89
90
            # Name of the table in the database, prefix provided by user
91
            __tablename__ = prefix + "nodes"
92
93
            # The internal ID of the element, only for structural use
94
            # Synchronised with the id of the parent table OSMElement through polymorphism
95
            element_id = Column(Integer, ForeignKey(prefix + 'elements.element_id'),
96
                                primary_key=True)
97
98
            # ID of the node, not to be confused with the primary key from OSMElements
99
            id = Column(Integer, unique=True)
100
101
            # Geographical coordinates of the node
102
            latitude = Column(Float, nullable=False)
103
            longitude = Column(Float, nullable=False)
104
105
            # Configure polymorphism with OSMElement
106
            __mapper_args__ = {
107
                'polymorphic_identity': prefix + 'nodes',
108
            }
109
110
            def __init__(self, latitude=0.0, longitude=0.0, **kwargs):
111
                """ Initialisation with two main positional arguments.
112
113
                Shorthand for OSMNode(lat, lon).
114
                """
115
116
                self.latitude = latitude
117
                self.longitude = longitude
118
119
                # Pass rest on to default constructor
120
                OSMElement.__init__(self, **kwargs)
121
122
        class OSMWaysNodes(base):
123
            """ Secondary mapping table for ways and nodes """
124
125
            # Name of the table in the database, prefix provided by user
126
            __tablename__ = prefix + "ways_nodes"
127
128
            # Internal ID of the mapping, only for structural use
129
            map_id = Column(Integer, primary_key=True)
130
131
            # Foreign key columns for the connected way and node
132
            way_id = Column(Integer, ForeignKey(prefix + 'ways.element_id'))
133
            node_id = Column(Integer, ForeignKey(prefix + 'nodes.element_id'))
134
            # Relationships for proxy access
135
            way = relationship("OSMWay", foreign_keys=[way_id])
136
            node = relationship("OSMNode", foreign_keys=[node_id])
137
138
            # Index of the node in the way to maintain ordered list, structural use only
139
            position = Column(Integer)
140
141
        class OSMWay(OSMElement):
142
            """ An OSM way element (also area).
143
144
            Contains a list of two or more nodes and a list of zero or more
145
            tags.
146
            """
147
148
            # Name of the table in the database, prefix provided by user
149
            __tablename__ = prefix + "ways"
150
151
            # The internal ID of the element, only for structural use
152
            # Synchronised with the id of the parent table OSMElement through polymorphism
153
            element_id = Column(Integer, ForeignKey(prefix + 'elements.element_id'),
154
                                primary_key=True)
155
156
            # ID of the way, not to be confused with the primary key from OSMElements
157
            id = Column(Integer, unique=True)
158
159
            # Relationship with all nodes in the way
160
            # Uses association proxy and a collection class to maintain an ordered list,
161
            # synchronised with the position field of OSMWaysNodes
162
            _nodes = relationship('OSMWaysNodes', order_by="OSMWaysNodes.position",
163
                                  collection_class=ordering_list("position"))
164
            nodes = association_proxy("_nodes", "node",
165
                                      creator=lambda _n: OSMWaysNodes(node=_n))
166
167
            # Configure polymorphism with OSMElement
168
            __mapper_args__ = {
169
                'polymorphic_identity': prefix + 'ways',
170
            }
171
172
        class OSMElementsTags(base):
173
            """ Secondary mapping table for elements and tags """
174
175
            # Name of the table in the database, prefix provided by user
176
            __tablename__ = prefix + "elements_tags"
177
178
            # Internal ID of the mapping, only for structural use
179
            map_id = Column(Integer, primary_key=True)
180
181
            # Foreign key columns for the element and tag of the mapping
182
            element_id = Column(Integer, ForeignKey(prefix + 'elements.element_id'))
183
            tag_id = Column(Integer, ForeignKey(prefix + 'tags.tag_id'))
184
185
            # Relationship with all the tags mapped to the element
186
            # The backref is the counter-part to the tags association proxy
187
            # in OSMElement to form the dictionary
188
            element = relationship("OSMElement", foreign_keys=[element_id],
189
                                   backref=backref(prefix+"elements_tags",
190
                                                   collection_class=attribute_mapped_collection("tag_key"),
191
                                                   cascade="all, delete-orphan"))
192
193
            # Relationship to the tag object and short-hand for its key and value
194
            # for use in the association proxy
195
            tag = relationship("OSMTag", foreign_keys=[tag_id])
196
            tag_key = association_proxy("tag", "key")
197
            tag_value = association_proxy("tag", "value")
198
199
        class OSMRelationsElements(base):
200
            """ Secondary mapping table for relation members """
201
202
            # Name of the table in the database, prefix provided by user
203
            __tablename__ = prefix + "relations_elements"
204
205
            # Internal ID of the mapping, only for structural use
206
            map_id = Column(Integer, primary_key=True)
207
208
            # Foreign ley columns for the relation and other element of the mapping
209
            relation_id = Column(Integer, ForeignKey(prefix + 'relations.element_id'))
210
            element_id = Column(Integer, ForeignKey(prefix + 'elements.element_id'))
211
            # Relationships for proxy access
212
            relation = relationship("OSMRelation", foreign_keys=[relation_id])
213
            element = relationship("OSMElement", foreign_keys=[element_id])
214
215
            # Role of the element in the relationship
216
            role = Column(String(256))
217
218
            # Index of element in the relationship to maintain ordered list, structural use only
219
            position = Column(Integer)
220
221
            # Produce (element, role) tuple for proxy access in OSMRelation
222
            @property
223
            def role_tuple(self):
224
                return (self.element, self.role)
225
226
        class OSMRelation(OSMElement):
227
            """ An OSM relation element.
228
229
            Contains zero or more members (ways, nodes or other relations)
230
            with associated, optional roles and zero or more tags.
231
            """
232
233
            # Name of the table in the database, prefix provided by user
234
            __tablename__ = prefix + "relations"
235
236
            # The internal ID of the element, only for structural use
237
            # Synchronised with the id of the parent table OSMElement through polymorphism
238
            element_id = Column(Integer, ForeignKey(prefix + 'elements.element_id'),
239
                                primary_key=True)
240
241
242
            # ID of the relation, not to be confused with the primary key from OSMElements
243
            id = Column(Integer, unique=True)
244
245
            # Relationship to the members of the relationship, proxied across OSMRelationsElements
246
            _members = relationship("OSMRelationsElements",
247
                                    order_by="OSMRelationsElements.position",
248
                                    collection_class=ordering_list("position"))
249
            # Accessed as a list like [(element, "role"), (element2, "role2")]
250
            members = association_proxy("_members", "role_tuple",
251
                                        creator=lambda _m: OSMRelationsElements(element=_m[0],
252
                                                                                role=_m[1]))
253
254
            # Configure polymorphism with OSMElement
255
            __mapper_args__ = {
256
                'polymorphic_identity': prefix + 'relations',
257
            }
258
259
        class OSMTag(base):
260
            """ An OSM tag element.
261
262
            Simple key/value pair.
263
            """
264
265
            # Name of the table in the database, prefix provided by user
266
            __tablename__ = prefix + "tags"
267
268
            # The internal ID of the element, only for structural use
269
            tag_id = Column(Integer, primary_key=True)
270
271
            # Key/value pair
272
            key = Column(String(256))
273
            value = Column(String(256))
274
275
            def __init__(self, key="", value="", **kwargs):
276
                """ Initialisation with two main positional arguments.
277
278
                Shorthand for OSMTag(key, value)
279
                """
280
281
                self.key = key
282
                self.value = value
283
284
                # Pass rest on to default constructor
285
                base.__init__(self, **kwargs)
286
287
        # Set the classes as members of the wrapper object
288
        self.Node = OSMNode #pylint: disable=invalid-name
289
        self.Way = OSMWay #pylint: disable=invalid-name
290
        self.Relation = OSMRelation #pylint: disable=invalid-name
291
        self.Tag = OSMTag #pylint: disable=invalid-name
292
293
        # Store generation attributes
294
        self._base = base
295
        self._prefix = prefix
296