428e7fa682
The ten PHOTO STYLE sims now carry nothing but their stock's own grade, and each is named for the stock it stands for: PROVIA, VELVIA, CLASSIC CHROME, CLASSIC VIVID (Velvia spliced with Classic Chrome at the blue row), CLASSIC NEGATIVE, ASTIA, ETERNA, ACROS, LC STREETLIFE CLASSIC, LC STREETLIFE VIVID. Grain, clarity, saturation and light moves were dropped from their `adjustments`, so a sim is a clean starting point and the general knobs read their defaults while the look still lands on the pixels. LC STREETLIFE VIVID keeps the one brightness step its stock needs, but as SIM_EXPOSURE_BIAS in colorUtils rather than as an adjustment: it is folded in where the Exposure slider applies, so the picture gets the lift and the parameter stays at 0. Also in this checkpoint: the watermark/GPS boxes and their colour pickers, the WATERMARK chip column, the real admin stats, and the fix that stopped presets from doubling and a frame from refusing to come off when a photo was reopened (/file is the finished render, /base the editable pixels).
62 lines
2.8 KiB
TypeScript
62 lines
2.8 KiB
TypeScript
// Place names for a coordinate. The phone app asks the OS geocoder, and a
|
|
// browser has no twin of that, so the lookup lives here: one HTTP call behind a
|
|
// cache, and the same naming rule the app uses, so a stamp reads the same on
|
|
// both.
|
|
import { findPlace, savePlace } from './db';
|
|
|
|
// ~11m at the equator: fixes from the same spot share a cache row.
|
|
const key = (lat: number, lng: number) => `${lat.toFixed(4)},${lng.toFixed(4)}`;
|
|
|
|
// Nominatim (OpenStreetMap). Its usage policy asks for one request per second,
|
|
// a caller that identifies itself, and answers that are kept — hence the queue
|
|
// below and the SQLite row every answer leaves behind.
|
|
const ENDPOINT = 'https://nominatim.openstreetmap.org/reverse';
|
|
const AGENT = 'RecipesCam/1.2 (https://recipescam.labz.io.vn)';
|
|
const GAP_MS = 1100;
|
|
const TIMEOUT_MS = 8000;
|
|
|
|
// One lookup at a time, never closer together than GAP_MS.
|
|
let queue: Promise<unknown> = Promise.resolve();
|
|
|
|
// Android answers with the commune in `subregion` and the city in `region`;
|
|
// Nominatim says the same thing with `suburb` and `city`. Same rule as the app's
|
|
// localityName: "COMMUNE, CITY", and a name that fills both slots prints once.
|
|
export function nameOf(address: Record<string, string | undefined>): string | null {
|
|
const district =
|
|
address.suburb || address.city_district || address.district || address.quarter || address.neighbourhood || address.county;
|
|
const city = address.city || address.town || address.municipality || address.village || address.state;
|
|
const parts = district && district !== city ? [district, city] : [district || city];
|
|
const name = parts.filter(Boolean).join(', ');
|
|
return name ? name.toUpperCase() : null;
|
|
}
|
|
|
|
export async function placeName(lat: number, lng: number): Promise<string | null> {
|
|
const cached = findPlace(key(lat, lng));
|
|
if (cached !== undefined) return cached || null;
|
|
|
|
const call = queue.then(async () => {
|
|
await new Promise((r) => setTimeout(r, GAP_MS));
|
|
const url = `${ENDPOINT}?format=jsonv2&zoom=14&addressdetails=1&lat=${lat}&lon=${lng}`;
|
|
const res = await fetch(url, {
|
|
headers: { 'user-agent': AGENT, accept: 'application/json' },
|
|
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
});
|
|
if (!res.ok) throw new Error(`geocoder HTTP ${res.status}`);
|
|
const body = (await res.json()) as { address?: Record<string, string> };
|
|
return body.address ? nameOf(body.address) : null;
|
|
});
|
|
// The queue must not inherit a rejection, or every later lookup would fail
|
|
// with it; the caller gets the failure through `call` itself.
|
|
queue = call.catch(() => undefined);
|
|
|
|
try {
|
|
const name = await call;
|
|
// Only an answer that came back is worth keeping — a timeout or an outage
|
|
// must not pin "no name here" on the coordinate for good.
|
|
savePlace(key(lat, lng), name ?? '');
|
|
return name;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|