-
Notifications
You must be signed in to change notification settings - Fork 0
/
grids.py
137 lines (107 loc) · 4.38 KB
/
grids.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Provide the ORM's `Grid` model."""
from __future__ import annotations
from typing import Any
import folium
import sqlalchemy as sa
from sqlalchemy import orm
from urban_meal_delivery import db
from urban_meal_delivery.db import meta
class Grid(meta.Base):
"""A grid of `Pixel`s to partition a `City`.
A grid is characterized by the uniform size of the `Pixel`s it contains.
That is configures via the `Grid.side_length` attribute.
"""
__tablename__ = 'grids'
# Columns
id = sa.Column( # noqa:WPS125
sa.SmallInteger, primary_key=True, autoincrement=True,
)
city_id = sa.Column(sa.SmallInteger, nullable=False)
side_length = sa.Column(sa.SmallInteger, nullable=False, unique=True)
# Constraints
__table_args__ = (
sa.ForeignKeyConstraint(
['city_id'], ['cities.id'], onupdate='RESTRICT', ondelete='RESTRICT',
),
# Each `Grid`, characterized by its `.side_length`,
# may only exists once for a given `.city`.
sa.UniqueConstraint('city_id', 'side_length'),
# Needed by a `ForeignKeyConstraint` in `address_pixel_association`.
sa.UniqueConstraint('id', 'city_id'),
)
# Relationships
city = orm.relationship('City', back_populates='grids')
pixels = orm.relationship('Pixel', back_populates='grid')
def __repr__(self) -> str:
"""Non-literal text representation."""
return '<{cls}: {area} sqr. km>'.format(
cls=self.__class__.__name__, area=self.pixel_area,
)
# Convenience properties
@property
def pixel_area(self) -> float:
"""The area of a `Pixel` on the grid in square kilometers."""
return round((self.side_length ** 2) / 1_000_000, 1)
@classmethod
def gridify(cls, city: db.City, side_length: int) -> db.Grid: # noqa:WPS210
"""Create a fully populated `Grid` for a `city`.
The `Grid` contains only `Pixel`s that have at least one
`Order.pickup_address`. `Address` objects outside the `.city`'s
viewport are discarded.
Args:
city: city for which the grid is created
side_length: the length of a square `Pixel`'s side
Returns:
grid: including `grid.pixels` with the associated `city.addresses`
"""
grid = cls(city=city, side_length=side_length)
# `Pixel`s grouped by `.n_x`-`.n_y` coordinates.
pixels = {}
pickup_addresses = (
db.session.query(db.Address)
.join(db.Order, db.Address.id == db.Order.pickup_address_id)
.filter(db.Address.city == city)
.all()
)
for address in pickup_addresses:
# Check if an `address` is not within the `city`'s viewport, ...
not_within_city_viewport = (
address.x < 0
or address.x > city.total_x
or address.y < 0
or address.y > city.total_y
)
# ... and, if so, the `address` does not belong to any `Pixel`.
if not_within_city_viewport:
continue
# Determine which `pixel` the `address` belongs to ...
n_x, n_y = address.x // side_length, address.y // side_length
# ... and create a new `Pixel` object if necessary.
if (n_x, n_y) not in pixels:
pixels[(n_x, n_y)] = db.Pixel(grid=grid, n_x=n_x, n_y=n_y)
pixel = pixels[(n_x, n_y)]
# Create an association between the `address` and `pixel`.
assoc = db.AddressPixelAssociation(address=address, pixel=pixel)
pixel.addresses.append(assoc)
return grid
def clear_map(self) -> Grid: # pragma: no cover
"""Shortcut to the `.city.clear_map()` method.
Returns:
self: enabling method chaining
""" # noqa:D402,DAR203
self.city.clear_map()
return self
@property # pragma: no cover
def map(self) -> folium.Map: # noqa:WPS125
"""Shortcut to the `.city.map` object."""
return self.city.map
def draw(self, **kwargs: Any) -> folium.Map: # pragma: no cover
"""Draw all pixels in the grid.
Args:
**kwargs: passed on to `Pixel.draw()`
Returns:
`.city.map` for convenience in interactive usage
"""
for pixel in self.pixels:
pixel.draw(**kwargs)
return self.map