~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
99 lines · 3.8 KB Raw History
# 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