def get_bbox(self, taxon_key: int) -> LatLonBBox: """ Given a taxon key, query the GBIF map API for a bounding box for the taxon. Args: taxon_key (int): The taxon ID to look up. Returns: LatLonBBox: bounding box for the taxon key. """ params = {"taxonKey": taxon_key} url = "v2/map/occurrence/density/capabilities.json" metadata = self.get_bbox_meta(url, params) left = metadata.left right = metadata.right top = metadata.top bottom = metadata.bottom bbox = LatLonBBox(left=left, top=top, right=right, bottom=bottom) return bbox
class TestFindTiles: @pytest.mark.parametrize( "bbox, start_zoom, end_zoom, name, am_invalid", [ ( LatLonBBox(east=175.781248, south=-42.032974, west=173.671878, north=-40.979897), 0, 8, "Wellington Test - general area test.", False, ), ( LatLonBBox( west=-179.99999999291, south=16.9397716157348, east=179.326113654898, north=71.9081724700314, ), 0, 1, # Ideally 2, but for the baseline, 1 is fine. "Bald Eagle - AM wrap with zoom.", True, ), ( LatLonBBox( west=-172.813477719954, south=-82.1269032464488, east=179.326113654898, north=-39.6895335169534, ), 0, 1, "Emperor Pengiun - Antartica.", False, # This technically doesn't quite cross the AM, I guess. ), ( LatLonBBox( west=-163.830324878759, south=47.02752144317, east=163.156438540747, north=71.9081724700314, ), 0, 1, "Gray-headed Chickadee- No AM wrap.", False, ), ( LatLonBBox( west=-91.965102149197, south=-1.38713438223174, east=-89.2701562968385, north=0.421740150964651, ), 0, 8, "Galapagos Pengiun - small area", False, ), ( LatLonBBox( west=-178.203369424671, south=-52.691212723642, east=179.326113654898, north=-28.7802470429875, ), 0, 1, # should be 4 with AM wrap. "Tui - AM wrap with more zoom.", True, ), ( LatLonBBox( west=-151.253910901085, south=4.9495734055138, east=5.05294853571131, north=63.6299576758096, ), 0, 2, "Gray Catbird - 3x1 line", False, ), ( LatLonBBox( west=63.4434420034802, south=6.76762999637114, east=109.257521493576, north=35.0816917166643, ), 0, 3, "Rufous Treepie", False, ), ( LatLonBBox( west=160.15, south=-53.38, east=-176.17, north=-25.13, ), 0, 4, "New Zealand well-formed AM cross - west > east.", False, ), ( LatLonBBox(west=165, north=-29, east=185, south=-53), 0, 4, "Tui - AM cross.", False, ), ], ) def test_bbox_to_tiles(self, bbox, start_zoom, end_zoom, name, am_invalid): res = bounding_box_to_tiles(bbox, start_zoom) print(f"res ({len(res)}):\n", res) if not res: assert am_invalid assert len(res) == 0 assert res.zoom is not None elif len(res) == 1: assert res[0].zoom == end_zoom assert not am_invalid else: assert res[0].zoom == end_zoom assert res[1].zoom == end_zoom assert not am_invalid
class TestTileArray: def create_tilearray(self, tids, imgs): tile_array = TileArray() for tid, img in zip(tids, imgs): tile = Tile(tid=tid, img=img) tile_array[tid] = tile return tile_array def test_creation(self, tile_ids, images): tile_array = self.create_tilearray(tile_ids, images) for tid, tile in tile_array.items(): assert tid in tile_ids pprint(tile_array) def test_creation_constructor(self, tile_ids, images): tile_array = TileArray.from_dict( {TileID(k): Tile(TileID(k), v) for k, v in zip(tile_ids, images)}) for tid in tile_ids: assert tid in tile_array pprint(tile_array) def test_set_name(self, tile_ids, images): ta = TileArray(name="testname") assert ta.name == "testname" ta.name = "newtestname" assert ta.name == "newtestname" ts2 = TileArray() ts2.name = "anothertestname" assert ts2.name == "anothertestname" @pytest.mark.parametrize( "dims", [ (2, 2), ], ) def test_dims(self, tile_ids, images, dims): tile_array = self.create_tilearray(tile_ids, images) assert tile_array.xy_dims == dims def test_mixed_zoom(self, tile_ids, images): fiddled_ids = [ TileID(z + 1 if x % 2 == 0 else z, x, y) for z, x, y in tile_ids ] with pytest.raises(TileArray.MixedZoomError): _ = self.create_tilearray(fiddled_ids, images) def test_change_zoom(self, tile_ids, images): tile_array = self.create_tilearray(tile_ids, images) with pytest.raises(TileArray.MixedZoomError): tile_array.zoom = tile_ids[0].z + 1 @pytest.mark.parametrize( "zoom, exc", [ ([1], None), ([1, 2], None), ([constants.max_zoom + 1], TileArray.ZoomRangeError), ([constants.min_zoom - 1], TileArray.ZoomRangeError), ], ) def test_change_zoom_empty(self, tile_ids, images, zoom, exc): print("zoom", zoom) print("exc", exc) tile_array = TileArray() for z in zoom: if exc: with pytest.raises(exc): tile_array.zoom = z else: tile_array.zoom = z @pytest.mark.parametrize( "test_line_ids, expected_sibling_ids", [ ( [ TileID(x=0, y=1, z=2), TileID(x=1, y=1, z=2), TileID(x=2, y=1, z=2) ], [ TileID(x=2, y=0, z=2), TileID(x=1, y=1, z=2), TileID(x=2, y=1, z=2), TileID(x=0, y=0, z=2), TileID(x=0, y=1, z=2), TileID(x=1, y=0, z=2), ], ), ( [ TileID(x=0, y=1, z=2), TileID(x=0, y=2, z=2), TileID(x=0, y=3, z=2) ], [ TileID(x=1, y=3, z=2), TileID(x=1, y=1, z=2), TileID(x=1, y=2, z=2), TileID(x=0, y=1, z=2), TileID(x=0, y=2, z=2), TileID(x=0, y=3, z=2), ], ), ( [TileID(x=0, y=0, z=3), TileID(x=0, y=1, z=3)], [ TileID(x=1, y=1, z=3), TileID(x=0, y=1, z=3), TileID(x=1, y=0, z=3), TileID(x=0, y=0, z=3), ], ), ( [TileID(x=0, y=0, z=4), TileID(x=1, y=0, z=4)], [ TileID(x=0, y=0, z=4), TileID(x=1, y=1, z=4), TileID(x=0, y=1, z=4), TileID(x=1, y=0, z=4), ], ), ( [TileID(x=3, y=0, z=5), TileID(x=4, y=0, z=5)], [ TileID(x=4, y=1, z=5), TileID(x=3, y=0, z=5), TileID(x=3, y=1, z=5), TileID(x=4, y=0, z=5), ], ), ( [TileID(x=3, y=3, z=6), TileID(x=3, y=4, z=6)], [ TileID(x=3, y=4, z=6), TileID(x=2, y=3, z=6), TileID(x=3, y=3, z=6), TileID(x=2, y=4, z=6), ], ), ( [TileID(x=-4, y=160, z=8), TileID(x=-3, y=160, z=8)], [ TileID(x=-4, y=160, z=8), TileID(x=-3, y=160, z=8), TileID(x=-4, y=161, z=8), TileID(x=-3, y=161, z=8), ], ), ], ) def test_find_filtered_siblings(self, test_line_ids, expected_sibling_ids, tile_ids, images): tile_array = self.create_tilearray( test_line_ids, [create_blank_image() for _ in range(len(test_line_ids))]) res = tile_array.find_line_sibling_tile_ids() assert isinstance(res, TileArray) for tid in expected_sibling_ids: assert tid in res.keys() def test_tilearray_from_tids(self, tile_ids, images): ta = empty_tilearray_from_ids(tile_ids) assert list(ta.keys()) == tile_ids assert [t.tid for t in ta.values()] == tile_ids @pytest.mark.parametrize( "result", [ LatLonBBox(left=-180.0, top=85.0511287798066, right=180.0, bottom=-85.0511287798066), LatLonBBox( left=4.85595703125, top=52.362183216744256, right=4.8779296875, bottom=52.34876318198808, ), ], ) @pytest.mark.skip("This passes, just needs to be parameterized correctly.") def test_tilearray_bbox(self, tile_ids, images, result): ta = empty_tilearray_from_ids(tile_ids) res = ta.bounds assert res == result @pytest.mark.parametrize( "result", [ (PixBbox(left=0, top=512, right=512, bottom=0), ), (PixBbox(left=4307456, top=5631488, right=4307968, bottom=5630976), ), ], ) @pytest.mark.skip("This passes, just needs to be parameterized correctly.") def test_tilearray_pixbbox(self, tile_ids, images, result): ta = empty_tilearray_from_ids(tile_ids) res = ta.pixel_bounds assert res == result
def bounding_box_to_tiles( bbox: geo.LatLonBBox, start_zoom: int = 0, size: int = 512, alt_bbox: bool = False ) -> Tuple["TileArray"]: """ Takes a bounding box and finds the TileArray that best covers that bounding box. This may result in tiles that are across the anti-meridian from the rest. The end result is a TileArray of either 4, 6 or 9 tiles that fully contain the bounding box. In the event that the bounding box crosses the antimeridian, will return two TileArrays, one for each side. While a TileArray can store non-contiguous tiles, this is easier for now. Args: bbox (LatLonBBox): Bounding box to find the tile covering for. start_zoom (int, optional): starting zoom level. Probably best to leave this as the default. Defaults to 0. size (int, optional): Size of the map, in pixels. Should be a multiple of 256. Defaults to 512. Returns: Tuple["TileArray"]: A TileArray covering the bounding box that isn't larger than size. If the antimeridian is crossed, this will return two TileArrays, one for each side, except in the case of zoom = 1, in which case all tiles are returned. If there's a bad bounding box, returns an empty TileArray. Eventually, this should try an alternative approach to finding the proper covering. """ n = bbox.north w = bbox.west s = bbox.south e = bbox.east zoom_level = start_zoom bad_bbox = False # Is this likely to be an incorrect bounding box that forgets that the map is actually a cynlinder? # This is really just a heuristic test for an incorrect bounding box, because it's hard to deal with bad data. if -180 < w < -178 and 178 < e < 180: print("Probable incorrect bounding box due to antimeridian crossing.") if alt_bbox: print("alt bounding box override.") else: bad_bbox = True if (w > e or abs(w) > 180 or abs(e) > 180) and not bad_bbox: print("Bbox crosses anti-meridian.") bbox_west, bbox_east = bbox.am_split() tile_west, alt_west, zoom_west = _bounding_box_candidates( bbox_west, zoom_level, size ) tile_east, alt_east, zoom_east = _bounding_box_candidates( bbox_east, zoom_level, size ) print("West:") pprint(tile_west) print("West alt:") pprint(alt_west) print("East:") pprint(tile_east) print("East alt:") pprint(alt_east) # If one bbox is tigher than the other, this is a problem. Only take the furthest out one. minimum_zoom = min(zoom_west, zoom_east) - 1 if zoom_west != zoom_east: print("Mixed zoom found for bbox!") print("bboxtt zoom:", zoom_west, zoom_east, minimum_zoom) # This may be the only fix, but it may need some edge case handling. # This is to avoid getting both bounding boxes showing tiles for the whole planet. Each tile should only be returned once. if minimum_zoom == 1: tids = [TileID(z=1, x=x, y=y) for x in (0, 1) for y in (0, 1)] ta = empty_tilearray_from_ids(tids) return (ta,) # Ignore the alts, because they're spurious for this case as a 2x2 split across the am will always trigger the alt for a 2x 2x2 bbox, which is wrong. # best_west = _best_tile_covering(bbox_west, tile_west, alt_west, minimum_zoom) # best_east = _best_tile_covering(bbox_east, tile_east, alt_east, minimum_zoom) best_west = _best_tile_covering(bbox_west, tile_west, TileArray(), minimum_zoom) best_east = _best_tile_covering(bbox_east, tile_east, TileArray(), minimum_zoom) return best_west, best_east else: tile_ids, tile_ids_alt, end_zoom_level = _bounding_box_candidates( bbox, zoom_level, size ) best = _best_tile_covering(bbox, tile_ids, tile_ids_alt, end_zoom_level) if not bad_bbox: return (best,) else: return TileArray(zoom_level=end_zoom_level)
class TestGBIF: gbif: Any = GBIF() @pytest.mark.vcr("new") @pytest.mark.parametrize( "species, taxon_id", [ ("Bushtit", ("Psaltriparus minimus", 2494988)), ("Barn Swallow", ("Hirundo rustica", 9515886)), ("Anna's Hummingbird", ("Calypte anna", 2476674)), ("Hirundo rustica", ("Hirundo rustica", 9515886)), ], ) def test_gbif_lookup(self, species, taxon_id): res = self.gbif.lookup_species(species) assert res == taxon_id @pytest.mark.vcr("new") @pytest.mark.parametrize( "taxon_id, lat_lon_bbox", [ (2494988, LatLonBBox(right=14, top=55, left=-127, bottom=15)), (5232445, LatLonBBox(right=10, top=54, left=-161, bottom=19)), (5228134, LatLonBBox(right=0, top=-38, left=-13, bottom=-38)), ], ) def test_get_bbox(self, taxon_id, lat_lon_bbox): res = self.gbif.get_bbox(taxon_id) assert res == lat_lon_bbox @pytest.mark.vcr("new") @pytest.mark.parametrize( "map_type", [ "hex", "square", ], ) @pytest.mark.parametrize( "taxon_id, tile_size", [ (9515886, None), (9515886, 256), (2494988, 512), ], ) def test_get_map_tile(self, taxon_id, tile_size, map_type): if map_type == "hex": gb = self.gbif.get_hex_tile(tile_id=TileID(0, 0, 0), taxonKey=taxon_id, tile_size=tile_size) elif map_type == "square": gb = self.gbif.get_square_tile(tile_id=TileID(0, 0, 0), taxonKey=taxon_id, tile_size=tile_size) # else: # gb = self.gbif.get_tile(tile_id=TileID(0, 0, 0), taxonKey=taxon_id, tile_size=tile_size) gb.name = f"test_{taxon_id}-{map_type}-s{tile_size // 512 if tile_size is not None else 1}" exp = tile_size if tile_size is not None else 512 assert gb.resolution == exp def test_high_res_override(self): assert mapbox.high_res is True res = mapbox.get_tiles([TileID(0, 0, 0)], high_res=False) assert res[TileID(0, 0, 0)].resolution == 256 res2 = mapbox.get_tiles([TileID(0, 0, 0)]) assert res2[TileID(0, 0, 0)].resolution == 512 @pytest.mark.vcr("new") @pytest.mark.parametrize( "map_type", [ "hex", "square", ], ) @pytest.mark.parametrize( "taxon_id, tile_ids_expected_image", [ ( 2482552, { TileID(7, 121, 26): True, TileID(7, 121, 27): True, TileID(7, 120, 27): True, TileID(7, 120, 26): False, }, ), ], ) def test_get_tilearray(self, taxon_id, tile_ids_expected_image, map_type): tile_ids = tile_ids_expected_image.keys() ta = TileArray.from_dict({x: Tile(x) for x in tile_ids}) ta = self.gbif.get_tiles(taxon_id, ta, mode=map_type) print(ta) for k, t in ta.items(): t.save() print("IE", k, tile_ids_expected_image[k]) print("timg", t.img)
class TestEbird: ebird: eBirdMap = eBirdMap() mapbox: MapBox = MapBox(token=get_token(), high_res=False) @pytest.mark.vcr("new") @pytest.mark.parametrize( "species_code, expected_bbox", [ ( "bushti", LatLonBBox( -128.796028798097, 51.8596628170432, -89.2701562968385, 14.126239979566, ), ), ( "tui1", LatLonBBox( -178.203369424671, -28.7802470429875, 179.326113654898, -52.691212723642, ), ), ( "", None, ), ( "redcro9", LatLonBBox( -115.321299536305, 43.2077783892461, -113.524668968066, 41.9867319031071, ), ), ], ) def test_get_bbox(self, species_code, expected_bbox): res = self.ebird.get_bbox(species_code) assert res == expected_bbox @pytest.mark.vcr("new") @pytest.mark.parametrize( "species_code", [ "bushti", ], ) def test_get_rsid(self, species_code): res = self.ebird.get_rsid(species_code) assert res @pytest.mark.vcr("new") @pytest.mark.parametrize( "tile_id, rsid", [ ( TileID(0, 0, 0), "RS108970032", ), ], ) def test_get_tile(self, tile_id, rsid): res = self.ebird.download_tile(tile_id, rsid) assert res.img @pytest.mark.vcr("new") @pytest.mark.parametrize( "species_code, expected_ids", [( "tui1", [(4, 15, 9), (4, 15, 10), (4, 0, 9), (4, 0, 10)], )], ) def test_get_tiles(self, species_code, expected_ids): res = self.ebird.get_tiles(species_code) print("res:\n", res) for x in res: for t in x.values(): t.save() for e in res: for x in e: assert tuple(x) in expected_ids @pytest.mark.vcr("new") @pytest.mark.parametrize( "species_code, size, no_data", [ ("tui1", 512, False), ("bushti", 512, False), ("pilwoo", 512, False), ("inirai1", 512, False), ("bkpwar", 512, False), ("baleag", 512, False), ("grycat", 512, False), ("kinpen1", 512, False), ("carchi", 512, False), ("arcter", 512, False), ("dodo1", 512, True), ("pifgoo", 512, False), ], ) def test_map_final(self, species_code, size, no_data): res, res_no_data = self.ebird.make_map(species_code, self.mapbox, size) assert res_no_data == no_data res.save(f"final-ebird-{species_code}_{size}.png")
class TestLatLonBbox: @pytest.mark.parametrize( "latlon_in, latlon_out, zoom", [ ((28.304380682962783, -15.468750000000012), (28.3, -15.5), 0), ((28.304380682962783, -15.468750000000012), (28.30, -15.47), 5), ((28.304380682962783, -15.468750000000012), (28.3044, -15.4688), 12), ((0.0, 0.0), (0.0, 0.0), 42), ((-89.86367491884421, 75.43308649874739), (-89.8637, 75.4331), 12), ], ) def test_truncate_precision(self, latlon_in, latlon_out, zoom): latlon_in = LatLon(*latlon_in) res = geo.truncate_latlon_precision(latlon_in, zoom) assert res == LatLon(*latlon_out) @pytest.mark.parametrize( "roundtrip", [False, True], ) @pytest.mark.parametrize( "pixels, latlon, zoom, tile_size", [ ((245, 153), (-33.1, 164.5), 0, 256), ((256, 173), (-53.3, 180.0), 0, 256), ((1, 149), (-28.3, -178.6), 0, 256), ((4, 164), (-45.1, -174.4), 0, 256), ((0, 0), (85.1, -180.0), 0, 256), ((128, 128), (0.0, 0.0), 0, 256), ((256, 256), (-85.1, 180.0), 0, 256), ((987, 808), (-71.5, 167.0), 0, 1024), ((525, 761), (41.9, -87.7), 3, 256), ], ) def test_pixels_to_lat_lon(self, pixels, latlon, zoom, tile_size, roundtrip): pixels = Pixel(*pixels) latlon = LatLon(*latlon) print(pixels, latlon, zoom, tile_size, roundtrip) result_lat_lon = geo.pixels_to_lat_lon(pixels, zoom, tile_size) result_pixels = geo.lat_lon_to_pixels(result_lat_lon, zoom, tile_size) assert result_lat_lon == latlon if not roundtrip: assert result_pixels == pixels @pytest.mark.parametrize( "latlon, pixels, zoom", [ (LatLon(lat=6.7, lon=109.2), Pixel(206, 123), 0), ], ) def test_lat_lon_to_pixels(self, latlon, zoom, pixels): res = geo.lat_lon_to_pixels(latlon, zoom) assert res == pixels # res2 = geo.pixels_to_lat_lon(pixels, zoom) # assert res2 == latlon @pytest.mark.parametrize( "bbox, other_bbox, result", [ ( LatLonBBox(left=-180.0, right=180.0, top=90.0, bottom=-90.0), LatLonBBox(left=-180.0, right=180.0, top=90.0, bottom=-90.0), True, ), ( LatLonBBox(left=-90.0, right=90.0, top=45.0, bottom=-45.0), LatLonBBox(left=-180.0, right=180.0, top=90.0, bottom=-90.0), False, ), ( LatLonBBox(left=-90.0, right=90.0, top=45.0, bottom=-45.0), LatLonBBox(left=-90.0, right=90.0, top=44.99, bottom=-45.0), True, ), ], ) def test_contains(self, bbox, other_bbox, result): assert bbox.contains(other_bbox) == result @pytest.mark.parametrize( "pixels_bbox, zoom, tile_size, truncate, expected", [ ( PixBbox(0, 0, 255, 255), 0, 256, True, LatLonBBox(bottom=-84.9, left=-180.0, top=85.1, right=178.6), ), # (PixBbox(1, 149, 256, 173), 0, 256, True, LatLonBBox(0, 0, 0, 0)), ], ) def test_pixel_bbox_to_latlon_bbox( self, pixels_bbox, zoom, tile_size, truncate, expected ): res = geo.bounding_pixels_to_lat_lon(pixels_bbox, zoom, tile_size, truncate) assert res == expected @pytest.mark.parametrize( "latlon_tl, latlon_br", [ (LatLon(lat=45, lon=90), LatLon(lat=-45, lon=-90)), (LatLon(lat=90, lon=180), LatLon(lat=-90, lon=-180)), (LatLon(lat=90, lon=180), LatLon(lat=0, lon=0)), (LatLon(lat=0, lon=0), LatLon(lat=-90, lon=-180)), ], ) def test_bbox_lat_clamp(self, latlon_tl, latlon_br): test_bbox = LatLonBBox( left=latlon_tl.lon, top=latlon_tl.lat, right=latlon_br.lon, bottom=latlon_br.lat, ) test_bbox.clamp_lat() print(test_bbox) assert test_bbox.top <= constants.max_latitude assert test_bbox.bottom >= -constants.max_latitude
def test_property_direct(self): a = LatLonBBox(20.0, 40.0, 40.0, -40.0).center assert a == LatLon(30.0, 0.0) assert type(a) == LatLon
def test_area_ni(self): with pytest.raises(NotImplementedError): _ = LatLonBBox(12, 45, 39, 124).area
def test_property_aliases(self): bbx = LatLonBBox(-110.39, 24.06, -110.25, 24.17) assert bbx.xy_dims == bbx.range
def test_comparison_srs(self, in_bbox, srs): assert LatLonBBox(*in_bbox) != LatLonBBox(*in_bbox, srs=srs)
def test_comparison(self, in_bbox, comp, not_eq): res_bbox = LatLonBBox(*in_bbox) if not_eq: assert res_bbox != tuple(reversed(in_bbox)) else: assert res_bbox == comp
def test_creation(self, in_bbox): res_bbox = LatLonBBox(*in_bbox) assert res_bbox == in_bbox
class TestLatLonBbox: @pytest.mark.parametrize( "in_bbox", [ (-54.75, -68.25, -54.85, -68.35), ], ) def test_creation(self, in_bbox): res_bbox = LatLonBBox(*in_bbox) assert res_bbox == in_bbox @pytest.mark.parametrize( "not_eq", [ True, False, ], ) @pytest.mark.parametrize( "in_bbox, comp", [ ( ( -90.0, -45.0, 90.0, 45.0, ), (-90.0, -45.0, 90.0, 45.0), ), ( ( 1, 2, 3, 4, ), [1, 2, 3, 4], ), ( ( 1, 2, 3, 4, ), LatLonBBox(1, 2, 3, 4), ), ], ) def test_comparison(self, in_bbox, comp, not_eq): res_bbox = LatLonBBox(*in_bbox) if not_eq: assert res_bbox != tuple(reversed(in_bbox)) else: assert res_bbox == comp @pytest.mark.parametrize( "in_bbox, srs", [ ((-54.75, -68.25, -54.85, -68.35), "EPSG:4326"), ], ) def test_comparison_srs(self, in_bbox, srs): assert LatLonBBox(*in_bbox) != LatLonBBox(*in_bbox, srs=srs) @pytest.mark.parametrize( "in_bbox", [ (1, 2, 3, 4), ], ) def test_get_set_alt(self, in_bbox): res_bbox = LatLonBBox(*in_bbox) assert res_bbox.maxlat == in_bbox[1] res_bbox.left = 7 assert res_bbox.left == 7 assert res_bbox.west == 7 res_bbox.minx = 3 assert res_bbox.left == 3 assert res_bbox.west == 3 def test_property_aliases(self): bbx = LatLonBBox(-110.39, 24.06, -110.25, 24.17) assert bbx.xy_dims == bbx.range def test_property_direct(self): a = LatLonBBox(20.0, 40.0, 40.0, -40.0).center assert a == LatLon(30.0, 0.0) assert type(a) == LatLon @pytest.mark.parametrize( "in_bbox, prop, res", [ (LatLonBBox(-180.0, 90.0, 180.0, -90.0), "tl", LatLon(90.0, -180.0)), (LatLonBBox(-180.0, 90.0, 180.0, -90.0), "br", LatLon(-90.0, 180)), (LatLonBBox(-54.75, -68.25, -54.85, -68.35), "tl", LatLon(-68.25, -54.75)), (LatLonBBox(-54.75, -68.25, -54.85, -68.35), "br", LatLon(-68.35, -54.85)), (LatLonBBox(-54.75, -68.25, -54.85, -68.35), "xy_dims", (0.1, 0.1)), (LatLonBBox(20.0, 40.0, 40.0, -40.0), "xy_dims", (20.0, 80.0)), (LatLonBBox(1.0, 2.0, 1.0, 4.0), "xy_dims", (0.0, 2.0)), ( LatLonBBox(-54.75, -68.25, -54.85, -68.35), "center", LatLon(-54.80, -68.30), ), ], ) def test_properties(self, in_bbox, prop, res): a = object.__getattribute__(in_bbox, prop) assert pytest.approx(a) == res assert type(a) == type(res) def test_area_ni(self): with pytest.raises(NotImplementedError): _ = LatLonBBox(12, 45, 39, 124).area @pytest.mark.parametrize( "latlon, zoom, pixels", [ ( LatLonBBox(west=-180.0, north=85.0, east=180.0, south=-85.0), 0, PixBbox(left=0, top=0, right=256, bottom=256), ), ( LatLonBBox(-54.75, -68.25, -54.85, -68.35), 4, PixBbox(1425, 3123, 1424, 3126), ), ( LatLonBBox(-54.75, 68.25, 54.85, -68.35), 5, PixBbox(2850, 1945, 5344, 6253), ), ( LatLonBBox(20.0, 40.0, 40.0, -40.0), 0, PixBbox(142, 97, 156, 159), ), ( LatLonBBox(-54.75, -68.25, -54.85, -68.35), 15, PixBbox(2918537, 6396732, 2916206, 6403034), ), ], ) def test_latlon_to_pixels(self, latlon, zoom, pixels): res = geo.bounding_lat_lon_to_pixels(latlon, zoom) print("res", res) assert [a for a in res] == [a for a in pixels] # coordinate origin is lower left corner. # make sure the results are in this order. assert res.left >= res.left assert res.top <= res.bottom @pytest.mark.parametrize( "latlon, result", [ ( LatLonBBox(north=45.0, south=-45.0, west=-90.0, east=90.0), None, ), ( LatLonBBox(north=45.0, south=-45.0, west=90.0, east=-90.0), ( LatLonBBox(north=45.0, south=-45.0, west=90.0, east=180.0), LatLonBBox(north=45.0, south=-45.0, west=-180.0, east=-90.0), ), ), ( LatLonBBox(left=165, top=-29, right=185, bottom=-53), ( LatLonBBox(left=165, top=-29, right=180.0, bottom=-53), LatLonBBox(left=-180.0, top=-29, right=-175, bottom=-53), ), ), ( LatLonBBox(left=-185, top=-29, right=-165, bottom=-53), ( LatLonBBox(left=175.0, top=-29, right=180, bottom=-53), LatLonBBox(left=-180, top=-29, right=-165, bottom=-53), ), ), ( LatLonBBox(left=99, top=72, right=379, bottom=9), ( LatLonBBox(left=99, top=72, right=180, bottom=9), LatLonBBox(left=-180, top=72, right=19, bottom=9), ), ), ], ) def test_antimeridian_split(self, latlon, result): res = latlon.am_split() if res is None: assert res == result else: assert res[0] == result[0] assert res[1] == result[1]