This document provides comprehensive API documentation for embedding and interacting with the map application.
Overview
There are two primary ways to use the embeddable map:
| Method | Use Case | Data Size Limit | Dynamic Updates |
|---|---|---|---|
| URL Parameters | Simple static embeds | ~2000 chars (browser URL limit) | No |
| Iframe Message API | Dynamic, complex embeds | Unlimited | Yes |
URL Parameters
Configure the map directly via URL parameters when loading the iframe or visiting the page directly.
Parameter Reference
| Parameter | Format | Required | Description |
|---|---|---|---|
geojson |
URL-encoded GeoJSON | No | A valid GeoJSON Feature or FeatureCollection to render |
zoom |
Integer (0-19) | No | Initial zoom level. Default: 13 |
center |
lat,lon (WGS84) |
No | Initial map center coordinates. Default: 51.505, -0.09 (London) |
bbox |
minLon,minLat,maxLon,maxLat |
No | Bounding box to fit map to |
basemap |
String | No | Base map layer provider. Default: osm |
highlight |
String | No | Feature ID to highlight (from the GeoJSON) |
Available Basemaps
| ID | Description |
|---|---|
osm |
OpenStreetMap (default) |
satellite |
Satellite imagery |
terrain |
Terrain map |
dark |
Dark theme map |
Parameter Precedence
- If
bboxis provided, map fits to bounding box (overrideszoomandcenter) - If
centerand/orzoomprovided, use those - Otherwise, if GeoJSON has bounds, fit to GeoJSON bounds
- Otherwise, use default center (51.505, -0.09) and zoom (13)
URL Parameter Examples
Basic Usage
https://yourdomain.com/map/?geojson=<URL-encoded-GeoJSON>&zoom=14¢er=52.52,13.405
Example with Point Feature
https://yourdomain.com/map/?geojson=%7B%22type%22%3A%22Feature%22%2C%22geometry%22%3A%7B%22type%22%3A%22Point%22%2C%22coordinates%22%3A%5B13.405%2C52.52%5D%7D%2C%22properties%22%3A%7B%22name%22%3A%22Berlin%22%7D%7D&zoom=12
Decodes to:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [13.405, 52.52]
},
"properties": {
"name": "Berlin"
}
}
Example with Bounding Box
Fit the map to a specific bounding box covering Berlin:
https://yourdomain.com/map/?geojson=...&bbox=13.0,52.3,13.8,52.7
Example with Custom Basemap
Use the satellite basemap:
https://yourdomain.com/map/?geojson=...&basemap=satellite
Example with Feature Highlighting
Highlight a feature with ID "park-001":
https://yourdomain.com/map/?geojson=...&highlight=park-001
Example with All Parameters
https://yourdomain.com/map/?geojson=%7B...%7D&zoom=14¢er=52.52,13.405&basemap=terrain&highlight=berlin
Iframe Embedding
Basic Iframe Embed
<iframe
src="https://yourdomain.com/map/?geojson=<URL-encoded-GeoJSON>&zoom=14"
width="600"
height="400"
frameborder="0"
allowfullscreen
></iframe>
Encoding GeoJSON for URL
Use JavaScript's encodeURIComponent to properly encode GeoJSON:
const geojson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
id: "berlin",
geometry: {
type: "Point",
coordinates: [13.405, 52.52]
},
properties: {
name: "Berlin",
description: "Capital of Germany"
}
}
]
};
const geojsonUrlParam = encodeURIComponent(JSON.stringify(geojson));
Then use in the iframe:
<iframe
src="https://yourdomain.com/map/?geojson={{geojsonUrlParam}}&zoom=14"
width="600"
height="400"
frameborder="0"
></iframe>
Responsive Iframe Embed
<div class="map-container">
<iframe
src="https://yourdomain.com/map/?geojson=..."
frameborder="0"
allowfullscreen
></iframe>
</div>
<style>
.map-container {
position: relative;
padding-bottom: 60%; /* Aspect ratio (e.g., 16:9 would be 56.25%) */
height: 0;
overflow: hidden;
}
.map-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
Iframe Message API
For large GeoJSON data or dynamic updates, use the postMessage API instead of URL parameters.
Connection Setup
<iframe
id="mapFrame"
src="https://yourdomain.com/map/"
width="600"
height="400"
frameborder="0"
></iframe>
<script>
const mapFrame = document.getElementById('mapFrame');
// Wait for map to be ready
window.addEventListener('message', (event) => {
// IMPORTANT: Verify origin in production!
// if (event.origin !== 'https://yourdomain.com') return;
if (event.data.type === 'map:ready') {
console.log('Map is ready:', event.data.payload);
// Map is now ready to receive messages
}
});
</script>
Messages from Parent to Iframe
Send messages to the map iframe using postMessage:
// Get reference to iframe
const mapFrame = document.getElementById('mapFrame');
// Send to the iframe's contentWindow
mapFrame.contentWindow.postMessage(message, targetOrigin);
| Message Type | Payload | Description |
|---|---|---|
map:updateGeoJson |
{ geojson: FeatureCollection | Feature | Geometry } |
Update the GeoJSON data displayed on the map |
map:setView |
{ center: [lat, lng], zoom: number } |
Change map view (center and zoom) |
map:fitBounds |
{ bounds: [[minLng, minLat], [maxLng, maxLat]] } |
Fit map to bounding box |
map:highlightFeature |
{ featureId: string } |
Highlight a specific feature by ID |
map:getFeatureInfo |
{ featureId: string } |
Request information about a specific feature |
map:clear |
{} |
Clear all features from the map |
Example: Update GeoJSON
mapFrame.contentWindow.postMessage({
type: 'map:updateGeoJson',
payload: {
geojson: {
type: "FeatureCollection",
features: [/* ... */]
}
}
}, '*');
Example: Set View
mapFrame.contentWindow.postMessage({
type: 'map:setView',
payload: {
center: [52.52, 13.405],
zoom: 14
}
}, '*');
Example: Fit to Bounds
mapFrame.contentWindow.postMessage({
type: 'map:fitBounds',
payload: {
bounds: [[13.0, 52.3], [13.8, 52.7]]
}
}, '*');
Example: Highlight Feature
mapFrame.contentWindow.postMessage({
type: 'map:highlightFeature',
payload: {
featureId: "berlin"
}
}, '*');
Example: Request Feature Info
mapFrame.contentWindow.postMessage({
type: 'map:getFeatureInfo',
payload: {
featureId: "berlin"
}
}, '*');
Example: Clear Map
mapFrame.contentWindow.postMessage({
type: 'map:clear',
payload: {}
}, '*');
Messages from Iframe to Parent
Listen for messages from the map iframe:
window.addEventListener('message', (event) => {
// Verify origin in production!
// if (event.origin !== 'https://yourdomain.com') return;
const data = event.data;
switch (data.type) {
case 'map:ready':
// Map has initialized
break;
case 'map:featureClicked':
// A feature was clicked
break;
case 'map:viewportChanged':
// Map viewport changed
break;
case 'map:featureInfo':
// Response to getFeatureInfo request
break;
case 'map:error':
// An error occurred
break;
}
});
| Message Type | Payload | Description |
|---|---|---|
map:ready |
{ version: string, leafletVersion: string } |
Map has initialized and is ready to receive messages |
map:featureClicked |
{ id?: string, featureId?: string, properties: Record<string, any>, geometry: Geometry, coordinates: [lng, lat] } |
A feature was clicked on the map |
map:viewportChanged |
{ center: [lng, lat], zoom: number, bounds: [[minLng, minLat], [maxLng, maxLat]] } |
Map viewport changed (panning or zooming) |
map:featureInfo |
{ featureId: string, found: boolean, feature?: Feature, error?: string } |
Response to getFeatureInfo request |
map:error |
{ message: string, code: string, details?: any } |
Error occurred |
Example: Handle Feature Clicks
window.addEventListener('message', (event) => {
if (event.data.type === 'map:featureClicked') {
const { id, featureId, properties, coordinates } = event.data.payload;
console.log(`Feature ${featureId || id} clicked:`);
console.log(` Name: ${properties.name || 'Unknown'}`);
console.log(` Coordinates: [${coordinates.join(', ')}]`);
}
});
Example: Handle Viewport Changes
window.addEventListener('message', (event) => {
if (event.data.type === 'map:viewportChanged') {
const { center, zoom, bounds } = event.data.payload;
console.log(`Map moved to [${center.join(', ')}] at zoom ${zoom}`);
console.log(`Bounds: ${bounds[0].join(', ')} to ${bounds[1].join(', ')}`);
}
});
Example: Handle Feature Info Response
window.addEventListener('message', (event) => {
if (event.data.type === 'map:featureInfo') {
const { featureId, found, feature, error } = event.data.payload;
if (found) {
console.log(`Feature ${featureId}:`, feature);
} else {
console.log(`Feature ${featureId} not found:`, error);
}
}
});
Example: Handle Errors
window.addEventListener('message', (event) => {
if (event.data.type === 'map:error') {
const { message, code, details } = event.data.payload;
console.error(`Map error [${code}]: ${message}`, details);
}
});
TypeScript Types
For TypeScript users embedding the map:
// GeoJSON types (from @types/geojson)
import type { Feature, FeatureCollection, Geometry } from 'geojson';
// Messages from Parent to Iframe
type ParentToIframeMessage =
| { type: 'map:updateGeoJson'; payload: { geojson: FeatureCollection | Feature | Geometry } }
| { type: 'map:setView'; payload: { center: [number, number]; zoom: number } }
| { type: 'map:fitBounds'; payload: { bounds: [[number, number], [number, number]] } }
| { type: 'map:highlightFeature'; payload: { featureId: string } }
| { type: 'map:getFeatureInfo'; payload: { featureId: string } }
| { type: 'map:clear'; payload: {} };
// Messages from Iframe to Parent
type IframeToParentMessage =
| { type: 'map:ready'; payload: { version: string; leafletVersion: string } }
| { type: 'map:featureClicked'; payload: { id?: string; featureId?: string; properties: Record<string, any>; geometry: Geometry; coordinates: [number, number] } }
| { type: 'map:viewportChanged'; payload: { center: [number, number]; zoom: number; bounds: [[number, number], [number, number]] } }
| { type: 'map:featureInfo'; payload: { featureId: string; found: boolean; feature?: Feature; error?: string } }
| { type: 'map:error'; payload: { message: string; code: 'INVALID_GEOJSON' | 'MISSING_PARAMETER' | 'RENDER_ERROR' | 'UNKNOWN'; details?: any } };
// Combined type for message handling
type MapMessage = ParentToIframeMessage | IframeToParentMessage;
// Example usage with type checking
const mapFrame = document.getElementById('mapFrame') as HTMLIFrameElement;
window.addEventListener('message', (event: MessageEvent<MapMessage>) => {
switch (event.data.type) {
case 'map:ready':
// event.data.payload.version is string
break;
case 'map:featureClicked':
// event.data.payload.properties is Record<string, any>
break;
case 'map:error':
// event.data.payload.code is one of the known error codes
break;
}
});
// Sending messages with type safety
function sendMapMessage(frame: HTMLIFrameElement, message: ParentToIframeMessage): void {
frame.contentWindow?.postMessage(message, '*');
}
sendMapMessage(mapFrame, {
type: 'map:updateGeoJson',
payload: { geojson: myGeoJson }
});
GeoJSON Support
The app supports all standard GeoJSON geometry types:
| GeoJSON Type | Leaflet Layer | Description |
|---|---|---|
Point |
CircleMarker | Single point/location |
MultiPoint |
Multiple CircleMarkers | Multiple points |
LineString |
Polyline | Connected line segments |
MultiLineString |
Multiple Polylines | Multiple line strings |
Polygon |
Polygon | Closed shape with holes |
MultiPolygon |
Multiple Polygons | Multiple polygons |
GeometryCollection |
LayerGroup | Collection of geometries |
Feature |
Depends on geometry type | Geometry + properties |
FeatureCollection |
LayerGroup | Collection of Features |
Feature Properties
GeoJSON Features can include properties that the app uses for rendering and interaction:
| Property | Type | Usage |
|---|---|---|
id |
String/Number | Unique identifier for the feature (used for highlighting) |
name |
String | Display name (used in popups) |
description |
String | Additional info (used in popups) |
stroke |
String | CSS color for stroke (lines/polygons) |
fill |
String | CSS color for fill (polygons) |
fillOpacity |
Number | Fill opacity (0-1) |
strokeWidth |
Number | Stroke width in pixels |
| Any custom property | Any | Available in popups and click events |
Example GeoJSON Feature
{
"type": "Feature",
"id": "park-001",
"geometry": {
"type": "Polygon",
"coordinates": [[[13.3, 52.4], [13.5, 52.4], [13.5, 52.6], [13.3, 52.6], [13.3, 52.4]]]
},
"properties": {
"name": "City Park",
"description": "A beautiful urban park",
"category": "recreation",
"fill": "#4CAF50",
"fillOpacity": 0.5,
"stroke": "#2E7D32",
"strokeWidth": 2
}
}
Example FeatureCollection
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "berlin",
"geometry": {
"type": "Point",
"coordinates": [13.405, 52.52]
},
"properties": {
"name": "Berlin",
"population": 3750000
}
},
{
"type": "Feature",
"id": "paris",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"properties": {
"name": "Paris",
"population": 2160000
}
}
]
}
Coordinate Order
Important: GeoJSON uses [longitude, latitude] order (x, y), which matches the standard.
{
"type": "Point",
"coordinates": [13.405, 52.52] // [lng, lat] - Berlin
}
The app automatically converts to Leaflet's [lat, lng] format internally.
Complete Example: Interactive Map Embed
Here's a complete HTML page that demonstrates all the Message API features:
<!DOCTYPE html>
<html>
<head>
<title>Map Embedding Example</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1, h2 {
color: #333;
}
.map-container {
position: relative;
padding-bottom: 60%;
height: 0;
overflow: hidden;
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 8px;
}
.map-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.controls {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
button {
padding: 10px 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #45a049;
}
#status {
padding: 12px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
margin: 20px 0;
}
.feature-list {
margin-top: 20px;
}
.feature-item {
padding: 8px 12px;
border: 1px solid #eee;
margin: 4px 0;
border-radius: 4px;
cursor: pointer;
}
.feature-item:hover {
background: #f5f5f5;
}
</style>
</head>
<body>
<h1>Map Embedding Demo</h1>
<p>This page demonstrates how to embed and interact with the map using the Message API.</p>
<div class="map-container">
<iframe id="mapFrame" src="https://yourdomain.com/map/" frameborder="0"></iframe>
</div>
<div id="status">Waiting for map to load...</div>
<div class="controls">
<button onclick="updateGeoJson()">Load Cities</button>
<button onclick="fitToGermany()">Fit to Germany</button>
<button onclick="highlightFeature('berlin')">Highlight Berlin</button>
<button onclick="highlightFeature('munich')">Highlight Munich</button>
<button onclick="clearMap()">Clear Map</button>
<button onclick="setBasemap('satellite')">Satellite View</button>
<button onclick="setBasemap('osm')">Street View</button>
</div>
<div class="feature-list" id="featureList"></div>
<script>
const mapFrame = document.getElementById('mapFrame');
const statusEl = document.getElementById('status');
const featureListEl = document.getElementById('featureList');
let currentFeatures = [];
// Listen for messages from the map
window.addEventListener('message', (event) => {
// In production, verify the origin!
// if (event.origin !== 'https://yourdomain.com') return;
const data = event.data;
if (data.type === 'map:ready') {
statusEl.textContent = `Map ready (v${data.payload.version}, Leaflet ${data.payload.leafletVersion})`;
// Load initial data
updateGeoJson();
}
if (data.type === 'map:featureClicked') {
const { id, featureId, properties, coordinates } = data.payload;
statusEl.textContent = `Clicked: ${properties.name || featureId || id} at [${coordinates.join(', ')}]`;
}
if (data.type === 'map:viewportChanged') {
const { center, zoom, bounds } = data.payload;
console.log('Viewport:', { center, zoom, bounds });
}
if (data.type === 'map:featureInfo') {
const { featureId, found, feature, error } = data.payload;
if (found) {
console.log('Feature info:', feature);
} else {
console.log('Feature not found:', featureId, error);
}
}
if (data.type === 'map:error') {
statusEl.textContent = `Error: ${data.payload.message} (${data.payload.code})`;
console.error('Map error:', data.payload);
}
});
function sendMessage(type, payload) {
mapFrame.contentWindow.postMessage({ type, payload }, '*');
}
function updateGeoJson() {
currentFeatures = [
{
type: "Feature",
id: "berlin",
geometry: {
type: "Point",
coordinates: [13.405, 52.52]
},
properties: {
name: "Berlin",
description: "Capital of Germany",
population: 3750000
}
},
{
type: "Feature",
id: "munich",
geometry: {
type: "Point",
coordinates: [11.582, 48.135]
},
properties: {
name: "Munich",
description: "Capital of Bavaria",
population: 1500000
}
},
{
type: "Feature",
id: "hamburg",
geometry: {
type: "Point",
coordinates: [9.993, 53.551]
},
properties: {
name: "Hamburg",
description: "Port city in northern Germany",
population: 1900000
}
}
];
sendMessage('map:updateGeoJson', {
geojson: { type: "FeatureCollection", features: currentFeatures }
});
// Update feature list
featureListEl.innerHTML = '<h3>Features:</h3>' +
currentFeatures.map(f =>
`<div class="feature-item" onclick="fitToFeature('${f.id}')">
<strong>${f.properties.name}</strong> - ${f.properties.description}
</div>`
).join('');
statusEl.textContent = 'Loaded 3 German cities';
}
function fitToGermany() {
sendMessage('map:fitBounds', {
bounds: [[5.8, 47.2], [15.1, 55.1]]
});
statusEl.textContent = 'Fitted to Germany bounds';
}
function highlightFeature(featureId) {
sendMessage('map:highlightFeature', { featureId });
statusEl.textContent = `Highlighted: ${featureId}`;
}
function clearMap() {
sendMessage('map:clear', {});
statusEl.textContent = 'Map cleared';
}
function setBasemap(basemap) {
// Note: basemap can also be set via URL parameter
statusEl.textContent = `Changed basemap to: ${basemap}`;
// In a real implementation, you might need to reload the iframe
// or use a custom message type for basemap changes
}
function fitToFeature(featureId) {
const feature = currentFeatures.find(f => f.id === featureId);
if (feature) {
const [lng, lat] = feature.geometry.coordinates;
sendMessage('map:setView', {
center: [lat, lng],
zoom: 12
});
highlightFeature(featureId);
}
}
</script>
</body>
</html>
Tips and Best Practices
URL Parameter vs Message API
| Aspect | URL Parameters | Message API |
|---|---|---|
| Simplicity | ✅ Very simple | ⚠️ Requires JavaScript |
| Data Size | ❌ Limited by URL length (~2000 chars) | ✅ Unlimited |
| Dynamic Updates | ❌ No | ✅ Yes |
| Complex Interactions | ❌ No | ✅ Yes |
| Caching | ✅ Browser may cache | ✅ No caching issues |
Use URL parameters for:
- Simple, static embeds
- Small GeoJSON data (< 2000 characters when URL-encoded)
- One-time configuration
- Embedding in CMS or platforms without JavaScript
Use Message API for:
- Large GeoJSON data
- Dynamic updates
- Complex interactions with the parent page
- Real-time feature updates
- Custom feature styling and behavior
Encoding Tips
Always use encodeURIComponent for URL parameters:
// Correct
const url = `https://map.example.com/?geojson=${encodeURIComponent(JSON.stringify(geojson))}`;
// Wrong (will break with special characters)
const url = `https://map.example.com/?geojson=${JSON.stringify(geojson)}`;
Feature Identification
Features are identified using this priority:
feature.id(GeoJSON standard)feature.properties.featureIdfeature.properties.id
Ensure your features have unique IDs if you plan to use highlighting or feature info requests.
Origin Security
Important: In production, always verify the message origin:
window.addEventListener('message', (event) => {
// Verify the origin matches your expected domain
if (event.origin !== 'https://yourdomain.com') return;
// Now it's safe to process the message
if (event.data.type === 'map:ready') {
// ...
}
});
Coordinate System
- GeoJSON standard:
[longitude, latitude](x, y) - Leaflet:
[latitude, longitude](y, x)
The app automatically converts between these formats, so always use GeoJSON's [lng, lat] in your data.
Troubleshooting
Map doesn't load in iframe
- Check that the hosting domain has proper CORS headers
- Ensure
X-Frame-Optionsis not set toDENYon the server - Verify the iframe
srcURL is correct and accessible
Messages not received
- Verify the iframe has loaded before sending messages
- Check that message types are spelled correctly (case-sensitive)
- Ensure proper origin verification is in place (or use
'*'for development)
Features not rendering
- Validate your GeoJSON structure using a tool like https://geojson.io
- Ensure coordinates are in
[longitude, latitude]order - Check browser console for parsing errors
URL too long
- URL parameters have browser-specific limits (~2000 characters)
- Switch to the Message API for large GeoJSON data
- Consider simplifying your GeoJSON or using a URL to hosted GeoJSON
See Also
- Main README - Project overview, installation, and deployment
- GeoJSON Specification - Official GeoJSON format
- Leaflet Documentation - Mapping library docs