aeiche01 commited on
Commit
e8701ee
·
verified ·
1 Parent(s): 34395e5

Update pages/_common_m.py

Browse files
Files changed (1) hide show
  1. pages/_common_m.py +42 -35
pages/_common_m.py CHANGED
@@ -22,27 +22,19 @@ IUCN_ASSET = f"{EE_LAMER_ROOT}/iucn_ranges"
22
  # =============================================================================
23
 
24
  RESIDENT_PARTS = {
25
- "M_adj": ["M_adj_part1", "M_adj_part2", "M_adj_part3"],
26
- "M_noadj": ["M_noadj_part1", "M_noadj_part2"],
27
- "M_buffer1deg": ["M_buffer1deg_part1", "M_buffer1deg_part2"],
28
- "M_occsrangehull": ["M_occsrangehull_part1", "M_occsrangehull_part2"],
29
  }
30
 
31
  MIGRANT_PARTS = {
32
- ("breeding","M_adj"): ["breeding_M_adj"],
33
- ("breeding","M_noadj"): ["breeding_M_noadj"],
34
- ("breeding","M_buffer1deg"): ["breeding_M_buffer1deg"],
35
- ("breeding","M_occsrangehull"): ["breeding_M_occsrangehull"],
36
-
37
- ("nonbreeding","M_adj"): ["nonbreeding_M_adj_part1","nonbreeding_M_adj_part2"],
38
- ("nonbreeding","M_noadj"): ["nonbreeding_M_noadj_part1","nonbreeding_M_noadj_part2"],
39
- ("nonbreeding","M_buffer1deg"): ["nonbreeding_M_buffer1deg_part1","nonbreeding_M_buffer1deg_part2"],
40
- ("nonbreeding","M_occsrangehull"): ["nonbreeding_M_occsrangehull_part1","nonbreeding_M_occsrangehull_part2"],
41
-
42
- ("year_round","M_adj"): ["year_round_M_adj"],
43
- ("year_round","M_noadj"): ["year_round_M_noadj"],
44
- ("year_round","M_buffer1deg"): ["year_round_M_buffer1deg"],
45
- ("year_round","M_occsrangehull"): ["year_round_M_occsrangehull"],
46
  }
47
 
48
  # =============================================================================
@@ -50,13 +42,11 @@ MIGRANT_PARTS = {
50
  # =============================================================================
51
 
52
  M_METADATA = {
53
- "M_adj": ("M - Adjacent Ecoregions", "Ecoregions adjacent to occurrences"),
54
- "M_noadj": ("M - No Adjacent", "Only ecoregions with occurrences"),
55
- "M_buffer1deg": ("M - 1° Buffer", "Ecoregions within 1° buffer"),
56
- "M_occsrangehull": ("M - Occurrence Hull", "Convex hull"),
57
  }
58
 
59
- M_ORDER = ["M_adj","M_noadj","M_buffer1deg","M_occsrangehull"]
60
  BIRD_TYPES = ["Residents", "Migrants"]
61
  SEASONS = ["breeding", "nonbreeding", "year_round"]
62
  PASS_DECISIONS = ["pass","reviewed_pass","proxim_pass"]
@@ -85,6 +75,7 @@ center = solara.reactive([0, -70])
85
 
86
  reviewer_name = solara.reactive("")
87
  reviewer_notes = solara.reactive("")
 
88
 
89
  submission_status = solara.reactive("idle")
90
  submission_result = solara.reactive(None)
@@ -131,16 +122,18 @@ def get_species_list(btype=None, season=None):
131
 
132
  def get_m_fc(hyp, species, btype, season=None):
133
  if btype == "Residents":
134
- parts = RESIDENT_PARTS[hyp]
135
  root = EE_RESIDENT_ROOT
136
  filt = ee.Filter.eq("species", species)
137
  else:
138
- parts = MIGRANT_PARTS[(season, hyp)]
139
  root = EE_MIGRANT_ROOT
140
  filt = ee.Filter.And(
141
  ee.Filter.eq("species", species),
142
  ee.Filter.eq("season", season),
143
  )
 
 
144
  fcs = [ee.FeatureCollection(f"{root}/{p}") for p in parts]
145
  fc = ee.FeatureCollection(fcs).flatten() if len(fcs) > 1 else fcs[0]
146
  return fc.filter(filt)
@@ -154,6 +147,18 @@ def compute_eco_color_map(species, btype=None, season=None):
154
  ecos.update([e for e in vals if e])
155
  return {eco: ECO_PALETTE[i % len(ECO_PALETTE)] for i, eco in enumerate(sorted(ecos))}
156
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def get_eco_geojson(hypothesis, species, btype=None, season=None, eco_color_map=None):
158
  try:
159
  fc = get_m_fc(hypothesis, species, btype, season)
@@ -185,7 +190,8 @@ def get_m_bounds(hypothesis, species, btype=None, season=None):
185
  # =============================================================================
186
 
187
  def create_m_map(hypothesis, species, eco_color_map, btype=None, season=None):
188
- m = geemap.Map(zoom=zoom.value, center=center.value, toolbar_control=False, lite_mode=True)
 
189
  geojson = get_eco_geojson(hypothesis, species, btype, season, eco_color_map)
190
  if geojson:
191
  m.add_geojson(geojson, layer_name="M regions", info_mode="on_click")
@@ -193,17 +199,20 @@ def create_m_map(hypothesis, species, eco_color_map, btype=None, season=None):
193
  occ = ee.FeatureCollection(get_pres_backg_asset(btype, season)).filter(ee.Filter.eq("species", species))
194
  fail = occ.filter(ee.Filter.inList("final_decision", FAIL_DECISIONS))
195
  passed = occ.filter(ee.Filter.inList("final_decision", PASS_DECISIONS))
196
- m.addLayer(fail.style(**{"color": "ff4444", "pointSize": 6}), {}, "Fail")
197
- m.addLayer(passed.style(**{"color": "2ca02c", "pointSize": 6}), {}, "Pass")
 
 
 
198
  except Exception as e:
199
  print("[WARN points]", e)
200
  return m
201
 
202
  # =============================================================================
203
- # SUBMISSION (FIXED FOR YOUR COLUMNS)
204
  # =============================================================================
205
 
206
- def submit_review(hypothesis, species, reviewer, notes, btype, season=None):
207
  """Submits review data to Airtable using specific column names."""
208
  api_key = os.environ.get("AIRTABLE_API_KEY")
209
  base_id = os.environ.get("AIRTABLE_BASE_ID")
@@ -218,15 +227,14 @@ def submit_review(hypothesis, species, reviewer, notes, btype, season=None):
218
  "Content-Type": "application/json"
219
  }
220
 
221
- # Mapping to your confirmed column names:
222
- # Reviewer, Species, Selected_Hypothesis, Notes
223
- # (Timestamp is usually handled automatically by Airtable if set to 'Created Time')
224
  data = {
225
  "fields": {
226
  "Reviewer": reviewer,
227
  "Species": species,
228
  "Selected_Hypothesis": hypothesis,
229
- "Notes": notes
 
230
  }
231
  }
232
 
@@ -234,7 +242,6 @@ def submit_review(hypothesis, species, reviewer, notes, btype, season=None):
234
  response = requests.post(url, headers=headers, json=data)
235
  if response.status_code != 200:
236
  error_info = response.json().get("error", {})
237
- # Provides the specific reason Airtable rejected the data
238
  msg = error_info.get("message", "Unknown error") if isinstance(error_info, dict) else error_info
239
  return {"success": False, "error": f"Airtable Error: {msg}"}
240
 
 
22
  # =============================================================================
23
 
24
  RESIDENT_PARTS = {
25
+ "M": ["M"],
26
+ "M_ecoregions": ["M_ecoregions"],
 
 
27
  }
28
 
29
  MIGRANT_PARTS = {
30
+ ("breeding","M"): ["breeding_M"],
31
+ ("breeding","M_ecoregions"): ["breeding_M_ecoregions"],
32
+
33
+ ("nonbreeding","M"): ["nonbreeding_M"],
34
+ ("nonbreeding","M_ecoregions"): ["nonbreeding_M_ecoregions"],
35
+
36
+ ("year_round","M"): ["year_round_M"],
37
+ ("year_round","M_ecoregions"): ["year_round_M_ecoregions"],
 
 
 
 
 
 
38
  }
39
 
40
  # =============================================================================
 
42
  # =============================================================================
43
 
44
  M_METADATA = {
45
+ "M": ("M", "M Hypothesis"),
46
+ "M_ecoregions": ("M Ecoregions", "Restricted by Ecoregions"),
 
 
47
  }
48
 
49
+ M_ORDER = ["M", "M_ecoregions"]
50
  BIRD_TYPES = ["Residents", "Migrants"]
51
  SEASONS = ["breeding", "nonbreeding", "year_round"]
52
  PASS_DECISIONS = ["pass","reviewed_pass","proxim_pass"]
 
75
 
76
  reviewer_name = solara.reactive("")
77
  reviewer_notes = solara.reactive("")
78
+ reviewer_ecoregions = solara.reactive("")
79
 
80
  submission_status = solara.reactive("idle")
81
  submission_result = solara.reactive(None)
 
122
 
123
  def get_m_fc(hyp, species, btype, season=None):
124
  if btype == "Residents":
125
+ parts = RESIDENT_PARTS.get(hyp, [])
126
  root = EE_RESIDENT_ROOT
127
  filt = ee.Filter.eq("species", species)
128
  else:
129
+ parts = MIGRANT_PARTS.get((season, hyp), [])
130
  root = EE_MIGRANT_ROOT
131
  filt = ee.Filter.And(
132
  ee.Filter.eq("species", species),
133
  ee.Filter.eq("season", season),
134
  )
135
+ if not parts:
136
+ return ee.FeatureCollection([])
137
  fcs = [ee.FeatureCollection(f"{root}/{p}") for p in parts]
138
  fc = ee.FeatureCollection(fcs).flatten() if len(fcs) > 1 else fcs[0]
139
  return fc.filter(filt)
 
147
  ecos.update([e for e in vals if e])
148
  return {eco: ECO_PALETTE[i % len(ECO_PALETTE)] for i, eco in enumerate(sorted(ecos))}
149
 
150
+ def get_species_ecoregions(species, btype=None, season=None):
151
+ """Fetches unique ecoregions from the M_ecoregions hypothesis layer."""
152
+ if not species: return ""
153
+ try:
154
+ fc = get_m_fc("M_ecoregions", species, btype, season)
155
+ vals = fc.aggregate_array("ECO_NAME").distinct().getInfo()
156
+ valid_vals = [str(e) for e in vals if e]
157
+ return "; ".join(sorted(valid_vals))
158
+ except Exception as e:
159
+ print("[WARN ecoregions]", e)
160
+ return ""
161
+
162
  def get_eco_geojson(hypothesis, species, btype=None, season=None, eco_color_map=None):
163
  try:
164
  fc = get_m_fc(hypothesis, species, btype, season)
 
190
  # =============================================================================
191
 
192
  def create_m_map(hypothesis, species, eco_color_map, btype=None, season=None):
193
+ # 1. Updated basemap to a simple gray map ("CartoDB.Positron")
194
+ m = geemap.Map(zoom=zoom.value, center=center.value, toolbar_control=False, lite_mode=True, basemap="CartoDB.Positron")
195
  geojson = get_eco_geojson(hypothesis, species, btype, season, eco_color_map)
196
  if geojson:
197
  m.add_geojson(geojson, layer_name="M regions", info_mode="on_click")
 
199
  occ = ee.FeatureCollection(get_pres_backg_asset(btype, season)).filter(ee.Filter.eq("species", species))
200
  fail = occ.filter(ee.Filter.inList("final_decision", FAIL_DECISIONS))
201
  passed = occ.filter(ee.Filter.inList("final_decision", PASS_DECISIONS))
202
+
203
+ # 2 & 3. Made points smaller and changed fail point to "triangle"
204
+ # (Earth Engine Server-Side styling does not support a "teardrop" shape natively)
205
+ m.addLayer(fail.style(**{"color": "ff4444", "pointSize": 3, "pointShape": "triangle"}), {}, "Fail")
206
+ m.addLayer(passed.style(**{"color": "2ca02c", "pointSize": 3, "pointShape": "circle"}), {}, "Pass")
207
  except Exception as e:
208
  print("[WARN points]", e)
209
  return m
210
 
211
  # =============================================================================
212
+ # SUBMISSION
213
  # =============================================================================
214
 
215
+ def submit_review(hypothesis, species, reviewer, notes, ecoregions, btype, season=None):
216
  """Submits review data to Airtable using specific column names."""
217
  api_key = os.environ.get("AIRTABLE_API_KEY")
218
  base_id = os.environ.get("AIRTABLE_BASE_ID")
 
227
  "Content-Type": "application/json"
228
  }
229
 
230
+ # 4. Added Ecoregions column submission data
 
 
231
  data = {
232
  "fields": {
233
  "Reviewer": reviewer,
234
  "Species": species,
235
  "Selected_Hypothesis": hypothesis,
236
+ "Notes": notes,
237
+ "Ecoregions": ecoregions
238
  }
239
  }
240
 
 
242
  response = requests.post(url, headers=headers, json=data)
243
  if response.status_code != 200:
244
  error_info = response.json().get("error", {})
 
245
  msg = error_info.get("message", "Unknown error") if isinstance(error_info, dict) else error_info
246
  return {"success": False, "error": f"Airtable Error: {msg}"}
247