---
title: "Weather custom popup | Weather SDK | Marker layout | JavaScript maps SDK"
description: "Create highly customizable popups for weather maps using MarkerLayout."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# Weather custom popup

Let’s create the custom non-colliding popups using the [Marker Layout](https://docs.maptiler.com/sdk-js/modules/marker-layout/) on top of MapTiler SDK. The example uses the custom Marker Layout because it allows complete freedom of customization, which we can’t do with the original MapLibre popups due to their limited design.

We gather the weather data (including wind, temperature and precipitation) displayed in the popup from various layers of the [MapTiler Weather JS module](https://docs.maptiler.com/sdk-js/modules/weather/).

For this demo, we used [Meteocons](https://bas.dev/work/meteocons), a weather icon pack designed by Bas Milius also available on [GitHub](https://github.com/basmilius/weather-icons).

### NPM module setup

```bash
npm install --save @maptiler/sdk @maptiler/marker-layout @maptiler/weather
```

#### main.js

```js
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import { MarkerLayout } from '@maptiler/marker-layout';
import { WindLayer, PrecipitationLayer, TemperatureLayer, RadarLayer, ColorRamp } from '@maptiler/weather';

const appContainer = document.getElementById('map');

  config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';

   // Creating a map
   const map = new Map({
    container: appContainer,
    style: MapStyle.STREETS,
    zoom: 4.5,
    center: [2, 40],
    hash: true,
    projectionControl: true
  });

  // Creating the div that will contain all the markers
  const markerContainer = document.createElement("div");
  appContainer.appendChild(markerContainer);

  (async () => {
    await map.onReadyAsync();

    const markerManager = new MarkerLayout(map, {
      layers: ["City labels", "Place labels", "Town labels"],
      markerSize: [140, 80],
      markerAnchor: "top",
      offset: [0, -8], // so that the tip of the marker bottom pin lands on the city dot
      sortingProperty: "rank",
      filter: ((feature) => {
        if (["City labels", "Town labels"].includes(feature.layer.id)) {
          return true;
        } else {
          return ["village"].includes(feature.properties.class)
        }
      }),
    });

    // Creating the weather layers...
    // Temperature will be used as the main overlay
    const temperatureLayer = new TemperatureLayer({
      opacity: 0.7,
    });

    // Radar will be using the cloud color ramp and used as a cloud overlay
    const radarLayer = new RadarLayer({
      colorramp: ColorRamp.builtin.RADAR_CLOUD,
    });

    // From the wind layer, we only display the particles (the background is using the NULL color ramp, which is transparent).
    // The slower particles are transparent, the fastest are opaque white
    const windLayer = new WindLayer({
      colorramp: ColorRamp.builtin.NULL,
      color: [255, 255, 255, 0],
      fastColor: [255, 255, 255, 100],
    });

    // The precispitation layer is created but actually not displayed.
    // It will only be used for picking precipitation metrics at the locations of the markers
    const precipitationLayer = new PrecipitationLayer({
      colorramp: ColorRamp.builtin.NULL,
    });

    // Setting the water layer partially transparent to increase the visual separation between land and water
    map.setPaintProperty("Water", "fill-color", "rgba(0, 0, 0, 0.7)");
    map.addLayer(temperatureLayer, "Place labels");
    map.addLayer(windLayer);
    map.addLayer(radarLayer);
    map.addLayer(precipitationLayer);

    // Waiting for weather data readyness
    await temperatureLayer.onSourceReadyAsync();
    await radarLayer.onSourceReadyAsync();
    await windLayer.onSourceReadyAsync();
    await precipitationLayer.onSourceReadyAsync();

    // This object contains the marker DIV so that they can be updated rather than fully recreated every time
    const markerLogicContainer = {};

    let markerStatus = null;

    // This function will be used as the callback for some map events
    const updateMarkers = () => {
      markerStatus = markerManager.update();

      if (!markerStatus) return;

      // Remove the div that corresponds to removed markers
      markerStatus.removed.forEach((abstractMarker) => {
        const markerDiv = markerLogicContainer[abstractMarker.id];
        delete markerLogicContainer[abstractMarker.id];
        markerContainer.removeChild(markerDiv);
      });

      // Update the div that corresponds to updated markers
      markerStatus.updated.forEach((abstractMarker) => {
        const markerDiv = markerLogicContainer[abstractMarker.id];
        updateMarkerDiv(abstractMarker, markerDiv);
      });

      // Create the div that corresponds to the new markers
      markerStatus.new.forEach((abstractMarker) => {
        const markerDiv = makeMarker(abstractMarker,
          temperatureLayer,
          radarLayer,
          precipitationLayer,
          windLayer,
          new Date()
        );
        markerLogicContainer[abstractMarker.id] = markerDiv;
        markerContainer.appendChild(markerDiv);
      });
    }

    const softUpdateMarkers = () => {
      // A previous run of .update() yieding no result or not being ran at all
      // would stop the soft update
      if (!markerStatus) return;

      markerStatus.updated.forEach((abstractMarker) => {
        markerManager.softUpdateAbstractMarker(abstractMarker);
        const markerDiv = markerLogicContainer[abstractMarker.id];
        updateMarkerDiv(abstractMarker, markerDiv);
      })

      markerStatus.new.forEach((abstractMarker) => {
        markerManager.softUpdateAbstractMarker(abstractMarker);
        const markerDiv = markerLogicContainer[abstractMarker.id];
        updateMarkerDiv(abstractMarker, markerDiv);
      })
    }

    // While moving the map, this event is triggered many times per seconds
    // so we only perform a soft update (that could be debounced)
    map.on("move", softUpdateMarkers);

    // When done moving, we perform a full update
    map.on("moveend", updateMarkers)

    map.once("idle", () => {
      updateMarkers();
    });

  })()

function makeMarker(abstractMarker, 
  temperatureLayer, radarLayer,
  precipitationLayer, windLayer, date) {

  const marker = document.createElement("div");
  marker.classList.add("marker");
  marker.classList.add('fade-in-animation');
  marker.style.setProperty("width", `${abstractMarker.size[0]}px`);
  marker.style.setProperty("height", `${abstractMarker.size[1]}px`);
  marker.style.setProperty("transform", `translate(${abstractMarker.position[0]}px, ${abstractMarker.position[1]}px)`);

  const feature = abstractMarker.features[0];
  const lonLat = feature.geometry.coordinates;
  const temperatureData = temperatureLayer.pickAt(lonLat[0], lonLat[1]);
  const precipitationData = precipitationLayer.pickAt(lonLat[0], lonLat[1]);
  const windData = windLayer.pickAt(lonLat[0], lonLat[1]);
  const radarData = radarLayer.pickAt(lonLat[0], lonLat[1]);

  let mainWeatherIconURL = "./weather-icons/";
  const radarDBz = radarData?.value || -20;
  const precipMmH = precipitationData?.value || 0;
  const temperatureDeg = temperatureData?.value || 0;
  const windSpeed = windData?.speedKilometersPerHour || 0;
  const compassDirection = windData?.compassDirection || 0;
  const temperature = temperatureData?.value.toFixed(1);

  const sunPosition = SunCalc.getPosition(date, lonLat[1], lonLat[0]);

  if (sunPosition.altitude < 0) {
    mainWeatherIconURL += "night-";
  } else {
    mainWeatherIconURL += "day-";
  }

  if (radarDBz < 0) {

    if (precipMmH > 0.2) {
      mainWeatherIconURL += "cloudy-";
    } else {
      mainWeatherIconURL += "clear-";
    }
    
  } else if (radarDBz < 10) {
    mainWeatherIconURL += "cloudy-";
  } else if (radarDBz < 20) {
    mainWeatherIconURL += "overcast-";
  } else {
    mainWeatherIconURL += "extreme-";
  }

  if (precipMmH > 5) {
    mainWeatherIconURL += (temperatureDeg < -1 ? "snow" : "rain");
  } else if (precipMmH > 0.2) {
    mainWeatherIconURL += (temperatureDeg < -1 ? "snow" : "drizzle");
  } else {
    mainWeatherIconURL += "none";
  }

  mainWeatherIconURL += ".svg";
  
  let windDiv = "";
  if (windSpeed > 5) {
    windDiv = `
    <div
      class="markerWind"
    >
      <img class="markerWindIcon" src="./weather-icons/wind.svg"></img>${windSpeed.toFixed(0)} <span style="font-weight: 600; margin-left: 4px">${compassDirection}</span>
    </div>
    `
  }

  let precipDiv = "";
  if (precipMmH > 0.1) {
    precipDiv = `
    <div
      class="markerPrecipitation"
    >
      ${precipMmH.toFixed(1)} mm/h
    </div>
    `
  }

  marker.innerHTML = `
    <div class="markerPointy"></div>
    <div class="markerBody">

      <div class="markerTop">
        ${feature.properties["name:en"] || feature.properties["name"]}
      </div>

      <div class="markerBottom">
        <div class="markerBottomLeft">
          ${temperature ? `<div class="markerTemperature">${temperature}°</div>`: `<div class="markerTemperature"></div>`}
          ${windDiv}
        </div>

        <div class="markerBottomRight">
          <img class="markerMainWeatherIcon" src=${mainWeatherIconURL}></img>
          ${precipDiv}
        </div>
      </div>
    </div>
  `
  return marker;
}

function updateMarkerDiv(abstractMarker, marker) {
  marker.style.setProperty("width", `${abstractMarker.size[0]}px`);
  marker.style.setProperty("height", `${abstractMarker.size[1]}px`);
  marker.style.setProperty("transform", `translate(${abstractMarker.position[0]}px, ${abstractMarker.position[1]}px)`);
}
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Weather custom popup | NPM example</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div id="map"></div>
  <script type="module" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}

ul {
  list-style-type: none;
  padding: 0;
  margin: 5px;
}

#map {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
}

.marker {
  position: absolute;
  pointer-events: none;
  color: #000;
  line-height: 12px;
  box-shadow: #00000040 0px 2px 10px;
}

.markerPointy {
  width: 12px;
  height: 12px;
  background-color: #e6e6e6;
  position: absolute;
  transform: rotate(45deg) translate(-70%);
  bottom: -12px;
  left: 0;
  right: -12px;
  margin: auto;
  box-shadow: #00000040 0px 2px 10px;
}

.markerBody {
  position: absolute;
  background: #e6e6e6;
  width: 100%;
  height: 100%;
  top: 0;
  border-radius: 2px;
  /* padding: 3px;  */
}

.markerTop {
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
  width: 100%;
  height: 20px;
  line-height: 20px;
  text-align: center;
  background: #4d4d4d;
  color: white;
  font-size: 15px;
  font-weight: 400;
}

.markerBottom {
  border-radius: inherit;
  width: 100%;
  height: calc(100% - 20px);
  display: flex;
}

.markerBottomLeft {
  width: 50%;
  height: 100%;
  position: relative;
}

.markerBottomRight {
  width: 50%;
  height: 100%;
  position: relative;
}

.markerTemperature {
  font-size: 25px;
  text-align: center;
  line-height: 25px;
  position: absolute;
  top: 0;
  bottom: 12%;
  left: 0;
  right: 0;
  margin: auto;
  width: fit-content;
  height: fit-content;
}

.markerMainWeatherIcon {
  width: 80%;
  height: 80%;
  position: absolute;
  top: 0;
  bottom: 0px;
  left: 0;
  right: 0;
  margin: auto;
}

.markerPrecipitation {
  position: absolute;
  top: 0;
  width: 100%;
  height: fit-content;
  text-align: center;
  font-size: 10px;
  line-height: 10px;
  color: #4392dc;
  padding-top: 3px;
  font-weight: 600;
}

.markerWindIcon {
  height: 25px;
  margin-top: -9px;
  filter: brightness(70%);
  margin-right: -2px;
}

.markerWind {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 13px;
  text-align: center;
  font-size: 10px;
  line-height: 10px;
  color: #6f6f6f;
  font-weight: 400;
  display: flex;
  padding-left: 5px;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in-animation {
  animation: fadeIn 0.5s ease forwards;
}
```

### Basic JavaScript setup

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>MapTiler Marker Layout</title>
  <!-- Importing MapTiler SDK -->
  <script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
  <link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />

  <!-- Importing MapTiler Weather JS -->
  <script
    src="https://cdn.maptiler.com/maptiler-weather/v4.0.1/maptiler-weather.umd.min.js"></script>

  <!-- Importing MapTiler Marker Layout -->
  <script
    src="https://cdn.maptiler.com/maptiler-marker-layout/v2.0.1/maptiler-marker-layout.umd.min.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/suncalc@1.9.0/suncalc.min.js"></script>

  <style>
    body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
    }

    ul {
      list-style-type: none;
      padding: 0;
      margin: 5px;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    .marker {
      position: absolute;
      pointer-events: none;
      color: #000;
      line-height: 12px;
      box-shadow: #00000040 0px 2px 10px;
    }

    .markerPointy {
      width: 12px;
      height: 12px;
      background-color: #e6e6e6;
      position: absolute;
      transform: rotate(45deg) translate(-70%);
      bottom: -12px;
      left: 0;
      right: -12px;
      margin: auto;
      box-shadow: #00000040 0px 2px 10px;
    }

    .markerBody {
      position: absolute;
      background: #e6e6e6;
      width: 100%;
      height: 100%;
      top: 0;
      border-radius: 2px;
      /* padding: 3px;  */
    }

    .markerTop {
      border-top-left-radius: inherit;
      border-top-right-radius: inherit;
      width: 100%;
      height: 20px;
      line-height: 20px;
      text-align: center;
      background: #4d4d4d;
      color: white;
      font-size: 15px;
      font-weight: 400;
    }

    .markerBottom {
      border-radius: inherit;
      width: 100%;
      height: calc(100% - 20px);
      display: flex;
    }

    .markerBottomLeft {
      width: 50%;
      height: 100%;
      position: relative;
    }

    .markerBottomRight {
      width: 50%;
      height: 100%;
      position: relative;
    }

    .markerTemperature {
      font-size: 25px;
      text-align: center;
      line-height: 25px;
      position: absolute;
      top: 0;
      bottom: 12%;
      left: 0;
      right: 0;
      margin: auto;
      width: fit-content;
      height: fit-content;
    }

    .markerMainWeatherIcon {
      width: 80%;
      height: 80%;
      position: absolute;
      top: 0;
      bottom: 0px;
      left: 0;
      right: 0;
      margin: auto;
    }

    .markerPrecipitation {
      position: absolute;
      top: 0;
      width: 100%;
      height: fit-content;
      text-align: center;
      font-size: 10px;
      line-height: 10px;
      color: #4392dc;
      padding-top: 3px;
      font-weight: 600;
    }

    .markerWindIcon {
      height: 25px;
      margin-top: -9px;
      filter: brightness(70%);
      margin-right: -2px;
    }

    .markerWind {
      position: absolute;
      bottom: 0;
      width: 100%;
      height: 13px;
      text-align: center;
      font-size: 10px;
      line-height: 10px;
      color: #6f6f6f;
      font-weight: 400;
      display: flex;
      padding-left: 5px;
    }

    @keyframes fadeIn {
      from {
        opacity: 0;
      }

      to {
        opacity: 1;
      }
    }

    .fade-in-animation {
      animation: fadeIn 0.5s ease forwards;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <script>
    const appContainer = document.getElementById('map');

    maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';

    // Creating a map
    const map = new maptilersdk.Map({
      container: appContainer,
      style: maptilersdk.MapStyle.STREETS,
      zoom: 4.5,
      center: [2, 40],
      hash: true,
      projectionControl: true
    });

    // Creating the div that will contain all the markers
    const markerContainer = document.createElement("div");
    appContainer.appendChild(markerContainer);

    (async () => {
      await map.onReadyAsync();

      const markerManager = new maptilermarkerlayout.MarkerLayout(map, {
        layers: ["City labels", "Place labels", "Town labels"],
        markerSize: [140, 80],
        markerAnchor: "top",
        offset: [0, -8], // so that the tip of the marker bottom pin lands on the city dot
        sortingProperty: "rank",
        filter: ((feature) => {
          if (["City labels", "Town labels"].includes(feature.layer.id)) {
            return true;
          } else {
            return ["village"].includes(feature.properties.class)
          }
        }),
      });

      // Creating the weather layers...
      // Temperature will be used as the main overlay
      const temperatureLayer = new maptilerweather.TemperatureLayer({
        opacity: 0.7,
      });

      // Radar will be using the cloud color ramp and used as a cloud overlay
      const radarLayer = new maptilerweather.RadarLayer({
        colorramp: maptilerweather.ColorRamp.builtin.RADAR_CLOUD,
      });

      // From the wind layer, we only display the particles (the background is using the NULL color ramp, which is transparent).
      // The slower particles are transparent, the fastest are opaque white
      const windLayer = new maptilerweather.WindLayer({
        colorramp: maptilerweather.ColorRamp.builtin.NULL,
        color: [255, 255, 255, 0],
        fastColor: [255, 255, 255, 100],
      });

      // The precispitation layer is created but actually not displayed.
      // It will only be used for picking precipitation metrics at the locations of the markers
      const precipitationLayer = new maptilerweather.PrecipitationLayer({
        colorramp: maptilerweather.ColorRamp.builtin.NULL,
      });

      // Setting the water layer partially transparent to increase the visual separation between land and water
      map.setPaintProperty("Water", "fill-color", "rgba(0, 0, 0, 0.7)");
      map.addLayer(temperatureLayer, "Place labels");
      map.addLayer(windLayer);
      map.addLayer(radarLayer);
      map.addLayer(precipitationLayer);

      // Waiting for weather data readyness
      await temperatureLayer.onSourceReadyAsync();
      await radarLayer.onSourceReadyAsync();
      await windLayer.onSourceReadyAsync();
      await precipitationLayer.onSourceReadyAsync();

      // This object contains the marker DIV so that they can be updated rather than fully recreated every time
      const markerLogicContainer = {};

      let markerStatus = null;

      // This function will be used as the callback for some map events
      const updateMarkers = () => {
        markerStatus = markerManager.update();

        if (!markerStatus) return;

        // Remove the div that corresponds to removed markers
        markerStatus.removed.forEach((abstractMarker) => {
          const markerDiv = markerLogicContainer[abstractMarker.id];
          delete markerLogicContainer[abstractMarker.id];
          markerContainer.removeChild(markerDiv);
        });

        // Update the div that corresponds to updated markers
        markerStatus.updated.forEach((abstractMarker) => {
          const markerDiv = markerLogicContainer[abstractMarker.id];
          updateMarkerDiv(abstractMarker, markerDiv);
        });

        // Create the div that corresponds to the new markers
        markerStatus.new.forEach((abstractMarker) => {
          const markerDiv = makeMarker(abstractMarker,
            temperatureLayer,
            radarLayer,
            precipitationLayer,
            windLayer,
            new Date()
          );
          markerLogicContainer[abstractMarker.id] = markerDiv;
          markerContainer.appendChild(markerDiv);
        });
      }

      const softUpdateMarkers = () => {
        // A previous run of .update() yieding no result or not being ran at all
        // would stop the soft update
        if (!markerStatus) return;

        markerStatus.updated.forEach((abstractMarker) => {
          markerManager.softUpdateAbstractMarker(abstractMarker);
          const markerDiv = markerLogicContainer[abstractMarker.id];
          updateMarkerDiv(abstractMarker, markerDiv);
        })

        markerStatus.new.forEach((abstractMarker) => {
          markerManager.softUpdateAbstractMarker(abstractMarker);
          const markerDiv = markerLogicContainer[abstractMarker.id];
          updateMarkerDiv(abstractMarker, markerDiv);
        })
      }

      // While moving the map, this event is triggered many times per seconds
      // so we only perform a soft update (that could be debounced)
      map.on("move", softUpdateMarkers);

      // When done moving, we perform a full update
      map.on("moveend", updateMarkers)

      map.once("idle", () => {
        updateMarkers();
      });

    })()

    function makeMarker(abstractMarker,
      temperatureLayer, radarLayer,
      precipitationLayer, windLayer, date) {

      const marker = document.createElement("div");
      marker.classList.add("marker");
      marker.classList.add('fade-in-animation');
      marker.style.setProperty("width", `${abstractMarker.size[0]}px`);
      marker.style.setProperty("height", `${abstractMarker.size[1]}px`);
      marker.style.setProperty("transform", `translate(${abstractMarker.position[0]}px, ${abstractMarker.position[1]}px)`);

      const feature = abstractMarker.features[0];
      const lonLat = feature.geometry.coordinates;
      const temperatureData = temperatureLayer.pickAt(lonLat[0], lonLat[1]);
      const precipitationData = precipitationLayer.pickAt(lonLat[0], lonLat[1]);
      const windData = windLayer.pickAt(lonLat[0], lonLat[1]);
      const radarData = radarLayer.pickAt(lonLat[0], lonLat[1]);

      let mainWeatherIconURL = "./weather-icons/";
      const radarDBz = radarData?.value || -20;
      const precipMmH = precipitationData?.value || 0;
      const temperatureDeg = temperatureData?.value || 0;
      const windSpeed = windData?.speedKilometersPerHour || 0;
      const compassDirection = windData?.compassDirection || 0;
      const temperature = temperatureData?.value.toFixed(1);

      const sunPosition = SunCalc.getPosition(date, lonLat[1], lonLat[0]);

      if (sunPosition.altitude < 0) {
        mainWeatherIconURL += "night-";
      } else {
        mainWeatherIconURL += "day-";
      }

      if (radarDBz < 0) {

        if (precipMmH > 0.2) {
          mainWeatherIconURL += "cloudy-";
        } else {
          mainWeatherIconURL += "clear-";
        }

      } else if (radarDBz < 10) {
        mainWeatherIconURL += "cloudy-";
      } else if (radarDBz < 20) {
        mainWeatherIconURL += "overcast-";
      } else {
        mainWeatherIconURL += "extreme-";
      }

      if (precipMmH > 5) {
        mainWeatherIconURL += (temperatureDeg < -1 ? "snow" : "rain");
      } else if (precipMmH > 0.2) {
        mainWeatherIconURL += (temperatureDeg < -1 ? "snow" : "drizzle");
      } else {
        mainWeatherIconURL += "none";
      }

      mainWeatherIconURL += ".svg";

      let windDiv = "";
      if (windSpeed > 5) {
        windDiv = `
        <div
          class="markerWind"
        >
          <img class="markerWindIcon" src="./weather-icons/wind.svg"></img>${windSpeed.toFixed(0)} <span style="font-weight: 600; margin-left: 4px">${compassDirection}</span>
        </div>
        `
      }

      let precipDiv = "";
      if (precipMmH > 0.1) {
        precipDiv = `
        <div
          class="markerPrecipitation"
        >
          ${precipMmH.toFixed(1)} mm/h
        </div>
        `
      }

      marker.innerHTML = `
        <div class="markerPointy"></div>
        <div class="markerBody">

          <div class="markerTop">
            ${feature.properties["name:en"] || feature.properties["name"]}
          </div>

          <div class="markerBottom">
            <div class="markerBottomLeft">
              ${temperature ? `<div class="markerTemperature">${temperature}°</div>` : `<div class="markerTemperature"></div>`}
              ${windDiv}
            </div>

            <div class="markerBottomRight">
              <img class="markerMainWeatherIcon" src=${mainWeatherIconURL}></img>
              ${precipDiv}
            </div>
          </div>
        </div>
      `
      return marker;
    }

    function updateMarkerDiv(abstractMarker, marker) {
      marker.style.setProperty("width", `${abstractMarker.size[0]}px`);
      marker.style.setProperty("height", `${abstractMarker.size[1]}px`);
      marker.style.setProperty("transform", `translate(${abstractMarker.position[0]}px, ${abstractMarker.position[1]}px)`);
    }
  </script>
</body>

</html>
```

## How to create your custom popup?

Check the [Marker Layout](https://docs.maptiler.com/sdk-js/modules/marker-layout/) helper reference to find inspiration on creating your popups and to learn about all the options, methods, etc.

## Learn more

Check out the [Weather JS module reference](https://docs.maptiler.com/sdk-js/modules/weather/)

## Related examples

### [Display a popup](https://docs.maptiler.com/sdk-js/examples/popup/)

Examples

Add an information popup to the map.

### [Show polygon information on click](https://docs.maptiler.com/sdk-js/examples/polygon-popup-on-click/)

Examples

Display a popup when a user clicks a polygon.

### [Filtered Marker Layout](https://docs.maptiler.com/sdk-js/examples/marker-layout-basic/)

Examples

Create non-colliding marker overlays to display the information from the city and town label layers.

### [Weather layer switcher](https://docs.maptiler.com/sdk-js/examples/weather-layer-switcher/)

Examples

Switch weather layers and view detailed data under cursors.
