Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-leaflet@5.0.x

docs

index.md
tile.json

tessl/npm-react-leaflet

tessl install tessl/npm-react-leaflet@5.0.3

React components for Leaflet maps

media-overlays.mddocs/reference/

Media Overlays

Components for displaying images, SVG, and video content overlaid on specific geographic bounds.

Related Documentation

  • Tile Layers - Base map tiles
  • Vector Shapes - Alternative visualization methods
  • Layer Groups - Grouping overlays
  • Map Container - Pane and z-index management

Capabilities

ImageOverlay Component

Displays an image over specific geographic bounds.

/**
 * Component for overlaying images on the map
 * @param props - Image overlay properties
 */
const ImageOverlay: FunctionComponent<ImageOverlayProps>;

interface ImageOverlayProps extends MediaOverlayProps {
  /** Image URL (required) */
  url: string;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, ImageOverlay } from "react-leaflet";

function Map() {
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <MapContainer center={[51.495, -0.07]} zoom={15}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <ImageOverlay
        url="https://example.com/overlay-image.png"
        bounds={bounds}
        opacity={0.7}
      />
    </MapContainer>
  );
}

With Popup:

<ImageOverlay
  url="https://example.com/map-overlay.png"
  bounds={[[51.49, -0.08], [51.5, -0.06]]}
  opacity={0.8}
>
  <Popup>This is an image overlay</Popup>
</ImageOverlay>

SVGOverlay Component

Displays SVG content over specific geographic bounds.

/**
 * Component for overlaying SVG on the map
 * @param props - SVG overlay properties
 */
const SVGOverlay: FunctionComponent<SVGOverlayProps>;

interface SVGOverlayProps extends MediaOverlayProps {
  /** SVG attributes */
  attributes?: Record<string, string>;
  /** SVG content as children */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, SVGOverlay } from "react-leaflet";

function Map() {
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <MapContainer center={[51.495, -0.07]} zoom={15}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <SVGOverlay bounds={bounds}>
        <rect x="0" y="0" width="100%" height="100%" fill="blue" opacity="0.3" />
        <circle cx="50%" cy="50%" r="40%" fill="red" opacity="0.5" />
        <text x="50%" y="50%" textAnchor="middle" fill="white">
          Overlay Text
        </text>
      </SVGOverlay>
    </MapContainer>
  );
}

With Custom Attributes:

<SVGOverlay
  bounds={bounds}
  attributes={{ preserveAspectRatio: "none" }}
>
  <polygon points="0,0 100,0 100,100 0,100" fill="green" opacity="0.4" />
</SVGOverlay>

VideoOverlay Component

Displays video content over specific geographic bounds.

/**
 * Component for overlaying video on the map
 * @param props - Video overlay properties
 */
const VideoOverlay: FunctionComponent<VideoOverlayProps>;

interface VideoOverlayProps extends MediaOverlayProps, VideoOverlayOptions {
  /** Video source URL(s) or HTMLVideoElement (required) */
  url: string | string[] | HTMLVideoElement;
  /** Whether to play the video */
  play?: boolean;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, VideoOverlay } from "react-leaflet";
import { useState } from "react";

function Map() {
  const [playing, setPlaying] = useState(false);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <>
      <button onClick={() => setPlaying(!playing)}>
        {playing ? "Pause" : "Play"}
      </button>
      <MapContainer center={[51.495, -0.07]} zoom={15}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        <VideoOverlay
          url="https://example.com/video.mp4"
          bounds={bounds}
          play={playing}
        />
      </MapContainer>
    </>
  );
}

Multiple Video Sources:

<VideoOverlay
  url={[
    "https://example.com/video.mp4",
    "https://example.com/video.webm",
    "https://example.com/video.ogg",
  ]}
  bounds={bounds}
  play={true}
/>

Types

MediaOverlayProps

interface MediaOverlayProps extends ImageOverlayOptions, InteractiveLayerProps {
  /** Geographic bounds of the overlay (required) */
  bounds: LatLngBoundsExpression;
}

interface InteractiveLayerProps extends LayerProps, InteractiveLayerOptions {
  interactive?: boolean;
  bubblingMouseEvents?: boolean;
}

ImageOverlayOptions

// From Leaflet
interface ImageOverlayOptions extends InteractiveLayerOptions {
  /** Opacity of the overlay (0.0 - 1.0) */
  opacity?: number;
  /** Alt text for the image */
  alt?: string;
  /** Whether image should be clipped to map bounds */
  interactive?: boolean;
  /** Crossorigin attribute for the image */
  crossOrigin?: boolean | string;
  /** Error image URL shown when image fails to load */
  errorOverlayUrl?: string;
  /** Z-index of the overlay */
  zIndex?: number;
  /** CSS class name */
  className?: string;
}

VideoOverlayOptions

// From Leaflet
interface VideoOverlayOptions extends ImageOverlayOptions {
  /** Whether video should autoplay */
  autoplay?: boolean;
  /** Whether video should loop */
  loop?: boolean;
  /** Whether to keep aspect ratio */
  keepAspectRatio?: boolean;
  /** Whether video should be muted */
  muted?: boolean;
  /** Whether to show video controls */
  playsInline?: boolean;
}

LatLngBoundsExpression

// From Leaflet
type LatLngBoundsExpression =
  | LatLngBounds
  | [LatLngExpression, LatLngExpression];

type LatLngExpression =
  | LatLng
  | [number, number]
  | [number, number, number]
  | { lat: number; lng: number }
  | { lat: number; lng: number; alt?: number };

// Bounds are specified as [[southWest], [northEast]]
// Example: [[51.49, -0.12], [51.52, -0.06]]

Important Notes

  1. Bounds Specification: All overlays require geographic bounds defining where the content is positioned:

    const bounds = [[southLat, westLng], [northLat, eastLng]];
  2. Opacity Control: Adjust overlay transparency:

    <ImageOverlay url="..." bounds={bounds} opacity={0.5} />
  3. Z-Index: Control layering with zIndex:

    <ImageOverlay url="base.png" bounds={bounds} zIndex={1} />
    <ImageOverlay url="overlay.png" bounds={bounds} zIndex={2} />
  4. Video Playback: Control video with the play prop:

    <VideoOverlay url="video.mp4" bounds={bounds} play={isPlaying} />
  5. Video Options: Configure video behavior:

    <VideoOverlay
      url="video.mp4"
      bounds={bounds}
      autoplay={false}
      loop={true}
      muted={false}
    />
  6. SVG Content: SVG children should be valid SVG elements:

    <SVGOverlay bounds={bounds}>
      <circle cx="50%" cy="50%" r="50%" fill="blue" />
    </SVGOverlay>
  7. Interactive Overlays: Make overlays respond to mouse events:

    <ImageOverlay
      url="..."
      bounds={bounds}
      interactive={true}
      eventHandlers={{
        click: () => console.log("Overlay clicked"),
      }}
    />
  8. CORS Considerations: For cross-origin images, set crossOrigin:

    <ImageOverlay url="..." bounds={bounds} crossOrigin={true} />
  9. Dynamic Bounds: Update overlay position by changing bounds:

    <ImageOverlay url="..." bounds={currentBounds} />
  10. Error Handling: Provide fallback image for failed loads:

    <ImageOverlay
      url="primary.png"
      bounds={bounds}
      errorOverlayUrl="fallback.png"
    />

Examples

Image Overlay with Opacity Control

function ImageOverlayWithControls() {
  const [opacity, setOpacity] = useState(0.7);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <>
      <input
        type="range"
        min="0"
        max="1"
        step="0.1"
        value={opacity}
        onChange={(e) => setOpacity(parseFloat(e.target.value))}
      />
      <MapContainer center={[51.495, -0.07]} zoom={15}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        <ImageOverlay
          url="https://example.com/overlay.png"
          bounds={bounds}
          opacity={opacity}
        />
      </MapContainer>
    </>
  );
}

Dynamic SVG Overlay

function DynamicSVGOverlay({ temperature }) {
  const bounds = [[51.49, -0.08], [51.5, -0.06]];
  const color = temperature > 30 ? "red" : temperature > 20 ? "orange" : "blue";

  return (
    <SVGOverlay bounds={bounds}>
      <rect x="0" y="0" width="100%" height="100%" fill={color} opacity="0.3" />
      <text x="50%" y="50%" textAnchor="middle" fill="white" fontSize="24">
        {temperature}°C
      </text>
    </SVGOverlay>
  );
}

Video Overlay with Controls

function VideoOverlayWithControls() {
  const [playing, setPlaying] = useState(false);
  const [loop, setLoop] = useState(true);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <>
      <div>
        <button onClick={() => setPlaying(!playing)}>
          {playing ? "Pause" : "Play"}
        </button>
        <label>
          <input
            type="checkbox"
            checked={loop}
            onChange={(e) => setLoop(e.target.checked)}
          />
          Loop
        </label>
      </div>
      <MapContainer center={[51.495, -0.07]} zoom={15}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        <VideoOverlay
          url="https://example.com/video.mp4"
          bounds={bounds}
          play={playing}
          loop={loop}
          muted={true}
        >
          <Popup>
            <button onClick={() => setPlaying(!playing)}>
              Toggle Playback
            </button>
          </Popup>
        </VideoOverlay>
      </MapContainer>
    </>
  );
}

Historical Map Overlay

function HistoricalMapOverlay() {
  const [showHistorical, setShowHistorical] = useState(false);
  const [opacity, setOpacity] = useState(0.7);
  const bounds = [[51.49, -0.12], [51.52, -0.06]];

  return (
    <>
      <button onClick={() => setShowHistorical(!showHistorical)}>
        Toggle Historical Map
      </button>
      <MapContainer center={[51.505, -0.09]} zoom={14}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        {showHistorical && (
          <ImageOverlay
            url="https://example.com/historical-map.png"
            bounds={bounds}
            opacity={opacity}
          >
            <Popup>Historical map from 1900</Popup>
          </ImageOverlay>
        )}
      </MapContainer>
    </>
  );
}

Interactive SVG Overlay

function InteractiveSVGOverlay() {
  const [clicked, setClicked] = useState(false);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <SVGOverlay
      bounds={bounds}
      interactive={true}
      eventHandlers={{
        click: () => setClicked(!clicked),
      }}
    >
      <rect
        x="0"
        y="0"
        width="100%"
        height="100%"
        fill={clicked ? "green" : "red"}
        opacity="0.4"
      />
      <text x="50%" y="50%" textAnchor="middle" fill="white">
        {clicked ? "Clicked!" : "Click me"}
      </text>
    </SVGOverlay>
  );
}

Edge Cases and Advanced Scenarios

Overlay with Dynamic Bounds

function DynamicBoundsOverlay({ center, size }) {
  const bounds = useMemo(() => {
    const offset = size / 2;
    return [
      [center[0] - offset, center[1] - offset],
      [center[0] + offset, center[1] + offset],
    ];
  }, [center, size]);

  return (
    <ImageOverlay
      url="https://example.com/overlay.png"
      bounds={bounds}
      opacity={0.7}
    />
  );
}

Rotating Image Overlay

function RotatingImageOverlay({ bounds, angle }) {
  const overlayRef = useRef(null);

  useEffect(() => {
    if (overlayRef.current) {
      const element = overlayRef.current.getElement();
      if (element) {
        element.style.transform = `rotate(${angle}deg)`;
        element.style.transformOrigin = "center";
      }
    }
  }, [angle]);

  return (
    <ImageOverlay
      ref={overlayRef}
      url="https://example.com/overlay.png"
      bounds={bounds}
    />
  );
}

Video Overlay with Playback Control

function ControlledVideoOverlay() {
  const [playing, setPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const videoRef = useRef(null);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  useEffect(() => {
    if (videoRef.current) {
      const video = videoRef.current.getElement();
      if (video) {
        video.addEventListener('timeupdate', () => {
          setCurrentTime(video.currentTime);
        });
      }
    }
  }, []);

  return (
    <>
      <div className="video-controls">
        <button onClick={() => setPlaying(!playing)}>
          {playing ? "Pause" : "Play"}
        </button>
        <span>Time: {currentTime.toFixed(1)}s</span>
      </div>
      <VideoOverlay
        ref={videoRef}
        url="https://example.com/video.mp4"
        bounds={bounds}
        play={playing}
        loop={true}
        muted={true}
      />
    </>
  );
}

SVG Overlay with Data Visualization

function DataVisualizationOverlay({ data, bounds }) {
  const maxValue = Math.max(...data.map(d => d.value));

  return (
    <SVGOverlay bounds={bounds}>
      <defs>
        <linearGradient id="heatGradient" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" style={{ stopColor: "blue", stopOpacity: 1 }} />
          <stop offset="50%" style={{ stopColor: "yellow", stopOpacity: 1 }} />
          <stop offset="100%" style={{ stopColor: "red", stopOpacity: 1 }} />
        </linearGradient>
      </defs>
      {data.map((point, idx) => (
        <circle
          key={idx}
          cx={`${(point.x / 100) * 100}%`}
          cy={`${(point.y / 100) * 100}%`}
          r={`${(point.value / maxValue) * 10}%`}
          fill="url(#heatGradient)"
          opacity="0.6"
        />
      ))}
    </SVGOverlay>
  );
}

Multi-Source Image Overlay with Fallback

function RobustImageOverlay({ urls, bounds }) {
  const [currentUrlIndex, setCurrentUrlIndex] = useState(0);
  const [error, setError] = useState(false);

  const handleError = useCallback(() => {
    if (currentUrlIndex < urls.length - 1) {
      setCurrentUrlIndex(prev => prev + 1);
      setError(false);
    } else {
      setError(true);
    }
  }, [currentUrlIndex, urls.length]);

  if (error) {
    return <div>Failed to load overlay image</div>;
  }

  return (
    <ImageOverlay
      url={urls[currentUrlIndex]}
      bounds={bounds}
      eventHandlers={{
        error: handleError,
      }}
    />
  );
}

Animated SVG Overlay

function AnimatedSVGOverlay({ bounds }) {
  const [frame, setFrame] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setFrame(prev => (prev + 1) % 360);
    }, 50);

    return () => clearInterval(interval);
  }, []);

  return (
    <SVGOverlay bounds={bounds}>
      <circle
        cx="50%"
        cy="50%"
        r="20%"
        fill="blue"
        opacity="0.5"
        transform={`rotate(${frame} 50 50)`}
      />
      <line
        x1="50%"
        y1="50%"
        x2={`${50 + 30 * Math.cos(frame * Math.PI / 180)}%`}
        y2={`${50 + 30 * Math.sin(frame * Math.PI / 180)}%`}
        stroke="red"
        strokeWidth="2"
      />
    </SVGOverlay>
  );
}

Conditional Overlay Based on Zoom

function ZoomDependentOverlay() {
  const [zoom, setZoom] = useState(13);
  const bounds = [[51.49, -0.08], [51.5, -0.06]];

  return (
    <MapContainer center={[51.495, -0.07]} zoom={zoom}>
      <MapEvents onZoomChange={setZoom} />
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      
      {zoom > 14 && (
        <ImageOverlay
          url="https://example.com/detailed-overlay.png"
          bounds={bounds}
          opacity={0.7}
        />
      )}
      
      {zoom <= 14 && (
        <ImageOverlay
          url="https://example.com/simple-overlay.png"
          bounds={bounds}
          opacity={0.5}
        />
      )}
    </MapContainer>
  );
}

Troubleshooting

Overlay Not Displaying

Solutions:

  1. Verify bounds are valid and in correct order
  2. Check image URL is accessible
  3. Verify opacity is not 0
  4. Check z-index settings
  5. Ensure overlay is inside MapContainer

Overlay Distorted or Misaligned

Solutions:

  1. Verify bounds match actual image dimensions
  2. Check coordinate order (should be [[south, west], [north, east]])
  3. Ensure CRS matches map CRS
  4. Check for CSS transforms interfering

Video Not Playing

Solutions:

  1. Verify video URL is accessible
  2. Check video format is supported by browser
  3. Ensure play prop is set to true
  4. Check autoplay and muted settings (autoplay requires muted)
  5. Verify video codec compatibility

SVG Not Rendering

Solutions:

  1. Verify SVG children are valid SVG elements
  2. Check for syntax errors in SVG
  3. Ensure viewBox or dimensions are set correctly
  4. Check for CSS conflicts

Performance Issues with Overlays

Solutions:

  1. Optimize image sizes
  2. Use appropriate image formats (WebP for photos, PNG for graphics)
  3. Implement lazy loading
  4. Use lower resolution overlays at low zoom levels
  5. Remove overlays when not visible

CORS Errors

Solutions:

  1. Set crossOrigin={true} on overlay
  2. Configure server to send CORS headers
  3. Use proxy server for external resources
  4. Host overlays on same domain

Best Practices

  1. Optimize Image Sizes: Use appropriately sized images for zoom levels
  2. Use Correct Formats: WebP for photos, PNG for graphics with transparency
  3. Set Proper Bounds: Ensure bounds match actual geographic area
  4. Handle Errors: Implement error handling for failed loads
  5. Consider Performance: Lazy load overlays and remove when not needed
  6. Test Cross-Browser: Verify video formats work across browsers
  7. Use Muted for Autoplay: Modern browsers require muted for video autoplay
  8. Implement Fallbacks: Provide alternative content if overlay fails
  9. Respect Opacity: Use appropriate opacity for readability
  10. Clean Up Resources: Remove overlays when component unmounts

Testing Media Overlays

Unit Testing Image Overlays

import { render } from '@testing-library/react';
import { MapContainer, ImageOverlay } from 'react-leaflet';

test('renders image overlay', () => {
  const bounds = [[51.49, -0.08], [51.5, -0.06]];
  
  const { container } = render(
    <MapContainer center={[51.495, -0.07]} zoom={15}>
      <ImageOverlay
        url="https://example.com/image.png"
        bounds={bounds}
        opacity={0.7}
      />
    </MapContainer>
  );
  
  expect(container).toBeInTheDocument();
});

Testing Video Overlay Controls

test('video overlay responds to play prop', () => {
  const bounds = [[51.49, -0.08], [51.5, -0.06]];
  const { rerender } = render(
    <MapContainer center={[51.495, -0.07]} zoom={15}>
      <VideoOverlay
        url="https://example.com/video.mp4"
        bounds={bounds}
        play={false}
      />
    </MapContainer>
  );
  
  // Test play state change
  rerender(
    <MapContainer center={[51.495, -0.07]} zoom={15}>
      <VideoOverlay
        url="https://example.com/video.mp4"
        bounds={bounds}
        play={true}
      />
    </MapContainer>
  );
});

Accessibility for Media Overlays

Image Overlay with Alt Text

<ImageOverlay
  url="https://example.com/map-overlay.png"
  bounds={bounds}
  alt="Historical map overlay from 1900"
>
  <Popup>
    <div role="region" aria-label="Historical map information">
      <h3>Historical Map</h3>
      <p>This overlay shows the area as it appeared in 1900</p>
    </div>
  </Popup>
</ImageOverlay>

Video Overlay with Controls

<VideoOverlay
  url="https://example.com/video.mp4"
  bounds={bounds}
  play={playing}
  muted={false}
  // Ensure video is accessible
>
  <Popup>
    <div role="region">
      <h3>Video Content</h3>
      <button
        onClick={() => setPlaying(!playing)}
        aria-label={playing ? "Pause video" : "Play video"}
      >
        {playing ? "Pause" : "Play"}
      </button>
    </div>
  </Popup>
</VideoOverlay>