A lightweight Leaflet.js plugin that clips/crops WMS TileLayers on the client-side, so the WMS layer is only visible inside a specified boundary (Polygon, GeoJSON, or extent).
Installation
Option 1: npm (Recommended for React/Node.js projects)
npm install leaflet-wms-crop
Usage in React/Next.js:
import 'leaflet-wms-crop'; // or import 'leaflet-wms-crop/leaflet-wms-crop.js';
Option 2: CDN (Recommended for HTML/vanilla JS)
Via GitHub (jsDelivr):
<script src="https://cdn.jsdelivr.net/gh/amanchry/leaflet-wms-crop@latest/leaflet-wms-crop.js"></script>
Via npm (unpkg.com):
<script src="https://unpkg.com/leaflet-wms-crop@latest/leaflet-wms-crop.js"></script>
Quick Start
Basic HTML Example
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> </head> <body> <div id="map" style="height: 100vh;"></div> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/amanchry/leaflet-wms-crop@latest/leaflet-wms-crop.js"></script> <script> // Create map var map = L.map('map').setView([20, 77], 5); // Add base layer L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); // Define boundary polygon (array of [lat, lng] coordinates) var boundary = [ [35.6, 68.0], // NW [35.6, 97.5], // NE [6.5, 97.5], // SE [6.5, 68.0], // SW [35.6, 68.0] // Close polygon ]; // Create clipped WMS layer var wmsLayer = L.tileLayer.wms.clipped('https://services.terrascope.be/wms/v2', { layers: 'WORLDCOVER_2021_MAP', format: 'image/png', transparent: true, opacity: 1 }, boundary); wmsLayer.addTo(map); </script> </body> </html>
API Reference
Main Method
L.tileLayer.wms.clipped(baseUrl, options, boundary)
Parameters
- baseUrl (String): WMS server URL
- options (Object): WMS layer options
layers(String): Comma-separated layer names (required)format(String): Image format, default:'image/png'transparent(Boolean): Enable transparency, default:trueopacity(Number): Layer opacity 0-1, default:1attribution(String): Attribution texttileSize(Number): Tile size in pixels, default:256
- boundary: Boundary to clip to (see Boundary Formats below)
Returns
L.TileLayer.WMS.Clipped - A Leaflet tile layer clipped to the boundary
Methods
setBoundary(boundary)
Update the clipping boundary dynamically.
var newBoundary = [ [28.0, 68.0], [38.0, 68.0], [38.0, 98.0], [28.0, 98.0], [28.0, 68.0] ]; wmsLayer.setBoundary(newBoundary);
getBoundary()
Get the current clipping boundary.
var currentBoundary = wmsLayer.getBoundary(); console.log(currentBoundary); // Array of [lat, lng] pairs
Boundary Formats
The plugin supports multiple boundary formats for clipping WMS layers. The boundary will be automatically normalized internally.
1. Array of Coordinates
The simplest format: an array of [lat, lng] coordinate pairs.
var boundary = [ [35.6, 68.0], // NW [35.6, 97.5], // NE [6.5, 97.5], // SE [6.5, 68.0], // SW [35.6, 68.0] // Close polygon ]; var wmsLayer = L.tileLayer.wms.clipped(url, options, boundary);
2. L.LatLngBounds
Use Leaflet's LatLngBounds for rectangular boundaries.
var bounds = L.latLngBounds([6.5, 68.0], [35.6, 97.5]); // [south, west], [north, east] var wmsLayer = L.tileLayer.wms.clipped(url, options, bounds);
3. L.Polygon
Use an existing Leaflet Polygon layer.
var polygon = L.polygon([ [35.6, 68.0], [35.6, 97.5], [6.5, 97.5], [6.5, 68.0] ]); var wmsLayer = L.tileLayer.wms.clipped(url, options, polygon);
4. GeoJSON Polygon
Standard GeoJSON Polygon format. Note: GeoJSON uses [lng, lat] coordinate order, which is automatically converted.
var geoJsonPolygon = { "type": "Polygon", "coordinates": [[ [68.0, 35.6], // [lng, lat] [97.5, 35.6], [97.5, 6.5], [68.0, 6.5], [68.0, 35.6] // Close polygon ]] }; var wmsLayer = L.tileLayer.wms.clipped(url, options, geoJsonPolygon);
5. GeoJSON MultiPolygon
GeoJSON MultiPolygon format. Only the first polygon in the MultiPolygon will be used.
var geoJsonMultiPolygon = { "type": "MultiPolygon", "coordinates": [[[ [68.0, 35.6], [97.5, 35.6], [97.5, 6.5], [68.0, 6.5], [68.0, 35.6] ]]] }; var wmsLayer = L.tileLayer.wms.clipped(url, options, geoJsonMultiPolygon);
6. GeoJSON Feature
A GeoJSON Feature object containing a Polygon or MultiPolygon geometry.
var geoJsonFeature = { "type": "Feature", "properties": { "name": "India Boundary" }, "geometry": { "type": "Polygon", "coordinates": [[ [68.0, 35.6], [97.5, 35.6], [97.5, 6.5], [68.0, 6.5], [68.0, 35.6] ]] } }; var wmsLayer = L.tileLayer.wms.clipped(url, options, geoJsonFeature);
7. GeoJSON FeatureCollection (with Turf.js)
For FeatureCollection with multiple features, use Turf.js to merge them first:
// Load GeoJSON from URL fetch('https://example.com/boundary.geojson') .then(response => response.json()) .then(geoJson => { let mergedPolygon = geoJson.features[0]; // Merge all features using Turf.js for (let i = 1; i < geoJson.features.length; i++) { mergedPolygon = turf.union(mergedPolygon, geoJson.features[i]); } // Extract coordinates and convert [lng, lat] to [lat, lng] const geometry = mergedPolygon.geometry; const coordinates = geometry.coordinates[0].map(coord => [coord[1], coord[0]]); // Create clipped WMS layer var wmsLayer = L.tileLayer.wms.clipped(url, options, coordinates); wmsLayer.addTo(map); });
React/Next.js Usage
Basic React Example
import { useEffect, useRef } from "react"; import L from "leaflet"; import "leaflet-wms-crop"; export default function MapComponent() { const mapDivRef = useRef(null); const mapRef = useRef(null); useEffect(() => { if (!mapDivRef.current || mapRef.current) return; // Create map const map = L.map(mapDivRef.current).setView([20, 77], 5); mapRef.current = map; // Base layer L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", }).addTo(map); // India boundary (simple bbox polygon) const boundary = [ [35.6, 68.0], // NW [35.6, 97.5], // NE [6.5, 97.5], // SE [6.5, 68.0], // SW [35.6, 68.0], // close ]; // Create clipped WMS layer const wmsLayer = L.tileLayer.wms.clipped( "https://services.terrascope.be/wms/v2", { layers: "WORLDCOVER_2021_MAP", format: "image/png", transparent: true, opacity: 1, }, boundary ); wmsLayer.addTo(map); return () => { map.remove(); mapRef.current = null; }; }, []); return ( <div style={{ height: "100vh", width: "100vw" }}> <div ref={mapDivRef} style={{ height: "100%", width: "100%" }} /> </div> ); }
Next.js Configuration
If using Next.js, you may need to configure webpack to handle the plugin:
// next.config.js module.exports = { webpack: (config) => { config.resolve.alias.canvas = false; return config; } };
Requirements
- Leaflet.js v1.0.0 or higher
- Turf.js (required for merging multiple features in GeoJSON FeatureCollection)
Live Demo
Check out the live demo with a complete working example:
The demo:
- Loads a GeoJSON boundary from URL
- Merges multiple features using Turf.js
- Clips WMS layer to the boundary
- Displays boundary outline
Links
- npm Package: https://www.npmjs.com/package/leaflet-wms-crop
- GitHub Repository: https://github.com/amanchry/leaflet-wms-crop
- Live Demo: https://amanchry.github.io/leaflet-wms-crop/
- Issues: https://github.com/amanchry/leaflet-wms-crop/issues
License
MIT License - see LICENSE file for details.
Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
Acknowledgments
- Built for Leaflet.js
- Uses Turf.js for GeoJSON operations
- Inspired by the need for client-side WMS clipping functionality