1 | # ~*~ coding: utf-8 ~*~ |
||
2 | #- |
||
3 | # OSMAlchemy - OpenStreetMap to SQLAlchemy bridge |
||
4 | # Copyright (c) 2016 Dominik George <[email protected]> |
||
5 | # |
||
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
||
7 | # of this software and associated documentation files (the "Software"), to deal |
||
8 | # in the Software without restriction, including without limitation the rights |
||
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
10 | # copies of the Software, and to permit persons to whom the Software is |
||
11 | # furnished to do so, subject to the following conditions: |
||
12 | # |
||
13 | # The above copyright notice and this permission notice shall be included in all |
||
14 | # copies or substantial portions of the Software. |
||
15 | # |
||
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||
22 | # SOFTWARE. |
||
23 | # |
||
24 | # Alternatively, you are free to use OSMAlchemy under Simplified BSD, The |
||
25 | # MirOS Licence, GPL-2+, LGPL-2.1+, AGPL-3+ or the same terms as Python |
||
26 | # itself. |
||
27 | |||
28 | 1 | """ Simple representation of OpenStreetMap's conceptual data model. |
|
29 | (cf. http://wiki.openstreetmap.org/wiki/Elements) |
||
30 | |||
31 | This implementation of the model assumes that only current data is used, |
||
32 | not historic data. |
||
33 | """ |
||
34 | |||
35 | 1 | import datetime |
|
36 | 1 | from sqlalchemy import (Column, ForeignKey, Integer, BigInteger, Numeric, String, Unicode, |
|
37 | DateTime, Boolean, UniqueConstraint) |
||
38 | 1 | from sqlalchemy.ext.declarative import declarative_base |
|
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
39 | 1 | from sqlalchemy.ext.associationproxy import association_proxy |
|
40 | 1 | from sqlalchemy.ext.orderinglist import ordering_list |
|
41 | 1 | from sqlalchemy.orm import relationship, backref |
|
42 | 1 | from sqlalchemy.orm.collections import attribute_mapped_collection |
|
43 | |||
44 | 1 | def _generate_model(oa): |
|
0 ignored issues
–
show
The name
oa does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$ ).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
45 | """ Generates the data model. |
||
46 | |||
47 | The model classes are generated dynamically to allow passing in a |
||
48 | declarative base and a prefix. |
||
49 | """ |
||
50 | |||
51 | 1 | class OSMTag(oa.base): |
|
52 | """ An OSM tag element. |
||
53 | |||
54 | Simple key/value pair. |
||
55 | """ |
||
56 | |||
57 | # Name of the table in the database, prefix provided by user |
||
58 | 1 | __tablename__ = oa.prefix + "tags" |
|
59 | |||
60 | # The internal ID of the element, only for structural use |
||
61 | 1 | tag_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
|
62 | |||
63 | # Key/value pair |
||
64 | 1 | key = Column(Unicode(256)) |
|
65 | 1 | value = Column(Unicode(256)) |
|
66 | |||
67 | 1 | def __init__(self, key="", value="", **kwargs): |
|
68 | """ Initialisation with two main positional arguments. |
||
69 | |||
70 | Shorthand for OSMTag(key, value) |
||
71 | """ |
||
72 | |||
73 | 1 | self.key = key |
|
74 | 1 | self.value = value |
|
75 | |||
76 | # Pass rest on to default constructor |
||
77 | 1 | oa.base.__init__(self, **kwargs) |
|
78 | |||
79 | 1 | class OSMElement(oa.base): |
|
80 | """ Base class for all the conceptual OSM elements. """ |
||
81 | |||
82 | # Name of the table in the database, prefix provided by user |
||
83 | 1 | __tablename__ = oa.prefix + "elements" |
|
84 | |||
85 | # The internal ID of the element, only for structural use |
||
86 | 1 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
|
87 | |||
88 | # Track element modification for OSMAlchemy caching |
||
89 | 1 | osmalchemy_updated = Column(DateTime, default=datetime.datetime.now, |
|
90 | onupdate=datetime.datetime.now) |
||
91 | |||
92 | # The type of the element, used by SQLAlchemy for polymorphism |
||
93 | 1 | type = Column(String(256)) |
|
94 | |||
95 | # ID of the element in OSM, not to be confused with the primary key element_id |
||
96 | 1 | id = Column(BigInteger) |
|
0 ignored issues
–
show
The name
id does not conform to the class attribute naming conventions (([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ ).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
97 | |||
98 | # Tags belonging to the element |
||
99 | # Accessed as a dictionary like {'name': 'value', 'name2': 'value2',…} |
||
100 | # Uses proxying across several tables to OSMTag |
||
101 | 1 | tags = association_proxy(oa.prefix+"elements_tags", "tag_value", |
|
102 | creator=lambda k, v: OSMElementsTags(tag=OSMTag(key=k, value=v))) |
||
103 | |||
104 | # Metadata shared by all element types |
||
105 | 1 | version = Column(Integer) |
|
106 | 1 | changeset = Column(BigInteger) |
|
107 | 1 | user = Column(Unicode(256)) |
|
108 | 1 | uid = Column(BigInteger) |
|
109 | 1 | visible = Column(Boolean) |
|
110 | 1 | timestamp = Column(DateTime) |
|
111 | |||
112 | # OSM ids are unique per type |
||
113 | 1 | _u_type_id = UniqueConstraint("type", "id") |
|
114 | |||
115 | # Configure polymorphism |
||
116 | 1 | __mapper_args__ = { |
|
117 | 'polymorphic_identity': 'element', |
||
118 | 'polymorphic_on': type, |
||
119 | 'with_polymorphic': '*' |
||
120 | } |
||
121 | |||
122 | 1 | class OSMElementsTags(oa.base): |
|
123 | """ Secondary mapping table for elements and tags """ |
||
124 | |||
125 | # Name of the table in the database, prefix provided by user |
||
126 | 1 | __tablename__ = oa.prefix + "elements_tags" |
|
127 | |||
128 | # Internal ID of the mapping, only for structural use |
||
129 | 1 | map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
|
130 | |||
131 | # Foreign key columns for the element and tag of the mapping |
||
132 | 1 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
|
133 | ForeignKey(oa.prefix + 'elements.element_id')) |
||
134 | 1 | tag_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
|
135 | ForeignKey(oa.prefix + 'tags.tag_id')) |
||
136 | |||
137 | # Relationship with all the tags mapped to the element |
||
138 | # The backref is the counter-part to the tags association proxy |
||
139 | # in OSMElement to form the dictionary |
||
140 | 1 | element = relationship(OSMElement, foreign_keys=[element_id], |
|
141 | backref=backref(oa.prefix+"elements_tags", |
||
142 | collection_class=attribute_mapped_collection( |
||
143 | "tag_key" |
||
144 | ), cascade="all, delete-orphan")) |
||
145 | |||
146 | # Relationship to the tag object and short-hand for its key and value |
||
147 | 1 | # for use in the association proxy |
|
148 | 1 | tag = relationship(OSMTag, foreign_keys=[tag_id]) |
|
149 | 1 | tag_key = association_proxy("tag", "key") |
|
150 | tag_value = association_proxy("tag", "value") |
||
151 | 1 | ||
152 | class OSMNode(OSMElement): |
||
153 | """ An OSM node element. |
||
154 | |||
155 | A node hast a latitude and longitude, which are non-optional, |
||
156 | and a list of zero or more tags. |
||
157 | """ |
||
158 | |||
159 | 1 | # Name of the table in the database, prefix provided by user |
|
160 | __tablename__ = oa.prefix + "nodes" |
||
161 | |||
162 | # The internal ID of the element, only for structural use |
||
163 | 1 | # Synchronised with the id of the parent table OSMElement through polymorphism |
|
164 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
||
165 | ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True) |
||
166 | |||
167 | # Track element modification for OSMAlchemy caching |
||
168 | 1 | osmalchemy_updated = Column(DateTime, default=datetime.datetime.now, |
|
169 | 1 | onupdate=datetime.datetime.now) |
|
170 | |||
171 | # Geographical coordinates of the node |
||
172 | 1 | latitude = Column(Numeric(precision="9,7", asdecimal=False)) |
|
173 | longitude = Column(Numeric(precision="10,7", asdecimal=False)) |
||
174 | |||
175 | # Configure polymorphism with OSMElement |
||
176 | 1 | __mapper_args__ = { |
|
177 | 'polymorphic_identity': 'node', |
||
178 | } |
||
179 | |||
180 | def __init__(self, latitude=None, longitude=None, **kwargs): |
||
181 | """ Initialisation with two main positional arguments. |
||
182 | 1 | ||
183 | 1 | Shorthand for OSMNode(lat, lon). |
|
184 | """ |
||
185 | |||
186 | 1 | self.latitude = latitude |
|
187 | self.longitude = longitude |
||
188 | 1 | ||
189 | # Pass rest on to default constructor |
||
190 | OSMElement.__init__(self, **kwargs) |
||
0 ignored issues
–
show
The
__init__ of the immediate ancestor is not called instead this calls
the method of another ancestor OSMElement .
It is usually advisable to call the class NonDirectParent:
def __init__(self):
pass
class DirectParent(NonDirectParent):
def __init__(self):
NonDirectParent.__init__(self)
class YourClass(DirectParent):
def __init__(self):
NonDirectParent.__init__(self) # Bad
DirectParent.__init__(self) # Good
![]() |
|||
191 | |||
192 | 1 | class OSMWaysNodes(oa.base): |
|
193 | """ Secondary mapping table for ways and nodes """ |
||
194 | |||
195 | 1 | # Name of the table in the database, prefix provided by user |
|
196 | __tablename__ = oa.prefix + "ways_nodes" |
||
197 | |||
198 | 1 | # Internal ID of the mapping, only for structural use |
|
199 | map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
||
200 | 1 | ||
201 | # Foreign key columns for the connected way and node |
||
202 | way_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
||
203 | 1 | ForeignKey(oa.prefix + 'ways.element_id')) |
|
204 | node_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
||
205 | ForeignKey(oa.prefix + 'nodes.element_id')) |
||
206 | 1 | # Relationships for proxy access |
|
207 | node = relationship(OSMNode, foreign_keys=[node_id]) |
||
208 | 1 | ||
209 | # Index of the node in the way to maintain ordered list, structural use only |
||
210 | position = Column(Integer) |
||
211 | |||
212 | class OSMWay(OSMElement): |
||
213 | """ An OSM way element (also area). |
||
214 | |||
215 | Contains a list of two or more nodes and a list of zero or more |
||
216 | 1 | tags. |
|
217 | """ |
||
218 | |||
219 | # Name of the table in the database, prefix provided by user |
||
220 | 1 | __tablename__ = oa.prefix + "ways" |
|
221 | |||
222 | # The internal ID of the element, only for structural use |
||
223 | # Synchronised with the id of the parent table OSMElement through polymorphism |
||
224 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
||
225 | ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True) |
||
226 | 1 | ||
227 | # Track element modification for OSMAlchemy caching |
||
228 | 1 | osmalchemy_updated = Column(DateTime, default=datetime.datetime.now, |
|
229 | onupdate=datetime.datetime.now) |
||
230 | |||
231 | # Relationship with all nodes in the way |
||
232 | 1 | # Uses association proxy and a collection class to maintain an ordered list, |
|
233 | # synchronised with the position field of OSMWaysNodes |
||
234 | _nodes = relationship(OSMWaysNodes, order_by="OSMWaysNodes.position", |
||
235 | collection_class=ordering_list("position")) |
||
236 | 1 | nodes = association_proxy("_nodes", "node", |
|
237 | creator=lambda _n: OSMWaysNodes(node=_n)) |
||
238 | |||
239 | # Configure polymorphism with OSMElement |
||
240 | 1 | __mapper_args__ = { |
|
241 | 'polymorphic_identity': 'way', |
||
242 | } |
||
243 | 1 | ||
244 | class OSMRelationsElements(oa.base): |
||
245 | """ Secondary mapping table for relation members """ |
||
246 | 1 | ||
247 | # Name of the table in the database, prefix provided by user |
||
248 | 1 | __tablename__ = oa.prefix + "relations_elements" |
|
249 | |||
250 | # Internal ID of the mapping, only for structural use |
||
251 | 1 | map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
|
252 | |||
253 | # Foreign ley columns for the relation and other element of the mapping |
||
254 | 1 | relation_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
|
255 | ForeignKey(oa.prefix + 'relations.element_id')) |
||
256 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
||
257 | 1 | ForeignKey(oa.prefix + 'elements.element_id')) |
|
258 | # Relationships for proxy access |
||
259 | element = relationship(OSMElement, foreign_keys=[element_id]) |
||
260 | 1 | ||
261 | # Role of the element in the relationship |
||
262 | 1 | role = Column(Unicode(256)) |
|
263 | |||
264 | 1 | # Index of element in the relationship to maintain ordered list, structural use only |
|
265 | position = Column(Integer) |
||
266 | |||
267 | # Produce (element, role) tuple for proxy access in OSMRelation |
||
268 | @property |
||
269 | def role_tuple(self): |
||
0 ignored issues
–
show
This method should have a docstring.
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods: class SomeClass:
def some_method(self):
"""Do x and return foo."""
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions. ![]() |
|||
270 | return (self.element, self.role) |
||
271 | |||
272 | 1 | class OSMRelation(OSMElement): |
|
273 | """ An OSM relation element. |
||
274 | |||
275 | Contains zero or more members (ways, nodes or other relations) |
||
276 | 1 | with associated, optional roles and zero or more tags. |
|
277 | """ |
||
278 | |||
279 | # Name of the table in the database, prefix provided by user |
||
280 | 1 | __tablename__ = oa.prefix + "relations" |
|
281 | |||
282 | # The internal ID of the element, only for structural use |
||
283 | # Synchronised with the id of the parent table OSMElement through polymorphism |
||
284 | 1 | element_id = Column(BigInteger().with_variant(Integer, "sqlite"), |
|
285 | ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True) |
||
286 | |||
287 | # Track element modification for OSMAlchemy caching |
||
288 | osmalchemy_updated = Column(DateTime, default=datetime.datetime.now, |
||
289 | 1 | onupdate=datetime.datetime.now) |
|
290 | |||
291 | # Relationship to the members of the relationship, proxied across OSMRelationsElements |
||
292 | _members = relationship(OSMRelationsElements, |
||
293 | order_by="OSMRelationsElements.position", |
||
294 | 1 | collection_class=ordering_list("position")) |
|
295 | # Accessed as a list like [(element, "role"), (element2, "role2")] |
||
296 | members = association_proxy("_members", "role_tuple", |
||
297 | creator=lambda _m: OSMRelationsElements(element=_m[0], |
||
298 | role=_m[1])) |
||
299 | |||
300 | # Configure polymorphism with OSMElement |
||
301 | __mapper_args__ = { |
||
302 | 'polymorphic_identity': 'relation', |
||
303 | } |
||
304 | |||
305 | class OSMCachedQuery(oa.base): |
||
306 | """ Table for caching queries run by the OSMAlchemy triggers. """ |
||
307 | |||
308 | __tablename__ = oa.prefix + "cached_queries" |
||
309 | |||
310 | # The internal ID of the element, only for structural use |
||
311 | query_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True) |
||
312 | |||
313 | # Hash sum of the query to identify it |
||
314 | oql_hash = Column(Unicode(32), unique=True) |
||
315 | # Last time this query was run |
||
316 | oql_queried = Column(DateTime, default=datetime.datetime.now, |
||
317 | onupdate=datetime.datetime.now) |
||
318 | |||
319 | # Return the relevant generated objects |
||
320 | return (OSMNode, OSMWay, OSMRelation, OSMElement, OSMCachedQuery) |
||
321 |