π£ 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:
- 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. - 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.
- 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.
- 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
responseSchemafor 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
paramsis now aPromiseβ had to read the docs innode_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
- elevenlabs
- gemini
- nano-banana
- next.js
- openstreetmap
- react
- tavily
- vercel
Log in or sign up for Devpost to join the conversation.