@@ -25,7 +25,7 @@ def generate_occurrence_cluster(
2525 res .append ((latitude + y , longitude + x ))
2626 return res
2727
28- def geojson_of_shaps (shapes ):
28+ def geojson_of_shapes (shapes , projection : str = "epsg:4326" ):
2929 features = []
3030 for geom in shapes :
3131 feature = {
@@ -37,11 +37,18 @@ def geojson_of_shaps(shapes):
3737
3838 geojson = {
3939 "type" : "FeatureCollection" ,
40+ "crs" : { "type" : "name" , "properties" : { "name" : "urn:ogc:def:crs:" + ("::" .join (projection .split (":" ))) } },
4041 "features" : features
4142 }
4243 return geojson
4344
44- def generate_faux_aoh (filename : Path , aoh_radius :float = 5.0 , range_radius :float = 10.0 ) -> None :
45+ def generate_faux_aoh (
46+ filename : Path ,
47+ aoh_radius : float = 5.0 ,
48+ range_radius : float = 10.0 ,
49+ projection : str = "epsg:4326" ,
50+ pixel_scale : float = 0.1 ,
51+ ) -> None :
4552 aoh_shapes = [
4653 Polygon ([
4754 (- aoh_radius , aoh_radius ),
@@ -64,19 +71,19 @@ def generate_faux_aoh(filename: Path, aoh_radius:float=5.0, range_radius:float=1
6471
6572 assert aoh_area <= range_area
6673
67- geojson_path = filename .with_suffix ( '. geojson' )
74+ geojson_path = filename .parent / f"range_ { filename . stem [ 4 :] } . geojson"
6875 with open (geojson_path , 'w' , encoding = "UTF-8" ) as f :
69- json .dump (geojson_of_shaps (range_shapes ), f , indent = 2 )
76+ json .dump (geojson_of_shapes (range_shapes , projection ), f , indent = 2 )
7077
7178 json_path = filename .with_suffix ('.json' )
7279 with open (json_path , 'w' , encoding = 'utf-8' ) as f :
7380 json .dump ({'prevalence' : aoh_area / range_area }, f )
7481
7582 with tempfile .TemporaryDirectory () as tmpdir :
76- aoh_geojson = Path (tmpdir ) / "test .geojson"
83+ aoh_geojson = Path (tmpdir ) / f"aoh_ { filename . stem [ 4 :] } .geojson"
7784 with open (aoh_geojson , 'w' , encoding = "UTF-8" ) as f :
78- json .dump (geojson_of_shaps (aoh_shapes ), f , indent = 2 )
79- with yg .read_shape (aoh_geojson , ("epsg:4326" , (0.1 , - 0.1 ))) as shape_layer :
85+ json .dump (geojson_of_shapes (aoh_shapes , projection ), f , indent = 2 )
86+ with yg .read_shape (aoh_geojson , (projection , (pixel_scale , - pixel_scale ))) as shape_layer :
8087 shape_layer .to_geotiff (filename )
8188
8289@pytest .mark .parametrize ("taxon_id,latitude,longitude,is_valid,expected_outlier" ,[
@@ -98,7 +105,7 @@ def test_simple_match_in_out_range(
98105 tmpdir_path = Path (tmpdir )
99106
100107 for test_id in [41 , 42 , 43 ]:
101- aoh_path = tmpdir_path / f"{ test_id } _RESIDENT .tif"
108+ aoh_path = tmpdir_path / f"aoh_T { test_id } A1_RESIDENT .tif"
102109 generate_faux_aoh (aoh_path )
103110
104111 occurences = generate_occurrence_cluster (latitude , longitude , 20 , 2.0 )
@@ -135,7 +142,7 @@ def test_bilinear_interprolation_lat(
135142) -> None :
136143 with tempfile .TemporaryDirectory () as tmpdir :
137144 tmpdir_path = Path (tmpdir )
138- aoh_path = tmpdir_path / "42_RESIDENT .tif"
145+ aoh_path = tmpdir_path / "aoh_T42A1_RESIDENT .tif"
139146 generate_faux_aoh (aoh_path )
140147
141148 # The AOH by default is 100x100 pixels, in 0.1 degree increments from -5.0 to 5.0
@@ -180,7 +187,7 @@ def test_bilinear_interprolation_lng(
180187) -> None :
181188 with tempfile .TemporaryDirectory () as tmpdir :
182189 tmpdir_path = Path (tmpdir )
183- aoh_path = tmpdir_path / "42_RESIDENT .tif"
190+ aoh_path = tmpdir_path / "aoh_T42A1_RESIDENT .tif"
184191 generate_faux_aoh (aoh_path )
185192
186193 # The AOH by default is 100x100 pixels, in 0.1 degree increments from -5.0 to 5.0
@@ -215,7 +222,7 @@ def test_occurrence_prevalence_of_one(
215222 tmpdir_path = Path (tmpdir )
216223
217224 for test_id in [41 , 42 , 43 ]:
218- aoh_path = tmpdir_path / f"{ test_id } _RESIDENT .tif"
225+ aoh_path = tmpdir_path / f"aoh_T { test_id } A1_RESIDENT .tif"
219226 generate_faux_aoh (aoh_path , aoh_radius = 5.0 , range_radius = 5.0 )
220227
221228 occurences = generate_occurrence_cluster (latitude , longitude , 20 , 2.0 )
@@ -237,7 +244,7 @@ def test_no_aoh_found() -> None:
237244 tmpdir_path = Path (tmpdir )
238245
239246 for test_id in [41 , 42 , 43 ]:
240- aoh_path = tmpdir_path / f"{ test_id } _RESIDENT .tif"
247+ aoh_path = tmpdir_path / f"aoh_T { test_id } A1_RESIDENT .tif"
241248 generate_faux_aoh (aoh_path )
242249
243250 df = pd .DataFrame (
@@ -265,7 +272,7 @@ def test_find_seasonal() -> None:
265272 tmpdir_path = Path (tmpdir )
266273
267274 for season in ['breeding' , 'nonbreeding' ]:
268- aoh_path = tmpdir_path / f"42_ { season } .tif"
275+ aoh_path = tmpdir_path / f"aoh_T42A1a_ { season } .tif"
269276 generate_faux_aoh (aoh_path )
270277
271278 df = pd .DataFrame (
@@ -280,3 +287,44 @@ def test_empty_species_list() -> None:
280287 df = pd .DataFrame ([], columns = ['iucn_taxon_id' , 'decimalLatitude' , 'decimalLongitude' ])
281288 with pytest .raises (ValueError ):
282289 _ = process_species (Path ("/some/aohs" ), Path ("/some/aohs" ), df )
290+
291+ @pytest .mark .parametrize ("taxon_id,latitude,longitude,expected_prev,is_valid,expected_outlier" ,[
292+ (42 , 0.0 , 0.0 , 1.0 , True , False ), # all in AoH
293+ (42 , 0.0 , 20.0 , None , False , None ), # all out of range
294+ ])
295+ def test_aoh_not_in_wgs84 (
296+ taxon_id : int ,
297+ latitude : float ,
298+ longitude : float ,
299+ expected_prev : float ,
300+ is_valid : bool ,
301+ expected_outlier : bool ,
302+ ) -> None :
303+ with tempfile .TemporaryDirectory () as tmpdir :
304+ tmpdir_path = Path (tmpdir )
305+
306+ for test_id in [41 , 42 , 43 ]:
307+ aoh_path = tmpdir_path / f"aoh_T{ test_id } A1_RESIDENT.tif"
308+ # Generate an AOH raster and GeoJSON in ESRI:54009
309+ generate_faux_aoh (
310+ aoh_path ,
311+ aoh_radius = 100000.0 ,
312+ range_radius = 100000.0 ,
313+ projection = "esri:54009" ,
314+ pixel_scale = 100.0
315+ )
316+
317+ # Occurrences are still in EPSG:4326, as that's what GBIF gives us
318+ occurences = generate_occurrence_cluster (latitude , longitude , 20 , 0.5 )
319+ df = pd .DataFrame (
320+ [(taxon_id , lat , lng ) for (lat , lng ) in occurences ],
321+ columns = ['iucn_taxon_id' , 'decimalLatitude' , 'decimalLongitude' ]
322+ )
323+
324+ result = process_species (tmpdir_path , tmpdir_path , df )
325+ assert result .iucn_taxon_id == taxon_id
326+ assert result .total_records == len (occurences )
327+ assert result .point_prevalence == expected_prev
328+ assert result .model_prevalence == 1.0
329+ assert result .is_valid == is_valid
330+ assert result .is_outlier == expected_outlier
0 commit comments