0
2
# Gallery Location Design
## Goal
Add location to galleries using H3 geohashing and Nominatim geocoding, with auto-suggest from photo EXIF GPS data. Display locations on gallery cards and provide location-based feeds.
## Data Model
**Gallery lexicon** (`social.grain.gallery`): Add optional `location` field referencing `social.grain.defs#location`.
**Location type** (`social.grain.defs#location`):
```json
{
"type": "object",
"required": ["name", "value"],
"properties": {
"name": { "type": "string", "description": "Display name (e.g. Tokyo, Japan)" },
"value": { "type": "string", "description": "H3 cell index at resolution 5" }
}
}
```
**Gallery view** (`social.grain.gallery.defs#galleryView`): Add optional `location` with same shape.
**Database**: The gallery table gains `location_name` and `location_value` columns.
**EXIF extraction**: Extend `extractExif()` in `image-resize.ts` to grab `GPSLatitude`/`GPSLongitude` from exifr. These are transient (not stored) — used only client-side to auto-suggest location.
**Dependencies**: Add `h3-js` package.
## Client Utilities
Port from atconf-astro:
- `app/lib/utils/h3.ts` — `latLonToH3(lat, lon, resolution=5)` using h3-js
- `app/lib/utils/nominatim.ts` — `searchLocations(query)` for forward geocoding, `reverseGeocode(lat, lon)` for EXIF GPS auto-suggest. Both hit `nominatim.openstreetmap.org`.
## Create Flow (Step 2)
Add `LocationInput` component below the description field:
- Text input with map pin icon
- Debounced Nominatim search (300ms, min 2 chars)
- Dropdown with up to 5 results
- On select: convert lat/lon to H3 cell, store name + h3Index
- Click-outside closes dropdown
- Optional — galleries without location are fine
**EXIF GPS auto-suggest**: On entering step 2, if the first photo has GPS coords, reverse-geocode via Nominatim and pre-fill the location input. User can clear or override.
**On publish**: Include `location: { name, value }` in the gallery record if set.
## Display
On `GalleryCard`: Show map pin icon + location name below EXIF info, linking to `/location/[h3]`.
## Feeds
**`server/xrpc/getLocations.ts`**: Top locations by gallery count, cached 5min (same pattern as getCameras).
**`server/feeds/location.ts`**: Filter galleries by H3 cell match.
**Lexicons**: Add `location` param to `getFeed.json`. Create `social.grain.unspecced.getLocations` lexicon.
**UI**: Location pills in sidebar and mobile drawer. `/location/[h3]` route with DetailHeader (location name) and FeedList.
## Hydration
Update `_hydrate.ts` to read `location_name`/`location_value` from the gallery row and include `location: { name, value }` in the hydrated gallery view.
## Files to Create/Modify
**Create:**
- `app/lib/utils/h3.ts`
- `app/lib/utils/nominatim.ts`
- `app/lib/components/atoms/LocationInput.svelte`
- `server/feeds/location.ts`
- `server/xrpc/getLocations.ts`
- `app/routes/location/[h3]/+page.ts`
- `app/routes/location/[h3]/+page.svelte`
- `lexicons/social/grain/unspecced/getLocations.json`
**Modify:**
- `lexicons/social/grain/defs.json` — add location type
- `lexicons/social/grain/gallery/gallery.json` — add location field
- `lexicons/social/grain/gallery/defs.json` — add location to galleryView
- `lexicons/dev/hatk/getFeed.json` — add location param
- `app/lib/utils/image-resize.ts` — extract GPS from EXIF
- `app/routes/create/+page.svelte` — add LocationInput to step 2
- `app/lib/components/molecules/GalleryCard.svelte` — display location
- `app/lib/components/organisms/SidebarRight.svelte` — location pills
- `app/lib/components/organisms/MobileDrawer.svelte` — location pills
- `app/lib/queries.ts` — add locationFeedQuery, locationsQuery
- `server/feeds/_hydrate.ts` — include location in gallery view
- `db/schema.sql` — add columns
- `package.json` — add h3-js