custom marker icon with react-leaflet
Asked Answered
Y

8

45

I tried everything I found on the web, Stackoverflow and Github, and I still can't make it.

I want to make a custom marker with a custom icon, but with my code below I always got an error : 'TypeError: options.icon.createIcon is not a function'

Here is my code (no error on the paths to folders, everything is in src/js or src/img)

Icon.js

import L from 'leaflet';

const iconPerson = L.Icon.extend({
  options: {
    iconUrl: require('../img/marker-pin-person.svg'),
    iconRetinaUrl: require('../img/marker-pin-person.svg'),
    iconAnchor: null,
    popupAnchor: null,
    shadowUrl: null,
    shadowSize: null,
    shadowAnchor: null,
    iconSize: new L.Point(60, 75),
    className: 'leaflet-div-icon'
  }
});

export { iconPerson };

MarkerPinPerson

import React from 'react';
import { Marker } from 'react-leaflet';
import {  iconPerson  } from './Icons';


export default class MarkerPinPerson extends React.Component {

  render() {

    return (
      <Marker
        position={this.props.markerPosition}
        icon={ iconPerson }
        >
      </Marker>
      );
  }
}

Really looking for your help !

Yalonda answered 8/12, 2017 at 23:55 Comment(0)
Y
59

I finally found the correct code for the Icon.js file :

import L from 'leaflet';

const iconPerson = new L.Icon({
    iconUrl: require('../img/marker-pin-person.svg'),
    iconRetinaUrl: require('../img/marker-pin-person.svg'),
    iconAnchor: null,
    popupAnchor: null,
    shadowUrl: null,
    shadowSize: null,
    shadowAnchor: null,
    iconSize: new L.Point(60, 75),
    className: 'leaflet-div-icon'
});

export { iconPerson };
Yalonda answered 9/12, 2017 at 17:47 Comment(2)
It wasn't rendering with out the require for me, that's what I needed.Paleoclimatology
I would not use the leaflet-div-icon class. It will render a white background with a border.Caesium
J
11

I was also experiencing the same problem. Here is my working solution:

`import L from 'leaflet';
import marker from '../assets/placer.svg';
const myIcon = new L.Icon({
    iconUrl: marker,
    iconRetinaUrl: marker,
    popupAnchor:  [-0, -0],
    iconSize: [32,45],     
});`
Jourdain answered 16/1, 2021 at 12:6 Comment(0)
D
7

I was brought here while trying to figure out how to render a custom icon server side (using react-leaflet-universal). I thought I'd post this in case anyone in the future finds themselves here for the same reason. Just as in the case of react-leaflet-markercluster, I was able to get this working by requiring leaflet inside the return function like:

<Map center={this.props.center}
             zoom={zoom}
             className={leafletMapContainerClassName}
             scrollWheelZoom={false}
             maxZoom={18}
             preferCanvas={false}
        >
            {() => {
                const MarkerClusterGroup = require('react-leaflet-markercluster').default;
                const L = require('leaflet');

                const myIcon = L.icon({
                    iconUrl: require('../assets/marker.svg'),
                    iconSize: [64,64],
                    iconAnchor: [32, 64],
                    popupAnchor: null,
                    shadowUrl: null,
                    shadowSize: null,
                    shadowAnchor: null
                });

                return (
                    <React.Fragment>
                        <TileLayer
                            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                            attribution=''
                            setParams={true}
                        />
                        <MarkerClusterGroup>
                            {coordArray.map(item => {
                                return (
                                    <Marker icon={myIcon} key={item.toString()} position={[item.lat, item.lng]}>
                                        {item.title && <Popup>
                                            <span>{item.title}</span>
                                        </Popup>}
                                    </Marker>
                                )
                            })}
                        </MarkerClusterGroup>
                    </React.Fragment>
                );
            }}
        </Map>
Dubbin answered 30/10, 2018 at 20:7 Comment(1)
How do I use a react-icon this way?Lizettelizotte
H
7

in react-leaflet v3 this worked very well for me : first I made an SVG icon then I encoded it with encodeURIComponent, finally I passed it to Marker

    import React, {useEffect} from 'react';
    import {MapContainer, Marker, Popup, TileLayer} from 'react-leaflet';
    import Main from "../publicComponents/main";
    import L from "leaflet";
    
    const position = [35.72428729739558, 51.447000503540046]
    const Red_MARKER = `data:image/svg+xml;utf8,${encodeURIComponent(`<?xml version="1.0" encoding="iso-8859-1"?>
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36.059" height="36.059" viewBox="0 0 36.059 36.059" style="transform:rotate(${0}deg)">
      <defs>
        <filter id="Path_10080" x="0" y="0" width="36.059" height="36.059" filterUnits="userSpaceOnUse">
          <feOffset input="SourceAlpha"/>
          <feGaussianBlur stdDeviation="2.5" result="blur"/>
          <feFlood flood-opacity="0.161"/>
          <feComposite operator="in" in2="blur"/>
          <feComposite in="SourceGraphic"/>
        </filter>
      </defs>
      <g id="Group_8038" data-name="Group 8038" transform="translate(5719.5 1106.5)">
        <rect id="Rectangle_2670" data-name="Rectangle 2670" width="21" height="21" transform="translate(-5712 -1099)" fill="none"/>
        <g transform="matrix(1, 0, 0, 1, -5719.5, -1106.5)" filter="url(#Path_10080)">
          <path id="Path_10080-2" data-name="Path 10080" d="M15.4,12.766a6.414,6.414,0,0,0,1.781-5.634l-.446-2.55-2.55-.446A6.414,6.414,0,0,0,8.553,5.916L6.746,7.723c.234-.232-.845.866-.626.626l-2.96,2.96a2.644,2.644,0,0,0,0,3.735l3.114,3.114a2.644,2.644,0,0,0,3.735,0l2.96-2.96Z" transform="translate(19.2 2.96) rotate(45)" fill="${"red"}"/>
        </g>
      </g>
    </svg>
    `)}`;

    const BoatIcon = L.icon({
        iconUrl: Red_MARKER,
        iconSize: [40, 40],
        iconAnchor: [12, 12],
        popupAnchor: [0, 0],
    });
    

    const Index = () => {
        return (<Main>
                <MapContainer center={position} zoom={13}
                              style={{width: "100%", height: "calc(100vh - 80px)", overflow: "hidden"}}>
                    <TileLayer
                        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                    />
                    <Marker position={position} icon={BoatIcon}>
                        <Popup>
                            A pretty CSS3 popup. <br/> Easily customizable.
                        </Popup>
                    </Marker>
                </MapContainer></Main>
        );
    };
    
    export default Index;
Helmuth answered 13/2, 2022 at 6:36 Comment(0)
M
4

You don't need to use require. instead of giving iconUrl = "../assets/name" you only need to import your png or svg then you can give the source to your iconUrl. look at the example below:

// first import your image or svg

import heart from "../../images/other/love.svg";

// give the source to your icon

let loveIcon = L.icon({
  iconUrl: heart,
  iconRetinaUrl: heart,
  iconAnchor: [5, 55],
  popupAnchor: [10, -44],
  iconSize: [25, 55],
});

// simply add it to your map

L.marker([28, 50], {
       icon: loveIcon,
     }).addTo(map);
Molton answered 20/11, 2020 at 9:46 Comment(1)
For me it worked on next.js v11.1.0 by using iconUrl: heart.src otherwise it would put an object in img src.Levin
P
4

You can organize in diferents files, and change the Svg Compoment properties, like colors, height width and more...

  1. We gonna create a function React Component, pay attention that we have to pass a spreat in {...props} to svg file, that way we can change it in execution time:

import React from "react";
export default function PinMoto(props) {
return (
    //its a SVG example, it`s by half, or corrupted, to not occupy large caracter space here, use your SVG file here...
    <svg width="37" height="45" viewBox="0 0 26 34" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
        <path d="M13 0C6.0974 0 0.481453 5.83195 0.481453 13C0.481453 15.1519 0.999529 17.2855 1.98441 19.1779L12.3154 33.5811C12.4529 33.8397 12.715 34 13 34C13.285 34 13.547 33.8397 13.6846 33.5811L24.0194 19.1715C25.0005 17.2855 25.5185 15.1518 25.5185 12.9999C25.5185 5.83195 19.9026 0 13 0Z" fill="#DC462D" {...props}/>
        <g clip-Path="url(#clip0)">
        <path d="M19.0012 12.7109C17.3488 12.7109 16.0023 14.1322 16.0023 15.8763C16.0023 17.6204 17.3453 19.0417 19.0012 19.0417C20.6535 19.0417 22 17.6242 22 15.8763C22 14.1285 20.6535 12.7109 19.0012 12.7109ZM19.0012 18.2513C17.7602 18.2513 16.7512 17.1863 16.7512 15.8763C16.7512 14.5663 17.7602 13.5013 19.0012 13.5013C20.2422 13.5013 21.2512 14.5663 21.2512 15.8763C21.2512 17.1863 20.2422 18.2513 19.0012 18.2513Z" fill="white" />
            
        </g>
        <defs>
            <clippath id="clip0">
                <rect width="18" height="19" fill="white" transform="translate(4 4)" />
            </clippath>
        </defs>
    </svg>);
}
  1. Import this Component in a another file Utils.js, in this file we can find a function thats return a React svg Component modified in real-time:

import PinMoto from '../svg_pins/PinMoto'
import { ReactDOM } from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { divIcon } from 'leaflet'

export const getRequiredSVGPinByCategory = (category,  myStyle) => { 
    let pin
    switch (category) {              
        case 'motorcycle':
            pin = <PinMoto {...myStyle}/>
            break;                                 
        case 'truck':
            pin = <PinCaminhao {...myStyle}/>
            break;                                                                
        default:
            //pin = <PinPadrao {...myStyle}/>
            break;
        }
    const iconMarkup = renderToStaticMarkup(
       pin
    )
    const customMarketIcon = divIcon({
        html: iconMarkup
    })
    return customMarketIcon
}
  1. In the main file, that's contain a MapContainer, we can use in this way:

import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from 'leaflet';
import   { getRequiredSVGPinByCategory } from '../../utils/util'
    
//your jsx and codes...
    <MapContainer
        center={[-20.268589, -40.290479]}
        zoom={10}
        scrollWheelZoom={true}
        style={{height: 500, width: '100%'}}>
        <TileLayer
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
            <Marker position={[-20.268589, -40.290479]}
            icon={ getRequiredSVGPinByCategory('motorcycle', {fill: 'orange'} ) }>
            </Marker>
    </MapContainer>
//...

I hope this can usefull for you guys.

Pyrotechnic answered 2/10, 2021 at 19:25 Comment(0)
H
1

Your marker can look like this:

<Marker
  position={{
    lat: myLatitude,
    lng: myLongitude,
  }}
  icon={
    divIcon({
      html: renderToStaticMarkup(
        <CustomIcon type={myType} />
      ),
      iconSize: [0, 0], 
    })
  }
/>

And in your global.css:

.leaflet-div-icon {
  background: unset !important;
  border: unset !important;
}

The iconSize can be either the size of your CustomIcon or [0,0] and use negative margin top/left of half inside CustomIcon

Based on this answer.

Hew answered 7/9, 2023 at 2:16 Comment(0)
P
0

You can just use react-leaflet-marker npm package. It support dynamic react component as leaflet markers

npm i react-leaflet-marker --save

     <Marker
        position={[55.796391, 49.108891]}
        size={[80, 20]} // required for placement
        // you can use optional `placement`
        placement="center" // "top", "bottom"
    >
        <div style={{
            background: 'red',
            textAlign: 'center'
        }}>
            center react component
        </div>
    </Marker>
Problematic answered 5/5, 2022 at 10:9 Comment(2)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Sudd
This is a great anwser. +1. Worked flawlessly and provides far more flexibility over the marked anwserOrit

© 2022 - 2025 — McMap. All rights reserved.