Natureshadow /
OSMAlchemy
| 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
Loading history...
|
|||
| 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. Loading history...
|
|||
| 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. Loading history...
|
|||
| 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
Loading history...
|
|||
| 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. Loading history...
|
|||
| 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 |