40

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 !

arnaudambro
  • 2,403
  • 3
  • 25
  • 51

7 Answers7

55

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 };
arnaudambro
  • 2,403
  • 3
  • 25
  • 51
10

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],     
});`
eliday
  • 156
  • 1
  • 3
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>
David
  • 944
  • 1
  • 13
  • 20
5

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;
Samira
  • 2,361
  • 1
  • 12
  • 21
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);
Ali Bayatpour
  • 348
  • 3
  • 8
  • 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. – Armino Popp Sep 08 '21 at 07:26
3

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.

Deividson Calixto
  • 350
  • 1
  • 4
  • 11
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>
  • 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](/help/how-to-answer). – Community May 08 '22 at 20:35
  • This is a great anwser. +1. Worked flawlessly and provides far more flexibility over the marked anwser – ThatBrianDude May 08 '23 at 07:53