AI illustrated map poster via API: art first, labels in code

Generate the illustrated map art at 2:3 with Nano Banana 2, leave the lettering out, then draw every place name in Pillow from a list so none are misspelled.

5 min readSume
All posts

An illustrated map poster is a bad fit for a model that has to spell, and a good fit for one that draws. Generate the map art with no lettering, then draw each place name yourself from a list. On Sume, google/nano-banana-2 accepts aspect_ratio: "2:3", which gives a tall poster shape, and the labels are placed with Pillow so every name is spelled the way you wrote it.

This is the approach to reach for whenever a picture must carry correct names: a city, a trail, a campus, a fictional continent. The model sets the mood and the geography; your code sets the text and the coordinates, and you can fix a typo by editing one string instead of regenerating.

Step 1: art with the lettering left out

Ask for a hand-painted illustrated map with landmarks as small drawings, a clear coastline, calm colours, and an empty banner area at the top for the title. Say no text, no labels and no legends. Models tend to scribble pseudo-words onto maps unless told not to, and gibberish text is much harder to remove than to prevent.

Request 2:3 and read the response's usage.cost so you know what each attempt billed. The n range on Nano Banana 2 is 1 to 4 in the catalog, so one call can give you a few compositions to choose from (Sume Image API docs).

import os, requests

r = requests.post("https://api.sume.com/v1/images", timeout=120, json={
    "model": "google/nano-banana-2",
    "prompt": ("Hand-painted illustrated map of a fictional harbor town, tiny landmark drawings, "
               "clear coastline, soft watercolor palette, empty banner space at the top, "
               "no text, no labels, no legend"),
    "aspect_ratio": "2:3", "resolution": "2K", "n": 2,
}, headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"})
r.raise_for_status()
if r.status_code == 202:
    raise SystemExit("queued: poll the job status_url")
for i, item in enumerate(r.json()["data"]):
    print(i, item["url"])
print("billed:", r.json()["usage"]["cost"])

Step 2: place the names

Keep the labels in a list of name, x and y as fractions of the image, so the same list works at any resolution. Look at the art, note where each landmark is, and write the fractions down. The function below draws each name with a light halo so it stays readable over busy painting, and puts the title in the banner at the top.

It uses Pillow's default font at a chosen size, which keeps the demo portable; for a real poster load a licensed font file with ImageFont.truetype. The demo builds a blank 2:3 canvas so it runs as-is.

from PIL import Image, ImageDraw, ImageFont

def label_map(path, out, title, places):
    img = Image.open(path).convert("RGB")
    w, h = img.size
    d = ImageDraw.Draw(img)
    big = ImageFont.load_default(size=int(w * 0.07))
    small = ImageFont.load_default(size=int(w * 0.03))
    d.text((w / 2, h * 0.05), title, font=big, fill="#1b2a3a", anchor="mm",
           stroke_width=3, stroke_fill="#f4ecd8")
    for name, fx, fy in places:
        d.text((fx * w, fy * h), name, font=small, fill="#1b2a3a", anchor="mm",
               stroke_width=2, stroke_fill="#f4ecd8")
    img.save(out)
    return img.size

if __name__ == "__main__":
    Image.new("RGB", (1024, 1536), "#9cc3c9").save("map.png")
    places = [("Lantern Point", 0.3, 0.35), ("Old Mill", 0.65, 0.5), ("Salt Market", 0.4, 0.75)]
    print(label_map("map.png", "map-labeled.png", "HARBOR TOWN", places))

Printing and exact size

If the poster needs an exact pixel size, treat the model output as the source and resize or crop after, as in Nano Banana exact pixels. Generating at 2K leaves room to scale down to common poster sizes without upscaling.

For another example of art from the model and text from your code, see the vintage travel poster recipe, which uses a 3:4 ratio.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume