Add Pixel.northeast/southwest
properties
- the properties are needed for the drawing functionalitites
This commit is contained in:
parent
ca2ba0c9d5
commit
605ade4078
5 changed files with 96 additions and 2 deletions
|
@ -1,9 +1,11 @@
|
|||
"""Provide the ORM's `Pixel` model."""
|
||||
|
||||
import sqlalchemy as sa
|
||||
import utm
|
||||
from sqlalchemy import orm
|
||||
|
||||
from urban_meal_delivery.db import meta
|
||||
from urban_meal_delivery.db import utils
|
||||
|
||||
|
||||
class Pixel(meta.Base):
|
||||
|
@ -58,3 +60,48 @@ class Pixel(meta.Base):
|
|||
def area(self) -> float:
|
||||
"""The area of a pixel in square kilometers."""
|
||||
return self.grid.pixel_area
|
||||
|
||||
@property
|
||||
def northeast(self) -> utils.Location:
|
||||
"""The pixel's northeast corner, relative to `.grid.city.southwest`.
|
||||
|
||||
Implementation detail: This property is cached as none of the
|
||||
underlying attributes to calculate the value are to be changed.
|
||||
"""
|
||||
if not hasattr(self, '_northeast'): # noqa:WPS421 note:d334120e
|
||||
# The origin is the southwest corner of the `.grid.city`'s viewport.
|
||||
easting_origin = self.grid.city.southwest.easting
|
||||
northing_origin = self.grid.city.southwest.northing
|
||||
|
||||
# `+1` as otherwise we get the pixel's `.southwest` corner.
|
||||
easting = easting_origin + ((self.n_x + 1) * self.side_length)
|
||||
northing = northing_origin + ((self.n_y + 1) * self.side_length)
|
||||
zone, band = self.grid.city.southwest.zone_details
|
||||
latitude, longitude = utm.to_latlon(easting, northing, zone, band)
|
||||
|
||||
self._northeast = utils.Location(latitude, longitude)
|
||||
self._northeast.relate_to(self.grid.city.southwest)
|
||||
|
||||
return self._northeast
|
||||
|
||||
@property
|
||||
def southwest(self) -> utils.Location:
|
||||
"""The pixel's northeast corner, relative to `.grid.city.southwest`.
|
||||
|
||||
Implementation detail: This property is cached as none of the
|
||||
underlying attributes to calculate the value are to be changed.
|
||||
"""
|
||||
if not hasattr(self, '_southwest'): # noqa:WPS421 note:d334120e
|
||||
# The origin is the southwest corner of the `.grid.city`'s viewport.
|
||||
easting_origin = self.grid.city.southwest.easting
|
||||
northing_origin = self.grid.city.southwest.northing
|
||||
|
||||
easting = easting_origin + (self.n_x * self.side_length)
|
||||
northing = northing_origin + (self.n_y * self.side_length)
|
||||
zone, band = self.grid.city.southwest.zone_details
|
||||
latitude, longitude = utm.to_latlon(easting, northing, zone, band)
|
||||
|
||||
self._southwest = utils.Location(latitude, longitude)
|
||||
self._southwest.relate_to(self.grid.city.southwest)
|
||||
|
||||
return self._southwest
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import utm
|
||||
|
||||
|
@ -82,6 +82,11 @@ class Location:
|
|||
"""The UTM zone of the location."""
|
||||
return f'{self._zone}{self._band}'
|
||||
|
||||
@property
|
||||
def zone_details(self) -> Tuple[int, str]:
|
||||
"""The UTM zone of the location as the zone number and the band."""
|
||||
return (self._zone, self._band)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""Check if two `Location` objects are the same location."""
|
||||
if not isinstance(other, Location):
|
||||
|
|
|
@ -205,7 +205,7 @@ class TestGridification:
|
|||
@pytest.mark.db
|
||||
@pytest.mark.no_cover
|
||||
@pytest.mark.parametrize('side_length', [250, 500, 1_000, 2_000, 4_000, 8_000])
|
||||
def test_make_random_grids( # noqa:WPS211
|
||||
def test_make_random_grids( # noqa:WPS211,WPS218
|
||||
self, db_session, city, make_address, make_restaurant, make_order, side_length,
|
||||
):
|
||||
"""With 100 random `Address` objects, a grid must have ...
|
||||
|
@ -228,5 +228,12 @@ class TestGridification:
|
|||
assert isinstance(result, db.Grid)
|
||||
assert 1 <= len(result.pixels) <= n_pixels_x * n_pixels_y
|
||||
|
||||
# Sanity checks for `Pixel.southwest` and `Pixel.northeast`.
|
||||
for pixel in result.pixels:
|
||||
assert abs(pixel.southwest.x - pixel.n_x * side_length) < 2
|
||||
assert abs(pixel.southwest.y - pixel.n_y * side_length) < 2
|
||||
assert abs(pixel.northeast.x - (pixel.n_x + 1) * side_length) < 2
|
||||
assert abs(pixel.northeast.y - (pixel.n_y + 1) * side_length) < 2
|
||||
|
||||
db_session.add(result)
|
||||
db_session.commit()
|
||||
|
|
|
@ -87,3 +87,31 @@ class TestProperties:
|
|||
result = pixel.area
|
||||
|
||||
assert result == 1.0
|
||||
|
||||
def test_northeast(self, pixel, city):
|
||||
"""Test `Pixel.northeast` property."""
|
||||
result = pixel.northeast
|
||||
|
||||
assert abs(result.x - pixel.side_length) < 2
|
||||
assert abs(result.y - pixel.side_length) < 2
|
||||
|
||||
def test_northeast_is_cached(self, pixel):
|
||||
"""Test `Pixel.northeast` property."""
|
||||
result1 = pixel.northeast
|
||||
result2 = pixel.northeast
|
||||
|
||||
assert result1 is result2
|
||||
|
||||
def test_southwest(self, pixel, city):
|
||||
"""Test `Pixel.southwest` property."""
|
||||
result = pixel.southwest
|
||||
|
||||
assert abs(result.x) < 2
|
||||
assert abs(result.y) < 2
|
||||
|
||||
def test_southwest_is_cached(self, pixel):
|
||||
"""Test `Pixel.southwest` property."""
|
||||
result1 = pixel.southwest
|
||||
result2 = pixel.southwest
|
||||
|
||||
assert result1 is result2
|
||||
|
|
|
@ -140,6 +140,13 @@ class TestProperties:
|
|||
|
||||
assert result == ZONE
|
||||
|
||||
def test_zone_details(self, location):
|
||||
"""Test `Location.zone_details` property."""
|
||||
result = location.zone_details
|
||||
|
||||
zone, band = result
|
||||
assert ZONE == f'{zone}{band}'
|
||||
|
||||
|
||||
class TestRelateTo:
|
||||
"""Test the `Location.relate_to()` method and the `.x` and `.y` properties."""
|
||||
|
|
Loading…
Reference in a new issue