EonMap

Featured

A public dashboard querying the Paleobiology Database in real time, letting users map and browse fossil occurrence records by taxon, time period, and region.

Live Demo

See it in action

This project is deployed and available to explore right now.

Launch Live Site

Overview

About This Project

The Paleobiology Database holds over half a billion fossil occurrence records spanning 500 million years of life on Earth. It's an extraordinary scientific resource, but querying it directly means learning a compact API vocabulary, understanding which response blocks to request, and handling non-standard data formats. EonMap wraps all of that into a map and table interface that anyone can use.

PBDB API Integration

The API layer is built around a clean separation: a readonly query object collects filter intent, a service class translates that into PBDB parameters and caches the result in Redis, and typed data objects are what everything else in the app ever touches. Raw API responses never surface outside the service.

The PBDB API has a few quirks that required specific handling. The show parameter controls which response blocks come back (coordinates, taxonomy, location, age, paleocoordinates) and has to be tuned per context, since a map query needs different data than a taxon detail page. Response records use compact single-letter field names that get mapped to readable properties. Numeric rank codes get translated to human labels. IDs come back as prefixed strings that would silently return 0 if cast naively to an integer, which would break every occurrence detail link without throwing an error.

Caching is keyed on a hash of the serialized query parameters. The same filter combination from any two users shares one cache entry, which meaningfully reduces load on the PBDB servers under concurrent usage.

One infrastructure gotcha: PBDB returns 403 to requests without a User-Agent header when called from inside a Docker container. The connection class hardcodes an appropriate one.

Interactive Fossil Map

Users pick filters from a sidebar (taxon name, time interval, country, bounding box, depositional environment), hit Apply, and get up to 500 results plotted as clustered markers on a Leaflet map. Each marker popup links to the occurrence detail page. A heatmap overlay and three basemap options round out the exploration tools.

The map is driven by three Livewire components that communicate through browser events rather than shared state. Filter changes dispatch an event; the map component catches it, fetches from the service, and fires a browser event that Alpine uses to update the markers. The Leaflet container is marked wire:ignore so Livewire's DOM diffing never touches it between renders. Status overlays inside that container read Livewire properties reactively through Alpine's $wire binding without any DOM replacement.

Tabular Browser

The browse page presents the same filter results as a paginated table using Tabulator.js with configurable page sizes. Column sorting happens client-side. Adding a sort parameter to the PBDB query would produce a different cache key per column and direction, meaning every sort would be a cold API request. In-memory sorting on a page of 100 rows is instant and costs nothing.

Taxon Detail Pages

Each taxon gets a detail page with an occurrence count, a bar chart of records by geological period, a geologic timeline, a mini-map of the distribution, and frequency breakdowns by phylum, class, and environment. The Chart.js bar chart orders geological periods chronologically by deriving the mean age from the occurrence data itself, so the ordering adapts to whatever intervals are actually represented rather than relying on a hardcoded period list.

When dark mode toggles, Chart.js and Vis.js instances are destroyed and rebuilt with colour values read fresh from the active theme. This keeps charts in sync without a page reload.

Theming

All colours are CSS custom properties defined in app.css. Tailwind's @theme inline block maps each variable to a utility alias, keeping hex values out of component files entirely. An Alpine store reads the saved preference from localStorage, falls back to the system prefers-color-scheme, and toggles the theme by setting a single attribute on <html>.

Explore

More Projects

All projects
Bridge
Screenshots
Featured

Bridge

A self-hosted household operations dashboard managing chore rotations, fitness tracking, and plant care, built for daily use and running 24/7 on a home server.

PHP Laravel Livewire MySQL +3 more
View details
Dinner With Lucy
Live

Dinner With Lucy

A recipe and cookbook manager with fraction-accurate ingredient scaling, star ratings, and cookbook collections.

PHP Laravel Livewire MySQL +2 more
View details
CronosPulse
Live
Featured

CronosPulse

A real-time geophysical dashboard surfacing USGS earthquake data, volcano monitoring, NWS flood alerts, and stream gauge readings.

PHP Laravel Livewire MySQL +5 more
View details