import pandas as pd
import folium
from datetime import datetime, timedelta
from pyinaturalist import (
Observation,
get_observations,
get_projects_by_id,
pprint,
)
import ipyplot
from itertools import compressA visit to Cairns
Cairns
Python
pyinaturalist
Folium
A summary of my iNaturalist observations during our visit to Cairns.
Load Python modules
Download iNaturalist observations
From the end of May to the first week of June 2025
projects = get_projects_by_id([253831, 255314])
pprint(projects)ID Title Type URL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 255314 Great Southern BioBlitz 2025: Cairns collection https://www.inaturalist.org/projects/255314 253831 Great Southern Bioblitz 2025: Great Barrier collection https://www.inaturalist.org/projects/253831 Reef
projects['results'][1].keys()dict_keys(['id', 'title', 'hide_title', 'description', 'slug', 'project_type', 'delegated_project_id', 'is_delegated_umbrella', 'banner_color', 'place_id', 'user_id', 'admins', 'user_ids', 'location', 'icon', 'icon_file_name', 'header_image_url', 'header_image_file_name', 'header_image_contain', 'project_observation_fields', 'terms', 'search_parameters', 'project_observation_rules', 'rule_preferences', 'created_at', 'updated_at', 'flags', 'site_features', 'prefers_user_trust', 'observation_requirements_updated_at', 'is_umbrella', 'is_new_style_project', 'user'])
projects['results'][0]['project_observation_rules'][ {'id': 2252635, 'operator': 'observed_in_place?', 'operand_type': 'Place', 'operand_id': 176237}, {'id': 2252636, 'operator': 'observed_in_place?', 'operand_type': 'Place', 'operand_id': 144285}, {'id': 2252637, 'operator': 'observed_in_place?', 'operand_type': 'Place', 'operand_id': 193954}, {'id': 2252638, 'operator': 'not_in_taxon?', 'operand_type': 'Taxon', 'operand_id': 118552}, {'id': 2252639, 'operator': 'not_in_taxon?', 'operand_type': 'Taxon', 'operand_id': 47144}, {'id': 2252640, 'operator': 'not_in_taxon?', 'operand_type': 'Taxon', 'operand_id': 43584} ]
Great Southern Bioblitz 2025: Great Barrier Reef
cairn_places=[x['operand_id'] for x in projects['results'][0]['project_observation_rules'] if x['operand_type']=='Place']gbr_places=[x['operand_id'] for x in projects['results'][1]['project_observation_rules'] if x['operand_type']=='Place']observations = get_observations(user_id='NeoMapas',
d1="2025-10-23",
d2="2025-10-28",
per_page=1000)len(observations)4
Map of observations
map = folium.Map(tiles="Esri NatGeoWorldMap")fg = folium.FeatureGroup(name="iNaturalist observations", control=True, attribution="observers @ iNaturalist").add_to(map)
popup_text = """<img src='{url}'>
<caption><i>{species}</i> observed on {observed_on} / {attribution}</caption> {desc}
"""
for obs in observations['results']:
if obs['quality_grade'] == 'research':
if obs['description'] is None:
desc = ""
else:
desc = obs['description']
pincolor = 'green'
else:
desc = "Observation is not research quality grade."
pincolor = 'gray'
fg.add_child(
folium.Marker(
location=obs['location'],
popup=popup_text.format(
species=obs['species_guess'],
observed_on=obs['observed_on'],
desc=desc,
url = obs['observation_photos'][0]['photo']['url'],
attribution = obs['observation_photos'][0]['photo']['attribution']),
icon=folium.Icon(color=pincolor),
)
)
folium.LayerControl().add_to(map)<folium.map.LayerControl object at 0x12a111730>
map.fit_bounds(map.get_bounds())
mapMake this Notebook Trusted to load map: File -> Trust Notebook
Travel log
records=list()
for obs in observations['results']:
record = {
'uuid': obs['uuid'],
'quality': obs['quality_grade'],
'description': obs['description'],
'location': obs['place_guess'],
'longitude': obs['location'][1],
'latitude': obs['location'][0],
'species guess': obs['species_guess'],
'Fecha': obs['observed_on'],
'Cairns_bioblitz': any([x in cairn_places for x in obs['place_ids']]),
'GBR_bioblitz': any([x in gbr_places for x in obs['place_ids']])
}
if len(obs['observation_photos'])>0:
record['url'] = obs['observation_photos'][0]['photo']['url']
record['attribution'] = obs['observation_photos'][0]['photo']['attribution']
records.append(record)inat_obs=pd.DataFrame(records)inat_obs["dia"]=inat_obs.Fecha.apply(datetime.date)inat_obs.groupby(['dia', 'Cairns_bioblitz', 'GBR_bioblitz']).agg({'uuid': ['count',pd.Series.nunique], 'species guess': ['count',pd.Series.nunique]})| uuid | species guess | |||||
|---|---|---|---|---|---|---|
| count | nunique | count | nunique | |||
| dia | Cairns_bioblitz | GBR_bioblitz | ||||
| 2025-10-24 | True | False | 5 | 5 | 5 | 4 |
| True | 1 | 1 | 0 | 0 | ||
| 2025-10-25 | False | True | 1 | 1 | 1 | 1 |
| True | False | 2 | 2 | 2 | 2 | |
| True | 55 | 55 | 48 | 36 | ||
| 2025-10-26 | False | False | 33 | 33 | 30 | 27 |
| 2025-10-27 | True | True | 30 | 30 | 28 | 22 |
my_observations = Observation.from_json_list(observations)images = [obs.photos[0].small_url for obs in my_observations]
labels = [str(obs) for obs in my_observations][x in cairn_places for x in obs['place_ids']][False, False, False, False, False, False, False, False, True, False, True]
Arrival in Cairns - 24th October 2025
Explore observations on iNaturalist
s1 = [datetime.date(obs.observed_on).strftime('%d/%m/%Y') in ['24/10/2025',] for obs in my_observations]
s2 = [obs.Cairns_bioblitz for obs in my_observations]
any([x in cairn_places for x in obs['place_ids']])
ipyplot.plot_images(list(compress(images, s1 & s2)), list(compress(labels, ss)), max_images=60,)--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[45], line 2 1 s1 = [datetime.date(obs.observed_on).strftime('%d/%m/%Y') in ['24/10/2025',] for obs in my_observations] ----> 2 s2 = [obs.Cairns_bioblitz for obs in my_observations] 3 ipyplot.plot_images(list(compress(images, s1 & s2)), list(compress(labels, ss)), max_images=60,) AttributeError: 'Observation' object has no attribute 'Cairns_bioblitz'
Fist day of bioblitzing: 25th October 2025
ss = [datetime.date(obs.observed_on).strftime('%Y-%m-%d') in ['2025-10-25'] for obs in my_observations]
ipyplot.plot_images(list(compress(images, ss)), list(compress(labels, ss)), max_images=60,)Second day of bioblitzing: 26th October 2025
ss = [datetime.date(obs.observed_on).strftime('%Y-%m-%d') in ['2025-10-26'] for obs in my_observations]
ipyplot.plot_images(list(compress(images, ss)), list(compress(labels, ss)), max_images=60,)Last day of bioblitzing: 27th October 2025
ss = [datetime.date(obs.observed_on).strftime('%Y-%m-%d') in ['2025-10-27'] for obs in my_observations]
ipyplot.plot_images(list(compress(images, ss)), list(compress(labels, ss)), max_images=60,)