---
name: preview-leaflet
description: Create interactive maps with markers, routes, and geographic data using Leaflet
user-invocable: true
commands:
- preview-leaflet
---
# Preview Leaflet Skill
Interactive map visualization viewer using Leaflet library for geographic data, markers, routes, and custom overlays.
## Agent Usage
When the user asks to create a map visualization, write the Leaflet code and pipe it to the script. Use the Bash tool to execute this skill's `run.sh` script:
```bash
# Pipe Leaflet code
cat route.js | ./run.sh
# Or from a file
./run.sh city-map.leaflet
```
The script handles all HTML generation and **automatically opens the result in the browser**. Do NOT open the file manually to avoid duplicate tabs.
## Usage
```bash
# Preview a Leaflet map file
/preview-leaflet city-map.leaflet
# Or use .map extension
/preview-leaflet route.map
# Pipe Leaflet code (preferred for temporary content)
cat route.js | /preview-leaflet
echo "const map = L.map('map').setView([51.505, -0.09], 13);" | /preview-leaflet
```
**Best Practice:** For temporary or generated maps, prefer piping over creating temporary files. This avoids cluttering your filesystem and the content is automatically cleaned up.
## Options
The script works with sensible defaults but supports these flags for flexibility:
- `-o, --output PATH` - Custom output path
- `--no-browser` - Skip browser, output file path only
## Features
- **Interactive maps** with pan and zoom
- **Markers and popups** for locations
- **Routes and paths** with polylines
- **Shapes and areas** with polygons and circles
- **Multiple tile layers** (OpenStreetMap, CartoDB, etc.)
- **Tooltips** on hover
- **Fit bounds** to show all points
- **Reset view** button
- **Code viewer** toggle
- **Responsive design** adapts to window size
## When to Use This Skill
Use this skill when the user wants to:
- Visualize geographic data on maps
- Plot locations with markers
- Draw routes between points
- Show service areas or regions
- Create interactive location-based visualizations
## Leaflet Code Requirements
Your code should:
1. Initialize the map with `L.map('map').setView([lat, lng], zoom)`
2. Add a tile layer (provides map imagery)
3. Add markers, paths, or other features
## Execution Context
Your code runs with:
- **Leaflet v1.9.4** available as `L`
- **Map container** `#map` sized and ready in DOM
## Complete Example - Simple Map with Marker
```javascript
// Initialize map centered on London
const map = L.map('map').setView([51.505, -0.09], 13);
// Add OpenStreetMap tiles (REQUIRED - map is blank without this)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
// Add a marker
L.marker([51.505, -0.09]).addTo(map).bindPopup('Hello World!').openPopup();
```
## Complete Example - Route with Multiple Points
```javascript
// Initialize map
const map = L.map('map').setView([40.7128, -74.006], 12);
// Add tiles (REQUIRED)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
// Define route points
const route = [
[40.7128, -74.006], // New York
[40.758, -73.9855], // Times Square
[40.7614, -73.9776], // Central Park
];
// Draw route
L.polyline(route, {
color: 'red',
weight: 3,
}).addTo(map);
// Add markers
route.forEach((point, i) => {
L.marker(point)
.addTo(map)
.bindPopup(`Stop ${i + 1}`);
});
// Fit map to show all points
map.fitBounds(L.latLngBounds(route), { padding: [50, 50] });
```
## Common Patterns
### Markers
```javascript
// Simple marker
L.marker([51.5, -0.09]).addTo(map);
// With popup
L.marker([51.5, -0.09]).addTo(map).bindPopup('Location name');
// Circle marker (fixed pixel size)
L.circleMarker([51.5, -0.09], {
radius: 8,
fillColor: '#3498db',
color: 'white',
weight: 2,
fillOpacity: 0.9,
}).addTo(map);
```
### Lines and Shapes
```javascript
// Polyline (route/path)
L.polyline(
[
[51.5, -0.09],
[51.51, -0.08],
[51.52, -0.07],
],
{
color: 'red',
weight: 3,
}
).addTo(map);
// Polygon (area)
L.polygon(
[
[51.5, -0.09],
[51.51, -0.08],
[51.52, -0.09],
],
{
color: 'blue',
fillOpacity: 0.5,
}
).addTo(map);
// Circle (fixed radius in meters)
L.circle([51.5, -0.09], {
radius: 500, // meters
color: 'red',
fillOpacity: 0.5,
}).addTo(map);
```
### Popups and Tooltips
```javascript
// HTML in popup
marker.bindPopup(`
Location Name
Description here
`);
// Tooltip (shows on hover)
marker.bindTooltip('Tooltip text', {
permanent: false,
direction: 'top',
});
// Permanent tooltip (always visible)
marker.bindTooltip('Always visible', {
permanent: true,
direction: 'right',
});
```
### View Control
```javascript
// Set view (instant)
map.setView([51.5, -0.09], 13);
// Fly to (animated)
map.flyTo([51.5, -0.09], 13, {
duration: 2, // seconds
});
// Fit bounds to show all features
const bounds = L.latLngBounds([
[51.49, -0.1],
[51.51, -0.08],
]);
map.fitBounds(bounds, {
padding: [50, 50],
});
```
## Tile Layers (Map Styles)
### OpenStreetMap (free, default)
```javascript
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
```
### CartoDB Light (clean, minimal)
```javascript
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap, © CartoDB',
maxZoom: 18,
}).addTo(map);
```
### CartoDB Dark
```javascript
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap, © CartoDB',
maxZoom: 18,
}).addTo(map);
```
## Complete Example - Multiple Locations
```javascript
// Initialize map
const map = L.map('map').setView([37.7749, -122.4194], 10);
// Add tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(map);
// Locations data
const locations = [
{ name: 'San Francisco', coords: [37.7749, -122.4194], type: 'city' },
{ name: 'Oakland', coords: [37.8044, -122.2712], type: 'city' },
{ name: 'Berkeley', coords: [37.8715, -122.273], type: 'city' },
];
// Add markers
locations.forEach((loc) => {
const color = loc.type === 'city' ? 'blue' : 'red';
L.circleMarker(loc.coords, {
radius: 8,
fillColor: color,
color: 'white',
weight: 2,
fillOpacity: 0.8,
})
.addTo(map)
.bindPopup(`${loc.name}
Type: ${loc.type}`);
});
// Fit map to show all locations
const allCoords = locations.map((loc) => loc.coords);
map.fitBounds(L.latLngBounds(allCoords), { padding: [50, 50] });
```
## Coordinate Format
Leaflet uses `[latitude, longitude]` format:
- **Latitude**: -90 to 90 (South to North)
- **Longitude**: -180 to 180 (West to East)
**Examples:**
- New York: `[40.7128, -74.0060]`
- London: `[51.5074, -0.1278]`
- Tokyo: `[35.6762, 139.6503]`
- Sydney: `[-33.8688, 151.2093]`
**Common mistake:** Reversing coordinates to `[lng, lat]` - always use `[lat, lng]`!
## Built-in Features
- **Pan/zoom** - Click and drag to pan, scroll to zoom
- **Zoom buttons** - +/- controls in top-left
- **Reset view** - Button to restore initial view
- **Code viewer** - Toggle to show/hide source
- **Responsive** - Automatically resizes with window
## Zoom Levels
- **1**: World view
- **5**: Continent
- **10**: City
- **15**: Streets
- **18**: Buildings
## Best Practices
1. **Always add tile layer** - Map is blank without it
2. **Use appropriate zoom** - 1 (world) to 18 (street level)
3. **Use fitBounds() for multi-point maps** - Shows all features
4. **Embed data in code** - Include coordinates directly
5. **Verify coordinate order** - `[lat, lng]` not `[lng, lat]`
## Troubleshooting
### Map appears blank/gray
- Ensure tile layer is added with `.addTo(map)`
- Check coordinates are valid (`[lat, lng]` format)
- Verify internet connection (tiles load from CDN)
- Check browser console for 404 errors on tiles
### Markers don't appear
- Check coordinate format: `[latitude, longitude]`
- Ensure `.addTo(map)` is called on marker
- Verify coordinates are in current view
- Try using fitBounds() to see all markers
### Tiles don't load
- Check internet connection
- Verify tile URL is correct
- Look for 404 errors in browser console
- Try a different tile provider
### Wrong location shown
- Verify coordinates are correct
- Check coordinate order (lat first, lng second)
- Ensure zoom level is appropriate
## Technical Details
### File Requirements
- File extension: `.leaflet`
- Maximum size: 10MB (configurable)
- Valid JavaScript code
- Self-contained (no external data files)
### Browser Compatibility
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Requires JavaScript enabled
- Requires internet connection for tiles
- CDN-dependent: Leaflet v1.9.4
## Output
The skill generates a standalone HTML file at:
```
/tmp/preview-skills/preview-leaflet-{filename}.html
```
## Development
This skill is standalone and includes all dependencies:
- Shared libraries bundled in `lib/`
- Templates bundled in `templates/`
- External CDN dependencies: Leaflet v1.9.4
To modify the skill:
1. Edit `config.sh` for configuration
2. Edit `templates/scripts/leaflet-renderer.js` for behavior
3. Edit `templates/styles/leaflet.css` for styling
4. Run `run.sh` to test changes
## Learn More
- Leaflet Documentation: https://leafletjs.com/
- Leaflet Tutorials: https://leafletjs.com/examples.html
- Tile Providers: https://leaflet-extras.github.io/leaflet-providers/preview/