Skip to content

Commit

Permalink
add cli test for --location, fix some minor things, change dst from p…
Browse files Browse the repository at this point in the history
…ositional params to kwarg, rebasing
  • Loading branch information
felix-schott committed Nov 6, 2023
1 parent 64229ba commit 7dc815c
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ private/
*.py[cod]
*$py.class

cache
issues.txt

# C extensions
Expand Down
14 changes: 7 additions & 7 deletions open_buildings/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ def handle_comma_separated(ctx, param, value):
def geocode(data: str):
location = osmnx.geocode_to_gdf(data)
wkt = box(*location.total_bounds)
geojson = json.dumps(mapping(wkt))
geojson = json.loads(json.dumps({"type": "Feature", "geometry": mapping(wkt)})) # turn geom tuple into list by (de-)serialising
return geojson

@main.command(name="get_buildings")
@click.argument('geojson_input', type=click.File('r'), required=False)
@click.argument('dst', type=str, default="buildings.json")
@click.option('--dst', type=str, default="buildings.json", help='The path to write the output to. Can be a directory or file.')
@click.option('--location', type=str, default=None, help='Use city or region name')
@click.option('--source', default="overture", type=click.Choice(['google', 'overture']), help='Dataset to query, defaults to Overture')
@click.option('--country_iso', type=str, default=None, help='A 2 character country ISO code to filter the data by.')
@click.option('-s', '--silent', is_flag=True, default=False, help='Suppress all print outputs.')
@click.option('--overwrite', default=False, is_flag=True, help='Overwrite the destination file if it already exists.')
@click.option('--verbose', default=False, is_flag=True, help='Print detailed logs with timestamps.')
@click.option('--location', type=str, default=None, help='Use city or region name')
def get_buildings(geojson_input, location, dst, source, country_iso, silent, overwrite, verbose):
@click.option('-v', '--verbose', default=False, is_flag=True, help='Print detailed logs with timestamps.')
def get_buildings(geojson_input, dst, location, source, country_iso, silent, overwrite, verbose):
"""Tool to extract buildings in common geospatial formats from large archives of GeoParquet data online. GeoJSON
input can be provided as a file or piped in from stdin. If no GeoJSON input is provided, the tool will read from stdin.
Expand Down Expand Up @@ -80,12 +80,12 @@ def get_buildings(geojson_input, location, dst, source, country_iso, silent, ove

if geojson_input:
geojson_data = json.load(geojson_input)
elif geocode:
elif location:
geojson_data = geocode(location)
else:
geojson_data = json.load(click.get_text_stream('stdin'))

download_buildings(geojson_data, location, generate_sql=False, dst=dst, silent=silent, overwrite=overwrite, verbose=verbose, country_iso=country_iso)
download_buildings(geojson_data, generate_sql=False, dst=dst, silent=silent, overwrite=overwrite, verbose=verbose, country_iso=country_iso)

@google.command('benchmark')
@click.argument('input_path', type=click.Path(exists=True))
Expand Down
12 changes: 1 addition & 11 deletions open_buildings/download_buildings.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ def geojson_to_wkt(data: dict) -> str:
geometry = shape(data['geometry'])
return geometry.wkt

def geocode(data: str):
location = osmnx.geocode_to_gdf(data)
wkt = box(*location.total_bounds)
g2 = geojson.Feature(geometry=wkt)
return (g2)

def quadkey_to_geojson(quadkey: str) -> dict:
# Convert the quadkey to tile coordinates
tile = mercantile.quadkey_to_tile(quadkey)
Expand Down Expand Up @@ -91,13 +85,10 @@ def quadkey(geojson_input):

@cli.command()
@click.argument('geojson_input', type=click.File('r'), required=False)
@click.option('--location', type=str, help='geocode using a city or region')
def WKT(geojson_input, location):
def WKT(geojson_input):
"""Convert GeoJSON to Well Known Text."""
if geojson_input:
result = json.load(geojson_input)
elif location:
result = geocode(location)
else:
geojson_data = json.load(click.get_text_stream('stdin'))
result = geojson_to_wkt(geojson_data)
Expand Down Expand Up @@ -142,7 +133,6 @@ def quad2json(quadkey_input):

def download(
geojson_data: Dict[str, Any],
location: Optional[str] = None,
dst: Union[Path, str] = "buildings.json",
source: Union[Source, str] = Source.OVERTURE,
format: Optional[Union[Format, str]] = None,
Expand Down
27 changes: 19 additions & 8 deletions tests/test_open_buildings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import re
import subprocess

from open_buildings.download_buildings import download, geojson_to_wkt, geojson_to_quadkey, quadkey_to_geojson, geocode
from open_buildings.download_buildings import download, geojson_to_wkt, geojson_to_quadkey, quadkey_to_geojson
from open_buildings.cli import geocode
from open_buildings.settings import Source, Format, settings

###########################################################################
Expand Down Expand Up @@ -67,8 +68,7 @@ def test_quadkey_to_geojson():

def test_geocode():
""" Tests geocode() using a pre-established true value. """

assert json.loads(geocode('plymouth')) == {"type": "Polygon", "coordinates": [[[-4.0196056, 50.3327426], [-4.0196056, 50.4441737], [-4.2055324, 50.4441737], [-4.2055324, 50.3327426], [-4.0196056, 50.3327426]]]}
assert geocode('plymouth') == {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[-4.0196056, 50.3327426], [-4.0196056, 50.4441737], [-4.2055324, 50.4441737], [-4.2055324, 50.3327426], [-4.0196056, 50.3327426]]]}}

@pytest.mark.integration
@pytest.mark.flaky(reruns=NUM_RERUNS)
Expand Down Expand Up @@ -150,7 +150,7 @@ def test_cli_get_buildings_from_file_to_directory(aoi: Dict[str, Any], tmp_path:
input_path = tmp_path.joinpath("input.json")
with open(input_path, "w") as f:
json.dump(aoi, f)
subprocess.run(["ob", "get_buildings", str(input_path), str(tmp_path), "--country_iso", "SC"])
subprocess.run(["ob", "get_buildings", str(input_path), "--dst", str(tmp_path), "--country_iso", "SC"], check=True)
output_path = tmp_path.joinpath("buildings.json") # default file name
assert os.path.exists(output_path)
assert os.path.getsize(output_path) != 0
Expand All @@ -164,7 +164,7 @@ def test_cli_get_buildings_from_stdin_to_directory(aoi: Dict[str, Any], tmp_path
Verifies that a log message with timestamp gets written to stdout.
"""
# we can't use pipes (e.g. f"echo {json.dumps(aoi)} | ...") in subprocess.run, instead we pass the json as stdin using the input/text arguments,
process = subprocess.run([ "ob", "get_buildings", "-", str(tmp_path), "--country_iso", "SC"], input=json.dumps(aoi), text=True,check=True, capture_output=True)
process = subprocess.run([ "ob", "get_buildings", "-", "--dst", str(tmp_path), "--country_iso", "SC"], input=json.dumps(aoi), text=True, check=True, capture_output=True)
dt_regex = re.compile(r"^\[[0-9]{4}(-[0-9]{2}){2} ([0-9]{2}:){2}[0-9]{2}\] ") # match timestamp format e.g. "[2023-10-18 19:08:24]"
assert dt_regex.search(process.stdout) # ensure that stdout contains at least one timestamped message
output_path = tmp_path.joinpath("buildings.json") # default file name
Expand All @@ -180,7 +180,7 @@ def test_cli_get_buildings_from_stdin_to_file_silent(aoi: Dict[str, Any], tmp_pa
"""
output_path = tmp_path.joinpath("test123.json")
# we can't use pipes (e.g. f"echo {json.dumps(aoi)} | ...") in subprocess.run, instead we pass the json as stdin using the input/text arguments,
process = subprocess.run(["ob", "get_buildings", "-", str(output_path), "--silent", "--country_iso", "SC"], input=json.dumps(aoi), text=True, check=True, capture_output=True)
process = subprocess.run(["ob", "get_buildings", "-", "--dst", str(output_path), "--silent", "--country_iso", "SC"], input=json.dumps(aoi), text=True, check=True, capture_output=True)
assert process.stdout == "" # assert that nothing gets printed to stdout
assert process.stderr == "" # assert that nothing gets printed to stdout
assert os.path.exists(output_path)
Expand All @@ -198,8 +198,19 @@ def test_cli_get_buildings_from_stdin_to_file_overwrite_false(aoi: Dict[str, Any
with open(output_path, "w") as f:
f.write("Foo bar")
# we can't use pipes (e.g. f"echo {json.dumps(aoi)} | ...") in subprocess.run, instead we pass the json as stdin using the input/text arguments,
process = subprocess.run(["ob", "get_buildings", "-", str(output_path), "--country_iso", "SC"], input=json.dumps(aoi), text=True, check=True, capture_output=True)
process = subprocess.run(["ob", "get_buildings", "-", "--dst", str(output_path), "--country_iso", "SC"], input=json.dumps(aoi), text=True, check=True, capture_output=True)
assert os.path.exists(output_path)
with open(output_path, "r") as f:
assert f.read() == "Foo bar" # verify that the file still has the same content as before
assert "exists" in process.stdout # verify that the user has been warned about the existing file
assert "exists" in process.stdout # verify that the user has been warned about the existing file

@pytest.mark.integration
@pytest.mark.flaky(reruns=NUM_RERUNS)
def test_cli_get_buildings_geocode(tmp_path: Path):
"""
Tests the geocoding functionality, implemented as the argument "location".
"""
output_path = tmp_path.joinpath("geocode_test.json")
subprocess.run(["ob", "get_buildings", "--dst", str(output_path), "--location", "oxford uk", "--country_iso", "UK"], check=True)
assert os.path.exists(output_path)
assert os.path.getsize(output_path) != 0

0 comments on commit 7dc815c

Please sign in to comment.