Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use PhantomJS to open the page, wait for a map-specific ready signal, then call page.render(). Setting a viewport before loading, checking the open status, and waiting for tiles and marker overlays are the parts that determine whether the PNG or JPEG is actually usable. The page.open callback only means the document load completed; it does not prove that asynchronous map tiles have finished drawing.
What you will build
The following script captures a map page at a chosen viewport and writes map.png. Your page must initialize the map and add its markers itself. Replace the readiness placeholder with a signal that your application sets after the map and any required tiles are ready.
var page = require('webpage').create();
page.viewportSize = { width: 1200, height: 800 };
page.open('https://example.test/map', function (status) {
if (status !== 'success') {
console.log('Map page failed to load');
phantom.exit(1);
return;
}
// Replace this with your map's real readiness check.
// For example, poll a flag exposed by the page after markers and tiles load.
waitForMapReady(function () {
page.render('map.png');
phantom.exit();
});
});
function waitForMapReady(callback) {
var start = Date.now();
var timer = setInterval(function () {
page.evaluate(function () {
return window.mapReady === true;
}, function (ready) {
if (ready) {
clearInterval(timer);
callback();
} else if (Date.now() - start > 30000) {
clearInterval(timer);
console.log('Map readiness timeout');
phantom.exit(1);
}
});
}, 200);
}
Run it with the PhantomJS executable, for example phantomjs capture-map.js. The output format is inferred from the filename extension. Use .png, .jpg or another format supported by your Qt build.
Prepare the map page and markers
Expose a reliable ready signal
On a page you control, set a flag only after the map has been created, markers added, and the tile work needed for the image has completed:
#1 Best Overall
- Set of 2 Posters
- Map posters are 18” x 29” in size
- High-quality 3 MIL lamination for added durability
- Tear Resistant
var map = L.map('map').setView([40.7128, -74.0060], 12);
var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
L.marker([40.7128, -74.0060]).addTo(map).bindPopup('New York');
L.marker([40.7306, -73.9352]).addTo(map).bindPopup('Second marker');
var pending = 0;
tiles.on('loading', function () { pending += 1; });
tiles.on('load', function () {
pending = Math.max(0, pending - 1);
if (pending === 0) window.mapReady = true;
});
map.whenReady(function () {
if (pending === 0) window.mapReady = true;
});
Leaflet’s normal pattern is L.map(...).setView(...), a tile layer, and L.marker([latitude, longitude]).addTo(map). Tile providers have their own terms; OpenStreetMap data requires attribution, and other providers generally do as well. Keep the attribution visible in the capture.
Google Maps pages
Google Maps markers are geographic overlays attached to latitude/longitude coordinates. Google documents both raster maps (tiles served to the page) and vector maps (composed client-side with WebGL). The <gmp-map> element defaults to vector rendering, while the traditional google.maps.Map div implementation defaults to raster. Do not assume an older PhantomJS build can render current vector maps or every Google API feature; test the exact page and credentials you use.
Capture sequence, step by step
- Make the target deterministic. Ensure the URL opens the intended zoom, center, markers, language, and authentication state. If markers depend on an API call, expose readiness only after that call succeeds.
- Set the viewport. Assign
page.viewportSizebeforepage.open. A 1200 × 800 viewport is a practical starting point; the official PhantomJS screen-capture example uses 1920 × 1080. - Open and validate. In the callback, continue only when
status === 'success'. A failed load should produce a nonzero exit status rather than a misleading blank image. - Wait for map readiness. Poll a page flag, call a page-defined callback, or wait for the map library’s tile-load event. A fixed delay is only a fallback. PhantomJS’s simple homepage example uses 200 ms, but that interval is not a guarantee for maps.
- Render. Call
page.render('map.png')after readiness. Then callphantom.exit()so the process closes cleanly. - Inspect the file. Check tile completeness, marker placement, clipping, attribution, controls, and labels at the selected dimensions.
Crop the map or tune output quality
To save only a rectangle, set page.clipRect before rendering:
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
- Updated
- Each Poster 18" tall x 29" wide
- High-quality 3 MIL lamination for added durability
- Tear Resistant
page.clipRect = { top: 80, left: 40, width: 1120, height: 640 };
page.render('map-crop.png');
The rectangle is in CSS pixels relative to the page. Make it large enough to include every marker; a marker near an edge can be cut off even when the map itself is complete.
PhantomJS page.render() can produce PDF, PNG, JPEG, BMP, PPM, and GIF where the installed Qt build supports them. JPEG quality is documented from 0 to 100. PNG compression changes file size, not visual appearance. Choose PNG for crisp labels and transparent-style edges, JPEG for smaller photographic composites, and a PDF only when a document page is the actual deliverable.
Waiting for asynchronous tiles without a page flag
If you cannot modify the page, use a conservative polling strategy around a DOM condition that means the map exists, then allow the tile layer additional time. For example, a Leaflet page might expose a map container and tile images:
Rank #3
- FOLDED EDITION - portable 8x10 inch folded size
- WORLD MAP is printed on 24lb paper
- 3D SHADED RELIEF: 3D shaded visual terrain relief for land and oceans
- PERFECT world map for business, home or educational use
- UP-TO-DATE: completely current world wall map poster
function waitForLeaflet(callback) {
var started = Date.now();
var timer = setInterval(function () {
var state = page.evaluate(function () {
var map = document.querySelector('.leaflet-container');
var tiles = map ? map.querySelectorAll('img.leaflet-tile') : [];
var complete = true;
for (var i = 0; i < tiles.length; i++) {
if (!tiles[i].complete || tiles[i].naturalWidth === 0) complete = false;
}
return !!map && tiles.length > 0 && complete;
});
if (state || Date.now() - started > 30000) {
clearInterval(timer);
callback(state);
}
}, 250);
}
This is still a heuristic: CSS backgrounds, canvas-rendered layers, authentication failures, or a provider that keeps requesting tiles can defeat it. A signal emitted by the application or map library is more reliable.
Common failure modes and fixes
Blank or partially blank map
- Cause: Rendering immediately in
page.openwhile tiles are still downloading. - Fix: Wait for the provider’s tile completion event or an application-level flag; increase the timeout only after verifying the network is actually progressing.
Markers are missing
- Cause: Marker creation occurs after the readiness check, or marker data requests failed.
- Fix: Set the ready flag after marker layers are added and verify the marker count in
page.evaluate. Log API responses and authentication errors in the page.
Capture exits with a failed status
- Cause: DNS, TLS, redirects, blocked resources, or a server-side error.
- Fix: Keep the
statuscheck, print PhantomJS resource errors, and open the same URL in a maintained browser to distinguish a site failure from PhantomJS incompatibility.
Current map controls or vector layers do not appear
- Cause: PhantomJS 2.1.1 is an old WebKit-based engine and may not implement modern JavaScript, WebGL, or browser APIs used by the page.
- Fix: Prefer a maintained headless browser for new work, or use a static map image when the page itself is unnecessary.
Attribution or controls are clipped
- Cause: An aggressive
clipRect, a viewport shorter than the map, or CSS that positions attribution outside the crop. - Fix: Capture the full map first, then reduce the rectangle while checking the provider’s required attribution remains visible.
Authentication-dependent tiles fail
- Cause: The page needs cookies, headers, or a session that PhantomJS does not have.
- Fix: Establish the session before opening the map, set the required cookies or headers, and confirm that the map provider permits automated access.
PhantomJS, Puppeteer, or a static map request?
PhantomJS is suitable mainly for a legacy page that is already known to render in its engine. The project homepage states, “Important: PhantomJS development is suspended until further notice.” Its repository is archived and read-only, and the maintainer’s suspension notice identifies 2.1.1 as the last known stable version.
| Option | Best fit | Important trade-off |
|---|---|---|
| PhantomJS | Existing legacy scripts and simple raster map pages | Suspended project; compatibility with current sites and APIs is limited |
| Puppeteer | New browser automation and pages requiring a maintained headless browser | Still requires page-specific waits, credentials, and testing; its availability does not guarantee a particular map will work |
| Google Maps Static API | A map image with supported markers, center, zoom, and map type | Requires an API key and is not a screenshot of arbitrary page content |
Google Maps Static API requests support dimensions, map type, center/zoom, markers, and paths. Geocoded marker locations are limited to 15 per request; coordinates supplied directly are not subject to that geocoding-specific limit. URLs are limited to 16,384 characters, and documentation notes that support may offer larger images up to 2048 × 2048 pixels. Use the static API when those constraints fit and you do not need the surrounding webpage or custom DOM overlays.
Rank #4
Or skip the browser setup
ScreenshotNeo provides a single-call website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
For a map page, the basic request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.test/map -o map.webp
See the ScreenshotNeo documentation for all options. The same endpoint supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, 12 device presets or custom viewports, retina scale, PDF output, custom CSS and JavaScript, click-before-capture actions, hidden selectors, waits for selectors/delays/network idle, request and resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.test/map"}, timeout=90)
r.raise_for_status()
open("map.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.test/map' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
require('fs').writeFileSync('map.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also offers take_screenshot, get_page_info, and capture_pdf through its MCP server for Claude, Cursor, and other MCP clients. Every plan includes every feature: 1,000 shots per month free with no card, then Starter is $5 for 3,000 shots, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; annual billing gives two months free. Create a free ScreenshotNeo account to get started.
Operational checklist
- Confirm the map provider permits your automated use and preserve required attribution.
- Use a viewport and crop that contain every marker and label.
- Wait for a map-specific signal, not merely
page.open. - Fail fast on load errors and readiness timeouts.
- Review output for missing tiles, authentication failures, clipping, and unsupported rendering technology.
- For new systems, test a maintained browser or a static map endpoint before committing to PhantomJS.
Frequently Asked Questions
Can PhantomJS save a JPEG instead of a PNG?
Yes. Pass a filename such as map.jpg to page.render(); the extension selects the format when the Qt build supports it. JPEG quality can be configured from 0 to 100.
Best Value
- Set of 2 Posters
- Map posters are 18” x 29” in size
- High-quality 3 MIL lamination for added durability
- Tear Resistant
Does a successful page.open mean all map tiles are ready?
No. It reports document loading, while tiles, overlays, and marker data can arrive afterward. Wait for an application- or map-specific readiness condition.
What should I use when I only need a map image and markers?
A static map API is often simpler, provided its marker, URL-length, image-size, API-key, attribution, and provider-term requirements fit your use case.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

