Skip to content

Commit

Permalink
Add pre-commit config file; run linting & formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
brynpickering committed Mar 14, 2024
1 parent cbe40af commit 3850534
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 385 deletions.
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
default_language_version:
python: python3

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
args: ["--maxkb=1000"]

- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black

- repo: https://github.com/astral-sh/ruff-pre-commit # https://beta.ruff.rs/docs/usage/#github-action
rev: v0.3.0
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]

ci: # https://pre-commit.ci/
autofix_prs: false
autoupdate_schedule: monthly
123 changes: 62 additions & 61 deletions src/osmox/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,15 @@
import osmium
import pandas as pd
import shapely.wkb as wkblib
from pyproj import Transformer, CRS
from pyproj import CRS, Transformer
from shapely.geometry import MultiPoint
from shapely.ops import transform, nearest_points
from shapely.ops import nearest_points, transform

from osmox import helpers

OSMTag = namedtuple('OSMtag', 'key value')
OSMObject = namedtuple('OSMobject', 'idx, activity_tags, geom')
AVAILABLE_FEATURES = [
"area",
"levels",
"floor_area",
"units",
"transit_distance"
]
OSMTag = namedtuple("OSMtag", "key value")
OSMObject = namedtuple("OSMobject", "idx, activity_tags, geom")
AVAILABLE_FEATURES = ["area", "levels", "floor_area", "units", "transit_distance"]


class Object:
Expand Down Expand Up @@ -57,7 +51,7 @@ class Object:
"university": 3,
"college": 3,
"sports_hall": 1,
"stadium": 1
"stadium": 1,
}

def __init__(self, idx, osm_tags, activity_tags, geom) -> None:
Expand All @@ -82,12 +76,12 @@ def area(self):
return int(self.geom.area)

def levels(self):
if 'building:levels' in self.osm_tags:
levels = self.osm_tags['building:levels']
if "building:levels" in self.osm_tags:
levels = self.osm_tags["building:levels"]
if levels.isnumeric():
return float(levels) # todo ensure integer
if 'height' in self.osm_tags:
height = helpers.height_to_m(self.osm_tags['height'])
if "height" in self.osm_tags:
height = helpers.height_to_m(self.osm_tags["height"])
if height:
return float(height / 4)
if self.osm_tags.get("building"):
Expand All @@ -100,8 +94,8 @@ def floor_area(self):
return self.area() * self.levels()

def units(self):
if 'building:flats' in self.osm_tags:
flats = self.osm_tags['building:flats']
if "building:flats" in self.osm_tags:
flats = self.osm_tags["building:flats"]
if flats.isnumeric():
return float(flats) # todo ensure integer
return 1
Expand Down Expand Up @@ -169,8 +163,8 @@ def summary(self):
"""
fixed = {
"id": self.idx,
"activities": ','.join(self.activities),
"geometry": self.geom.centroid
"activities": ",".join(self.activities),
"geometry": self.geom.centroid,
}
return {**fixed, **self.features}

Expand All @@ -179,11 +173,7 @@ def single_activity_summaries(self):
Yield (dict) summaries for each each activity of an object.
"""
for act in self.activities:
fixed = {
"id": self.idx,
"activity": act,
"geometry": self.geom.centroid
}
fixed = {"id": self.idx, "activity": act, "geometry": self.geom.centroid}
yield {**fixed, **self.features}


Expand All @@ -193,12 +183,7 @@ class ObjectHandler(osmium.SimpleHandler):
logger = logging.getLogger(__name__)

def __init__(
self,
config,
crs='epsg:27700',
from_crs='epsg:4326',
lazy=False,
level=logging.DEBUG
self, config, crs="epsg:27700", from_crs="epsg:4326", lazy=False, level=logging.DEBUG
):

super().__init__()
Expand All @@ -216,12 +201,7 @@ def __init__(
self.points = helpers.AutoTree()
self.areas = helpers.AutoTree()

self.log = {
"existing": 0,
"points": 0,
"areas": 0,
"defaults": 0
}
self.log = {"existing": 0, "points": 0, "areas": 0, "defaults": 0}

"""
On handler.apply_file() method; parse through all nodes and areas:
Expand All @@ -243,14 +223,19 @@ def get_filtered_tags(self, tags):
found = []
for osm_key, osm_val in tags.items():
if osm_key in self.activity_config:
if osm_val in self.activity_config[osm_key] or self.activity_config[osm_key] == "*":
if (
osm_val in self.activity_config[osm_key]
or self.activity_config[osm_key] == "*"
):
found.append(OSMTag(key=osm_key, value=osm_val))
return found

def add_object(self, idx, activity_tags, osm_tags, geom):
if geom:
geom = transform(self.transformer.transform, geom)
self.objects.auto_insert(Object(idx=idx, osm_tags=osm_tags, activity_tags=activity_tags, geom=geom))
self.objects.auto_insert(
Object(idx=idx, osm_tags=osm_tags, activity_tags=activity_tags, geom=geom)
)

def add_point(self, idx, activity_tags, geom):
if geom:
Expand All @@ -267,29 +252,33 @@ def fab_point(self, n):
wkb = self.wkbfab.create_point(n)
return wkblib.loads(wkb, hex=True)
except RuntimeError:
self.logger.warning(f' RuntimeError encountered for point: {n}')
self.logger.warning(f" RuntimeError encountered for point: {n}")
return None

def fab_area(self, a):
try:
wkb = self.wkbfab.create_multipolygon(a)
return wkblib.loads(wkb, hex=True)
except RuntimeError:
self.logger.warning(f' RuntimeError encountered for polygon: {a}')
self.logger.warning(f" RuntimeError encountered for polygon: {a}")
return None

def node(self, n):
activity_tags = self.get_filtered_tags(n.tags)
# todo consider renaming activiity tags to filtered or selected tags
if self.selects(n.tags):
self.add_object(idx=n.id, osm_tags=n.tags, activity_tags=activity_tags, geom=self.fab_point(n))
self.add_object(
idx=n.id, osm_tags=n.tags, activity_tags=activity_tags, geom=self.fab_point(n)
)
elif activity_tags:
self.add_point(idx=n.id, activity_tags=activity_tags, geom=self.fab_point(n))

def area(self, a):
activity_tags = self.get_filtered_tags(a.tags)
if self.selects(a.tags):
self.add_object(idx=a.id, osm_tags=a.tags, activity_tags=activity_tags, geom=self.fab_area(a))
self.add_object(
idx=a.id, osm_tags=a.tags, activity_tags=activity_tags, geom=self.fab_area(a)
)
elif activity_tags:
self.add_area(idx=a.id, activity_tags=activity_tags, geom=self.fab_area(a))

Expand All @@ -307,7 +296,9 @@ def assign_tags_full(self):
Assign unknown tags to buildings spatially.
"""

for obj in helpers.progressBar(self.objects, prefix='Progress:', suffix='Complete', length=50):
for obj in helpers.progressBar(
self.objects, prefix="Progress:", suffix="Complete", length=50
):

if obj.activity_tags:
# if an onject already has activity tags, continue
Expand All @@ -332,7 +323,9 @@ def assign_tags_full(self):
def assign_tags_lazy(self):
"""Assign tags if filtered object does not already have useful tags."""

for obj in helpers.progressBar(self.objects, prefix='Progress:', suffix='Complete', length=50):
for obj in helpers.progressBar(
self.objects, prefix="Progress:", suffix="Complete", length=50
):

if obj.activity_tags:
# if an onject already has activity tags, continue
Expand All @@ -356,16 +349,18 @@ def assign_tags_lazy(self):
obj.apply_default_tag(a)

def assign_activities(self):
for obj in helpers.progressBar(self.objects, prefix='Progress:', suffix='Complete', length=50):
for obj in helpers.progressBar(
self.objects, prefix="Progress:", suffix="Complete", length=50
):
obj.assign_activities(self.activity_config)

def fill_missing_activities(
self,
area_tags=("landuse", "residential"),
required_acts="home",
new_tags=("building", "house"),
size=(10, 10),
spacing=(25, 25)
self,
area_tags=("landuse", "residential"),
required_acts="home",
new_tags=("building", "house"),
size=(10, 10),
spacing=(25, 25),
):
"""
Fill "empty" areas with new objects. Empty areas are defined as areas with the select_tags but
Expand All @@ -389,7 +384,9 @@ def fill_missing_activities(
new_osm_tags = [OSMTag(key=k, value=v) for k, v in area_tags]
new_tags = [OSMTag(key=k, value=v) for k, v in new_tags]

for area in helpers.progressBar(self.areas, prefix='Progress:', suffix='Complete', length=50):
for area in helpers.progressBar(
self.areas, prefix="Progress:", suffix="Complete", length=50
):

if not helpers.tag_match(a=area_tags, b=area.activity_tags):
continue
Expand All @@ -402,7 +399,9 @@ def fill_missing_activities(
# sample a grid
points = helpers.area_grid(area=area.geom, spacing=spacing)
for point in points: # add objects built from grid
self.objects.auto_insert(helpers.fill_object(i, point, size, new_osm_tags, new_tags, required_acts))
self.objects.auto_insert(
helpers.fill_object(i, point, size, new_osm_tags, new_tags, required_acts)
)
i += 1

return empty_zones, i
Expand All @@ -420,15 +419,19 @@ def add_features(self):
"""
["units", "floors", "area", "floor_area"]
"""
for obj in helpers.progressBar(self.objects, prefix='Progress:', suffix='Complete', length=50):
for obj in helpers.progressBar(
self.objects, prefix="Progress:", suffix="Complete", length=50
):
obj.add_features(self.object_features)

def assign_nearest_distance(self, target_act):
"""
For each facility, calculate euclidean distance to targets of given activity type.
"""
targets = self.extract_targets(target_act)
for obj in helpers.progressBar(self.objects, prefix='Progress:', suffix='Complete', length=50):
for obj in helpers.progressBar(
self.objects, prefix="Progress:", suffix="Complete", length=50
):
obj.get_closest_distance(targets, target_act)

def extract_targets(self, target_act):
Expand All @@ -447,12 +450,10 @@ def geodataframe(self, single_use=False):
df = pd.DataFrame(
(summary for o in self.objects for summary in o.single_activity_summaries())
)
return gp.GeoDataFrame(df, geometry='geometry', crs=self.crs)
return gp.GeoDataFrame(df, geometry="geometry", crs=self.crs)

df = pd.DataFrame(
(o.summary() for o in self.objects)
)
return gp.GeoDataFrame(df, geometry='geometry', crs=self.crs)
df = pd.DataFrame((o.summary() for o in self.objects))
return gp.GeoDataFrame(df, geometry="geometry", crs=self.crs)

# def extract(self):
# df = pd.DataFrame.from_records(
Expand Down
48 changes: 28 additions & 20 deletions src/osmox/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

import click

from osmox import config, build
from osmox.helpers import PathPath
from osmox.helpers import path_leaf
from osmox import build, config
from osmox.helpers import PathPath, path_leaf

default_config_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../configs/config.json")
Expand Down Expand Up @@ -38,15 +37,28 @@ def validate(config_path):


@cli.command()
@click.argument('config_path', type=PathPath(exists=True), nargs=1, required=True)
@click.argument('input_path', type=PathPath(exists=True), nargs=1, required=True)
@click.argument('output_name', nargs=1, required=True)
@click.option("-crs", "--crs", type=str, default="epsg:27700",
help="crs string eg (default): 'epsg:27700' (UK grid)")
@click.option("-s", "--single_use", is_flag=True,
help="split multi-activity facilities into multiple single-activity facilities")
@click.option("-l", "--lazy", is_flag=True,
help="if filtered object already has a label, do not search for more (supresses multi-use)")
@click.argument("config_path", type=PathPath(exists=True), nargs=1, required=True)
@click.argument("input_path", type=PathPath(exists=True), nargs=1, required=True)
@click.argument("output_name", nargs=1, required=True)
@click.option(
"-crs",
"--crs",
type=str,
default="epsg:27700",
help="crs string eg (default): 'epsg:27700' (UK grid)",
)
@click.option(
"-s",
"--single_use",
is_flag=True,
help="split multi-activity facilities into multiple single-activity facilities",
)
@click.option(
"-l",
"--lazy",
is_flag=True,
help="if filtered object already has a label, do not search for more (supresses multi-use)",
)
def run(config_path, input_path, output_name, crs, single_use, lazy):
logger.info(f" Loading config from {config_path}")
cnfg = config.load(config_path)
Expand All @@ -58,13 +70,9 @@ def run(config_path, input_path, output_name, crs, single_use, lazy):
if lazy:
logger.info("Handler will be using lazy assignment, this may suppress some multi-use.")

handler = build.ObjectHandler(
config=cnfg,
crs=crs,
lazy=lazy
)
handler = build.ObjectHandler(config=cnfg, crs=crs, lazy=lazy)
logger.info(f" Filtering all objects found in {input_path}. This may take a long while.")
handler.apply_file(str(input_path), locations=True, idx='flex_mem')
handler.apply_file(str(input_path), locations=True, idx="flex_mem")
logger.info(f" Found {len(handler.objects)} buildings.")
logger.info(f" Found {len(handler.points)} nodes with valid tags.")
logger.info(f" Found {len(handler.areas)} areas with valid tags.")
Expand All @@ -84,15 +92,15 @@ def run(config_path, input_path, output_name, crs, single_use, lazy):
required_acts=group["required_acts"],
new_tags=group["new_tags"],
size=group["size"],
spacing=group["spacing"]
spacing=group["spacing"],
)
logger.info(f" Filled {zones} zones with {objects} objects.")

if cnfg.get("object_features"):
logger.info(f" Assigning object features: {cnfg['object_features']}.")
handler.add_features()

if 'distance_to_nearest' in cnfg:
if "distance_to_nearest" in cnfg:
for target_activity in cnfg["distance_to_nearest"]:
logger.info(f" Assigning distances to nearest {target_activity}.")
handler.assign_nearest_distance(target_activity)
Expand Down
Loading

0 comments on commit 3850534

Please sign in to comment.