{"id":27204491,"url":"https://github.com/simonrolph/inatcounter","last_synced_at":"2026-02-08T11:31:26.911Z","repository":{"id":119113184,"uuid":"152996333","full_name":"simonrolph/inatcounter","owner":"simonrolph","description":"Live visualisation of iNaturalist observations and identifications","archived":false,"fork":false,"pushed_at":"2024-12-19T22:44:17.000Z","size":65,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T22:58:33.164Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://simonrolph.github.io/inatcounter/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simonrolph.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-14T16:46:03.000Z","updated_at":"2024-12-19T22:44:20.000Z","dependencies_parsed_at":"2025-04-12T02:01:35.583Z","dependency_job_id":null,"html_url":"https://github.com/simonrolph/inatcounter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/simonrolph/inatcounter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Finatcounter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Finatcounter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Finatcounter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Finatcounter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simonrolph","download_url":"https://codeload.github.com/simonrolph/inatcounter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Finatcounter/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267552097,"owners_count":24106000,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-09T22:58:31.383Z","updated_at":"2026-02-08T11:31:21.873Z","avatar_url":"https://github.com/simonrolph.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# iNaturalist Live\nLive visualisation of iNaturalist observations and identifications.\n\n![image](https://user-images.githubusercontent.com/17750766/138076782-04740f0e-a670-43dc-af5c-07efafdf2411.png)\n\n## About\n\nThis is a one-page serverless web page that calls the iNaturalist API: https://api.inaturalist.org/v1/docs/ to show actions across the whole of iNaturalist such as submission of records and identification of records.\n\nIt has three main features:\n * A central counter for the number of verifable records in iNaturalist\n * A map with markers for new submissions or identification on iNatauralist. Markers have a popup with a small image of the observation, and the current identification. THe symbol of the marker indicates whether it is an observation or a identification, and the type of identification, as idicated by the key on the bottom of the page.\n * A 'ticker' on the right hand side which lists new observations / identifications as they are added to iNaturalist. Mouse rollover highlights the corresponding marker on the map. Clicking on the ticker item takes you to inaturalist to look at the observation on iNaturalist.org.\n\nVisit the webpage: https://simonrolph.github.io/inatcounter/\n\nNote that each page you have open is calling the iNaturalist API individually so \n\n## How it works\n\n### Set up\n\nAll the html and JS is in a single file: https://github.com/simonrolph/inatcounter/blob/master/index.html and css is in: https://github.com/simonrolph/inatcounter/blob/master/style.css\n\nThe map is a leaflet map: https://leafletjs.com/\n\nThe counter ticks up (or down) using countUp.js https://inorganik.github.io/countUp.js/\n\nCustom leaflet map markers are defined using font-awesome (https://fontawesome.com/) icons. For example this is the marker for observations:\n```javascript\nvar obs = new L.DivIcon({\n    className: 'my-div-icon',\n    html: '\u003cspan class=\"fa-stack\"\u003e\u003ci class=\"fas fa-circle fa-stack-2x fa-inverse\" style=\"text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);\"\u003e\u003c/i\u003e\u003ci class=\"fas fa-camera fa-stack-1x\"\u003e\u003c/i\u003e\u003c/span\u003e'\n});\n```\n\nFirstly a function gets the current date/time for x number of seconds ago (as specified in the `secondsdiff` argument) and formats it in the right string format for calling the iNaturalist API.\n\n```javascript\nfunction getTheDate(secondsdiff) {\n    var d = Math.floor((new Date()).getTime() / 1000) + secondsdiff\n    //console.log(d);\n    var da = new Date(0); // The 0 there is the key, which sets the date to the epoch\n    da.setUTCSeconds(d);\n\n    // make the date in the right format for the API call\n\n    // example correct format: 2018-10-14T12%3A44%3A24%2B00%3A00\n    var outDate = String(addZero(da.getUTCFullYear())) + \"-\" + String(addZero(da.getUTCMonth() + 1)) + \"-\" + String(addZero(da.getUTCDate())) + \"T\" + String(addZero(da.getUTCHours())) + \"%3A\" + String(addZero(da.getUTCMinutes())) + \"%3A\" + String(addZero(da.getUTCSeconds())) + \"%2B00%3A00\";\n\n    return (outDate)\n}\n```\n\n### API calls\n\nThe page uses the `window.setInterval(function(){}, 15000)` to make API calls at set intervals in milliseconds, so `15000` = 15 seconds. The page then makes 3 different calls to the API at various intervals.\n\nEvery 15 seconds it updates the big counter in the middle \n\n```javascript\nvar theDate = getTheDate(0)\nrequest.open('GET', 'https://api.inaturalist.org/v1/observations?verifiable=true\u0026created_d2=' + theDate + '\u0026page=1\u0026per_page=0\u0026order=desc\u0026order_by=created_at', true);\n```\n\nEvery 6 seconds it gets recent observations from the past 10 seconds using an API call (limited to one page of 40 so if someone uploads a massive batch then it might miss some of them) like so:\n\n```javascript\nvar theDate2 = getTheDate(-10);\nrequest2.open('GET', 'https://api.inaturalist.org/v1/observations?verifiable=true\u0026created_d1=' + theDate2 + '\u0026page=1\u0026per_page=40\u0026order=desc\u0026order_by=created_at', true);\n```\n\nIt then uses this data to update the map and ticker. In the same interval it gets identifications from the past 10 seconds like so:\n\n```javascript\nrequest3.open('GET', 'https://api.inaturalist.org/v1/identifications?current=true\u0026d1=' + theDate2 + '\u0026order=desc\u0026order_by=created_at', true);\n```\n\nSince the call is getting observations/identifications from the last 10 seconds but doing the action every 6 seconds (to accomodate time for the api call to be made) the API might get pull the same observation/identification twice. Therefor, it builds up an array of unique IDs of observations / identifications in the oject `uuid` and for each new item in pulled data it checks if it's already downloaded that item before.\n\n5 minutes after it has added the markers/ticker items, it then removes the markers / ticker items using this code:\n```\nwindow.setTimeout(function(markerid) {\n  //console.log(markerid);\n  document.getElementById(\"marker\"+markerid).remove();\n  mymap.removeLayer(layerObvs.getLayer(markerid));\n\n},300000,marker.marker_id);\n```\n\nThat's essentially it really. There are various comments in the code which point out what each bit is doing: https://github.com/simonrolph/inatcounter/blob/master/index.html\n\nIf I were to make it again I'd make it have some server-side operation that pulls the data from the API once per time interval, and then the webpage is served by that data - rather than each instance of someone having the web page open contacting the iNaturalist API directly - because then it wouldn't be at risk of overloading their API servers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonrolph%2Finatcounter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimonrolph%2Finatcounter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonrolph%2Finatcounter/lists"}