Dynamic City Map Poster: How I Personalized My Landing Page Based on Visitor Location

Jan 25 / 1 min read

https://www.youtube.com/watch?v=SloeuZbZv8w

Why I Built This

Let me be honest: because I can, and it looks cool.

But there's more to it. The previous landing page was bland. It worked, but it felt generic. You'd land on the page and see the same static content as everyone else. The only way to see the product in action was to click on a video or scroll to the demo.

I wanted something different. Something that would make visitors stop and think: "Wait, is this showing MY city?"

The Goals

  1. Increase conversion by creating an immediate connection with visitors
  2. Make the homepage feel personal - not like a template
  3. Show the product in action before asking users to take any action
  4. Create a sense of belonging no matter where you're visiting from

How It Works

The Flow

Visitor IP --> Geolocation API --> City + Country --> Map Generation --> Cached Image
  1. Detect location from IP: When someone visits the page, we grab their IP address
  2. Resolve city and country: Using IP geolocation, we determine their approximate location
  3. Generate a custom map poster: A beautiful, stylized map of their city is generated
  4. Cache and serve: The image is cached so subsequent visitors from the same location get instant loads

Technical Implementation

The implementation is built in Laravel/PHP, but the concept originated from a Python script. Here's the architecture:

1. IP to Location

IP Address --> GeoIP Service --> { city: "Dakar", country: "Senegal" }

2. Map Generation

The map is generated using OpenStreetMap tiles with custom styling:

  • Tile fetching: We fetch map tiles from various providers (CartoDB, Stadia Maps)
  • Image stitching: Multiple 256x256 tiles are stitched together
  • Overlay application: A subtle overlay is applied for better text contrast
  • Caching: Generated images are saved as country_city_theme_size.jpg

3. Dynamic Route

GET /images/maps/{country}/{city}

This isn't a real file path - it's a dynamic route that:

  • Checks if the image already exists (instant serve)
  • Generates it on-demand if not
  • Applies proper caching headers for browser caching

4. Lazy Loading

The map is loaded via JavaScript, not as a blocking resource:

<div class="hero-background" data-map-url="/images/maps/senegal/dakar"></div>

This ensures the page loads fast, and the personalized map loads in the background.

Production Considerations

Building something cool is one thing. Making it production-ready is another.

Rate Limiting

You never know how people will try to abuse an open endpoint. I implemented:

  • 10 generations per IP per day: Prevents abuse while allowing legitimate use
  • Generation locks: Prevents duplicate generation requests for the same image
  • closed usage: Only available if called from my website

Fallback Strategy

APIs go down. Networks fail. The map feature should never break the page:

  • If geolocation fails: Default to a generic location (Dakar, Senegal - where I'm from)
  • If map tile API fails: Serve a solid color background matching the theme
  • If any step fails: Gracefully degrade to a simple background

Responsive Images

Desktop and mobile need different image sizes:

Device Size Optimization
Desktop 1920x1080 High quality
Mobile 768x1024 Smaller file

The controller detects the device type and serves the appropriate size.

Caching Strategy

Browser Cache: 1 year (immutable content)
Server Cache: Generated once, served forever
API Cache: Coordinates cached for 30 days, map data for 7 days

The Code

Standalone Map Generator Class

The MapPosterGenerator class is completely standalone and reusable:

$generator = new MapPosterGenerator("japanese_ink", 1920, 1080);
$path = $generator->createPoster("Dakar", "Senegal", 6000);

Features:

  • Multiple theme support (dark, light, japanese_ink)
  • Configurable dimensions
  • Distance-based zoom calculation
  • Built-in caching toggle

Controller with Security

The MapImageController handles:

  • Referer checking (prevents hotlinking)
  • Input sanitization
  • Rate limiting
  • Device detection
  • Fallback image generation

Themes

The map supports multiple visual styles:

Theme Description
japanese_ink Light, minimal, ink-wash aesthetic
dark Dark mode friendly
midnight Deep dark with subtle details
default Clean, neutral styling

Each theme defines colors for:

  • Background
  • Roads (by type: motorway, primary, secondary, etc.)
  • Water bodies
  • Parks and green spaces
  • Text overlay

Results

The landing page now feels alive. When you visit:

  1. You see a map of YOUR city
  2. Animated markers show vehicles moving (simulating the product)
  3. The product demo is visible immediately - no action required

It creates an instant connection. You're not looking at "some GPS tracking software" - you're looking at GPS tracking in YOUR neighborhood.


Credit Where It's Due

This entire feature is built on the shoulders of open source. Massive shoutout to:

@originalankur for the original Python implementation: https://github.com/originalankur/maptoposter

The original script was in Python. Using AI, I converted it to PHP as a standalone class that fits into the Laravel ecosystem. The core concept and approach are entirely from that repository.


Lessons Learned

  1. Personalization doesn't have to be complex: A simple IP lookup + map generation creates a powerful personal touch
  2. Always have fallbacks: The feature should enhance, never break
  3. Rate limit everything: If it hits the internet, someone will try to abuse it
  4. Lazy load non-critical content: Don't let cool features slow down your page
  5. Open source is amazing: Standing on the shoulders of giants makes building faster

Try It Yourself

Visit the landing page from different locations (or use a VPN) and watch the map change. Better yet, check out the original repository and build something cool with it:

https://github.com/originalankur/maptoposter


Built with Laravel, OpenStreetMap, and a desire to make landing pages less boring.