🍣 Foodscape β€” what it is

A living, isometric map of London where every building is the real building at that spot β€” but the best restaurants are rebuilt out of their signature dish. Real Soho, re-skinned in food. You talk to it; it talks back.


πŸ’‘ Inspiration

I came across isometric.nyc β€” a gorgeous AI-rendered isometric New York β€” and two thoughts collided. First: Imagen / Nano Banana is genuinely cool, and nobody's pushing it as a structural map renderer. Second, more selfishly: I'm always hungry, and I wanted to see more food around me. So instead of a city of grey buildings, what if the city was the food? What if my actual neighbourhood β€” the real townhouse on the real corner β€” was rebuilt out of the signature dish of the best restaurant inside it? That's Foodscape: a map of London you can eat.


πŸ”¨ How I built it

The whole project rests on two locks applied to every tile:

  • Form lock β€” the real building. I pull the actual footprint, height, and roofline at each coordinate from OpenStreetMap / Overpass, so the silhouette stays true to the real structure.
  • Style lock β€” one pixel-art isometric anchor image fed into every generation, so $N$ independent image calls still produce one coherent city.

The pipeline:

  1. Discovery (Tavily + Gemini + OSM). A hybrid sweep: Overpass returns ~50 real eateries with real coordinates, Gemini (structured output via responseSchema) assigns each a cuisine and a tiling food material, and Tavily web search grounds the top named spots with their real, current signature dishes.
  2. Rendering (Nano Banana). For each restaurant I render its real OSM massing as a grey whitebox, then feed it to Nano Banana in edit mode β€” "keep this exact shape, re-skin every surface in this food." A flood-fill background remover knocks out the fake checkerboard the model bakes in instead of real alpha, and the transparent sprite drops onto its exact footprint.
  3. The autonomous agent. On a daily Vercel Cron it re-runs discovery, diffs against the stored snapshot (keyed on normalised restaurant name), and regenerates only the cells that changed β€” a new opening, a closure, a new dish. Everything unchanged is left untouched. The map keeps itself true with no human in the loop.
  4. JARVIS (ElevenLabs + Gemini). A click-to-talk particle orb: Web Speech β†’ transcript β†’ Gemini intent parse β†’ query the world model β†’ fly the camera + ring the matches β†’ reply out loud through ElevenLabs.

Stack: Next.js 16 (App Router, Turbopack), Vercel Blob for persistence, deployed on Vercel.


πŸ“š What I learned

  • Base image models won't tile. I tried neighbour-conditioned seamless tiles (isometric.nyc's approach, which needed a fine-tuned model) and the base model ignored the format ~8/9 times. The fix was to stop fighting it: draw the ground and streets deterministically from OSM, and only ask the model for the one thing it's reliable at β€” individual transparent food buildings.
  • Edit mode > generation for shape control. Handing Nano Banana an actual rendered massing and saying "repaint, keep the silhouette" preserves the real building far better than any from-scratch prompt.
  • Structured output is underrated as glue. Using Gemini's responseSchema for both restaurant extraction and voice-intent parsing meant guaranteed-shape JSON I could act on with zero parsing or retries.

πŸ§— Challenges

  • "It looked like floating toys." Early versions rendered fine buildings on a synthetic grid that read as a lattice, not a city. A city reads as a city because of its street network β€” so I rebuilt the ground from real OSM geometry.
  • Fake transparency. The image model returns an opaque light checkerboard instead of an alpha channel. I wrote a connectivity-based magic-wand flood-fill (light-and-near-grey, seeded from the borders) so it keys the surround but preserves pale pixels inside the building.
  • Next.js 16 breaking changes. Route-handler params is now a Promise β€” had to read the docs in node_modules, not my training data.
  • Blob read-after-overwrite lag. Vercel Blob serves a stale copy briefly after an overwrite, which bit a tight seedβ†’read test loop; I version persisted JSON shapes and poll until visible.
  • Performance. First load was pulling ~130 MB β€” fifty full-res ~2.6 MB PNGs, served uncached. I downscaled + palette-quantised the sprites (pixel-art tolerates it almost losslessly) and put them behind the Vercel CDN: 130 MB β†’ 6.5 MB, ~95% smaller, with warm reloads served from the edge.

Live: foodscape-olive.vercel.app Β· Tavily finds it, Gemini reasons, Nano Banana builds it, the agent keeps it true, ElevenLabs gives it a voice.

Built With

Share this project:

Updates