# How Atlas Tracks Visited Countries and Sub-Regions in TREK: A Technical Deep Dive

> Discover how Atlas tracks visited countries and sub-regions in TREK. Learn about the synchronized data sources and cached model within the useAtlas hook. A technical deep dive.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: deep-dive
- Published: 2026-07-09

---

**Atlas maintains a live model of your travel history by combining three synchronized data sources—country statistics, regional visit records, and ISO code mappings—loaded and cached inside the `useAtlas` hook.**

The TREK application's Atlas feature provides users with an interactive map visualization of their travels, supporting both country-level and sub-regional tracking. According to the source code in the `mauriceboe/TREK` repository, this functionality relies on a sophisticated client-side state management system that fetches, caches, and renders visited territories in real-time. Understanding this architecture is essential for developers looking to extend the Atlas functionality or integrate similar geographic tracking into their own React applications.

## The Three Data Sources

Atlas tracks visited territories by aggregating data from three distinct sources, each serving a specific purpose in the rendering pipeline.

### Visited Countries

Country-level data is stored in the `data.countries` property of the `AtlasData` object. This structure contains trip counts, place counts, and first/last visit timestamps for each visited nation.

On component mount, the `useAtlas` hook initiates a fetch to `/addons/atlas/stats` via `apiClient.get` (lines 24-34 in [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts)). The response populates the state with an array of `AtlasCountry` objects. When the map renders, the GeoJSON country layer builds a `visitedA3` set from these entries to apply coloured fills and tooltips. Clicking a visited country triggers the un-mark flow if no trips or places are associated.

### Visited Sub-Regions

Sub-regional tracking—covering states, provinces, and territories—uses a separate state variable called `visitedRegions`. This is typed as `Record<string, {code: string; name: string; placeCount: number; manuallyMarked?:boolean}[]>`, keyed by the country's ISO-A2 code.

A second effect in `useAtlas` (lines 61-66) fetches `/addons/atlas/regions`, returning a map of ISO-3166-2 codes and names. During region layer rendering (lines 403-427), the hook constructs `visitedRegionCodes` and `visitedRegionNamesByCountry`. The `isVisitedFeature` helper then checks whether a region feature's code or name matches a visited entry, determining its visual state and click actions.

### Country-to-Region Lookup

To ensure consistent code conversion between ISO-A2 and ISO-A3 standards, Atlas utilizes a mutable module-level map called `A2_TO_A3` defined in [`client/src/pages/atlas/atlasModel.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/atlasModel.ts).

After loading the country borders GeoJSON from `/addons/atlas/countries/geo` (lines 36-56), the hook augments this mapping with any missing alpha-2 to alpha-3 conversions found in the GeoJSON properties. This mapping facilitates fast lookups when rendering countries and provides fallback support for unvisited territories.

## Step-by-Step Data Flow

The initialization sequence follows a specific order to ensure data consistency before rendering.

### 1. Initialization and Stats Loading

When `AtlasPage` mounts, `useAtlas` executes two parallel fetches to populate the core data structures:

```typescript
// Load atlas stats + bucket list
Promise.all([
  apiClient.get('/addons/atlas/stats'),   // → visited countries & overall stats
  apiClient.get('/addons/atlas/bucket-list')
]).then(([statsRes, bucketRes]) => {
  setData(statsRes.data);                 // ← country visit data stored here
  setBucketList(bucketRes.data.items);
});

```

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 24-34)

### 2. Geographic Data Loading

The country borders GeoJSON is fetched and used to enrich the `A2_TO_A3` map, ensuring every country can be referenced consistently across the application.

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 36-56)

### 3. Region Data Loading

A lightweight request retrieves visited regions:

```typescript
// Fetched in useEffect lines 61-66
apiClient.get('/addons/atlas/regions').then((res) => {
  setVisitedRegions(res.data);
});

```

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 61-66)

### 4. Rendering the Country Layer

The country layer iterates over GeoJSON features, building a `visitedA3` set from `data.countries` and applying a deterministic colour palette to visited nations.

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 84-90)

### 5. Rendering Sub-Regions

When the map zoom level reaches 5 or higher, the region layer activates. The `isVisitedFeature` helper checks against `visitedRegions` using either ISO-3166-2 codes or region names scoped to the parent country. Visited regions receive solid fills, while unvisited regions display only outlines.

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 403-429)

### 6. Mark and Un-Mark Actions

Clicking an unvisited territory opens a confirmation popup. Confirmed actions call the API endpoints (`/addons/atlas/country/:code/mark` or `/unmark`) and immediately update local state via `setData` or `setVisitedRegions`, ensuring UI responsiveness without full page reloads.

*Source:* [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) (lines 84-106 for countries, lines 81-106 for regions)

## Practical Implementation Examples

### Accessing Visited Country Data

To display visited countries elsewhere in your application:

```typescript
import { useAtlas } from '@/pages/atlas/useAtlas';

function VisitedCountryList() {
  const { data } = useAtlas();                // data?.countries holds visited entries
  return (
    <ul>
      {data?.countries.map(c => (
        <li key={c.code}>
          {c.code} – {c.tripCount} trips, {c.placeCount} places
        </li>
      ))}
    </ul>
  );
}

```

### Checking Sub-Region Visit Status

To programmatically verify if a specific region has been visited:

```typescript
import { useAtlas } from '@/pages/atlas/useAtlas';

function isRegionVisited(countryA2: string, regionCode: string) {
  const { visitedRegions } = useAtlas();
  const regions = visitedRegions[countryA2] ?? [];
  return regions.some(r => r.code === regionCode);
}

```

### Manually Marking a Country

The same logic used by the UI for manual marking:

```typescript
import apiClient from '@/api/client';
import { useAtlas } from '@/pages/atlas/useAtlas';

async function markCountry(code: string) {
  await apiClient.post(`/addons/atlas/country/${code}/mark`);
  // The hook's state updates automatically via the confirm action flow
}

```

## Key Files and Architecture

| File | Purpose |
|------|---------|
| [`client/src/pages/atlas/atlasModel.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/atlasModel.ts) | Type definitions (`AtlasCountry`, `AtlasData`, `BucketItem`) and the mutable `A2_TO_A3` map |
| [`client/src/pages/atlas/useAtlas.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/useAtlas.ts) | Core hook implementing data fetching, state management, and mark/un-mark logic |
| [`client/src/pages/AtlasPage.tsx`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/AtlasPage.tsx) | UI wrapper integrating the hook with map components and sidebars |
| [`shared/continent.ts`](https://github.com/mauriceboe/TREK/blob/main/shared/continent.ts) | Helper `continentForCountry` used when updating stats after mark/un-mark operations |

## Summary

- **Three data sources** power the Atlas: `data.countries` for nations, `visitedRegions` for sub-regions, and `A2_TO_A3` for code consistency.
- **Parallel initialization** fetches stats and bucket lists via `Promise.all`, followed by separate GeoJSON and region data loads.
- **Real-time rendering** uses `visitedA3` sets for countries and `isVisitedFeature` helper functions for regions, with zoom-level gated rendering.
- **Optimistic updates** occur through immediate state mutations after API calls to `/addons/atlas/country/:code/mark` or `/unmark`, eliminating UI latency.
- **Type-safe architecture** relies on `AtlasCountry` and `AtlasData` interfaces defined in [`atlasModel.ts`](https://github.com/mauriceboe/TREK/blob/main/atlasModel.ts).

## Frequently Asked Questions

### How does Atlas handle country code conversions between ISO-A2 and ISO-A3?

The system maintains a mutable module-level map called `A2_TO_A3` in [`client/src/pages/atlas/atlasModel.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/atlas/atlasModel.ts). After loading the GeoJSON country borders, the `useAtlas` hook populates this map with any missing mappings found in the GeoJSON properties (lines 66-70). This ensures consistent referencing when the API returns alpha-2 codes but the map visualization requires alpha-3 codes for feature matching.

### What determines whether a sub-region appears as visited on the map?

The `isVisitedFeature` helper checks two criteria: the region's ISO-3166-2 code and its display name, scoped to the parent country. If either matches an entry in the `visitedRegions` record (keyed by ISO-A2), the region receives a solid fill colour. This dual-check approach handles cases where GeoJSON features might use full names while the API stores codes, or vice versa.

### Can users mark countries as visited without having trip data?

Yes. The system supports manually marking territories through the `/addons/atlas/country/:code/mark` endpoint. When a user clicks an unvisited country or region, the UI presents a confirmation dialog. Upon confirmation, the hook updates `setData` or `setVisitedRegions` immediately while synchronizing with the backend, allowing users to track places they visited outside of logged trips.

### How does the map optimize performance when rendering thousands of regions?

Atlas implements zoom-level gating (threshold of 5) for the region layer, ensuring sub-regions only render when the user has zoomed in sufficiently. Additionally, the hook builds memoized lookup sets (`visitedA3`, `visitedRegionCodes`) rather than scanning arrays during each render cycle, providing O(1) complexity when checking visit status for individual GeoJSON features.