Add Address.x and Address.y coordinates

- the Address.x and Address.y properties use the UTMCoordinate class
  behind the scenes
- x and y are simple coordinates in an x-y plane
- the (0, 0) origin is the southwest corner of Address.city.viewport
This commit is contained in:
Alexander Hess 2021-01-02 16:29:50 +01:00
parent 6f9935072e
commit 6cb4be80f6
Signed by: alexander
GPG key ID: 344EA5AB10D868E0
5 changed files with 69 additions and 1 deletions

View file

@ -134,8 +134,11 @@ per-file-ignores =
WPS115, WPS115,
# Numbers are normal in config files. # Numbers are normal in config files.
WPS432, WPS432,
# No real string constant over-use.
src/urban_meal_delivery/db/addresses.py: src/urban_meal_delivery/db/addresses.py:
WPS226, WPS226,
src/urban_meal_delivery/db/cities.py:
WPS226,
src/urban_meal_delivery/db/orders.py: src/urban_meal_delivery/db/orders.py:
WPS226, WPS226,
tests/*.py: tests/*.py:

View file

@ -1,11 +1,14 @@
"""Provide the ORM's `Address` model.""" """Provide the ORM's `Address` model."""
from typing import Any
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from sqlalchemy.ext import hybrid from sqlalchemy.ext import hybrid
from urban_meal_delivery.db import meta from urban_meal_delivery.db import meta
from urban_meal_delivery.db import utils
class Address(meta.Base): class Address(meta.Base):
@ -64,6 +67,15 @@ class Address(meta.Base):
foreign_keys='[Order._delivery_address_id]', foreign_keys='[Order._delivery_address_id]',
) )
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Create a new address."""
# Call SQLAlchemy's default `.__init__()` method.
super().__init__(*args, **kwargs)
self._utm_coordinates = utils.UTMCoordinate(
self.latitude, self.longitude, relative_to=self.city.as_origin,
)
def __repr__(self) -> str: def __repr__(self) -> str:
"""Non-literal text representation.""" """Non-literal text representation."""
return '<{cls}({street} in {city})>'.format( return '<{cls}({street} in {city})>'.format(
@ -80,3 +92,13 @@ class Address(meta.Base):
`.is_primary` indicates the first in a group of `Address` objects. `.is_primary` indicates the first in a group of `Address` objects.
""" """
return self.id == self._primary_id return self.id == self._primary_id
@property
def x(self) -> int: # noqa=WPS111
"""The `.easting` of the address in meters, relative to the `.city`."""
return self._utm_coordinates.x
@property
def y(self) -> int: # noqa=WPS111
"""The `.northing` of the address in meters, relative to the `.city`."""
return self._utm_coordinates.y

View file

@ -1,12 +1,13 @@
"""Provide the ORM's `City` model.""" """Provide the ORM's `City` model."""
from typing import Dict from typing import Any, Dict
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from urban_meal_delivery.db import meta from urban_meal_delivery.db import meta
from urban_meal_delivery.db import utils
class City(meta.Base): class City(meta.Base):
@ -45,6 +46,18 @@ class City(meta.Base):
# Relationships # Relationships
addresses = orm.relationship('Address', back_populates='city') addresses = orm.relationship('Address', back_populates='city')
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Create a new city."""
# Call SQLAlchemy's default `.__init__()` method.
super().__init__(*args, **kwargs)
# Take the "lower left" of the viewport as the origin
# of a Cartesian coordinate system.
lower_left = self.viewport['southwest']
self._origin = utils.UTMCoordinate(
lower_left['latitude'], lower_left['longitude'],
)
def __repr__(self) -> str: def __repr__(self) -> str:
"""Non-literal text representation.""" """Non-literal text representation."""
return '<{cls}({name})>'.format(cls=self.__class__.__name__, name=self.name) return '<{cls}({name})>'.format(cls=self.__class__.__name__, name=self.name)
@ -81,3 +94,12 @@ class City(meta.Base):
'longitude': self._southwest_longitude, 'longitude': self._southwest_longitude,
}, },
} }
@property
def as_origin(self) -> utils.UTMCoordinate:
"""The lower left corner of the `.viewport` in UTM coordinates.
This property serves as the `relative_to` argument to the
`UTMConstructor` when representing an `Address` in the x-y plane.
"""
return self._origin

View file

@ -122,3 +122,15 @@ class TestProperties:
result = address.is_primary result = address.is_primary
assert result is False assert result is False
def test_x_is_positive(self, address):
"""Test `Address.x` property."""
result = address.x
assert result > 0
def test_y_is_positive(self, address):
"""Test `Address.y` property."""
result = address.y
assert result > 0

View file

@ -3,6 +3,7 @@
import pytest import pytest
from tests.db.utils import test_coordinates as consts
from urban_meal_delivery import db from urban_meal_delivery import db
@ -63,3 +64,11 @@ class TestProperties:
assert len(result) == 2 assert len(result) == 2
assert result['latitude'] == pytest.approx(city_data[f'_{corner}_latitude']) assert result['latitude'] == pytest.approx(city_data[f'_{corner}_latitude'])
assert result['longitude'] == pytest.approx(city_data[f'_{corner}_longitude']) assert result['longitude'] == pytest.approx(city_data[f'_{corner}_longitude'])
def test_city_in_utm_coordinates(self, city):
"""Test `City.as_origin` property."""
result = city.as_origin
assert result.zone == consts.ZONE
assert consts.MIN_EASTING < result.easting < consts.MAX_EASTING
assert consts.MIN_NORTHING < result.northing < consts.MAX_NORTHING