ui_elements_legendIcon.mjs

/**
### /ui/elements/legendIcon

The legendIcon module exports the default legendIcon(style) method.

@module /ui/elements/legendIcon
*/

const xmlSerializer = new XMLSerializer();

/**
@function legendIcon

@description
The legendIcon method returns an icon for displaying a mapp-style object outside the mapview.Map canvas.

@param {feature-style} style A JSON style object.

@returns {HTMLElement} A HTML element for the style.
*/
export default function legendIcon(style) {
  if (Array.isArray(style.icon)) {
    return createIconFromArray(style);
  }

  if (style.svg || style.type || style.icon) {
    return createIconFromInlineStyle(style);
  }

  if (!style.fillColor) {
    return createLineSymbol(style);
  }

  if (style.fillColor) {
    return createPolygonSymbol(style);
  }
}

/**
@function createIconFromArray

@description
The createIconFromArray method iterates through an `style.icon[]` array to create a layered and scaled icon element for displaying an icon style.

@param {feature-style} style A JSON style object.

@returns {HTMLElement} A HTML element for the style.
*/

function createIconFromArray(style) {
  const canvas = document.createElement('canvas');
  canvas.width = style.width;
  canvas.height = style.height;

  let toLoad = style.icon.length;

  // Images must be loaded in imageStyle image object before they can be applied to canvas.
  function onImgLoad() {
    if (--toLoad) return;

    const vectorContext = ol.render.toContext(canvas.getContext('2d'), {
      size: [style.width, style.height],
      pixelRatio: 1,
    });

    // Styles can not be assigned as array to convas context.
    style.icon.forEach((icon) => {
      vectorContext.setStyle(icon.legendStyle);
      vectorContext.drawGeometry(
        new ol.geom.Point([canvas.width * 0.5, canvas.height * 0.5]),
      );
    });
  }

  const legendScale = style.icon[0].legendScale || 1;

  style.icon.forEach((icon) => {
    if (icon.type && Object.hasOwn(mapp.utils.svgSymbols, icon.type)) {
      icon.url = mapp.utils.svgSymbols[icon.type](icon);
    }

    const imageStyle = new ol.style.Icon({
      src: icon.svg || icon.url,
      crossOrigin: 'anonymous',
      scale: legendScale * (icon.scale || 1),
      anchor: icon.legendAnchor || [0.5, 0.5],
    });

    icon.legendStyle = new ol.style.Style({
      image: imageStyle,
    });

    const img = imageStyle.getImage();

    // Check whether the image is loaded in style.
    if (imageStyle.getImageState() === 2) {
      onImgLoad();
    } else {
      img.addEventListener('load', onImgLoad);
      imageStyle.load();
    }
  });

  return canvas;
}

/**
@function createIconFromInlineStyle

@description
The createIconFromInlineStyle creates an icon from an inline style object.

@param {feature-style} style A JSON style object.

@returns {HTMLElement} A HTML element for the style.
*/

function createIconFromInlineStyle(style) {
  const iconUrl =
    style.icon?.svg ||
    style.svg ||
    style.icon?.url ||
    style.url ||
    mapp.utils.svgSymbols[style.icon?.type || style.type](style.icon || style);

  const inlineStyle = `
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
    width: ${style.width + 'px' || '100%'};
    height: ${style.height + 'px' || '100%'};
    background-image: url(${iconUrl})`;

  return mapp.utils.html.node`<div style=${inlineStyle}>`;
}

/**
@function createLineSymbol

@description
The createLineSymbol creates an icon for a stroke [line] style object.

@param {feature-style} style A JSON style object.

@returns {HTMLElement} A HTML element for the style.
*/

function createLineSymbol(style) {
  const path = `M 0,${style.height / 2} L ${style.width / 2},${style.height / 2} ${style.width / 2},${style.height / 2} ${style.width},${style.height / 2}`;

  const icon = mapp.utils.svg.node`
  <svg 
    height=${style.height} 
    width=${style.width}>
    <path
      d=${path}
      fill="none"
      stroke=${style.strokeColor}
      stroke-width=${style.strokeWidth || 1}/>`;

  const backgroundImage = `data:image/svg+xml,${encodeURIComponent(xmlSerializer.serializeToString(icon))}`;

  const inlineStyle = `
    background-position: center; 
    background-repeat: no-repeat; 
    background-size: contain; 
    width: ${style.width}px; 
    height: ${style.height}px; 
    background-image: url(${backgroundImage});`;

  return mapp.utils.html`<div style=${inlineStyle}>`;
}

/**
@function createPolygonSymbol

@description
The createPolygonSymbol creates an icon for a fill [polygon] style object.

@param {feature-style} style A JSON style object.

@returns {HTMLElement} A HTML element for the style.
*/

function createPolygonSymbol(style) {
  const icon = mapp.utils.svg.node`
  <svg 
    height=${style.height}
    width=${style.width}>
    <rect
      x=${style.strokeWidth || 1}
      y=${style.strokeWidth || 1}
      rx="4px"
      ry="4px"
      stroke-linejoin="round"
      width=${style.width - 2 * (style.strokeWidth || 1)}
      height=${style.height - 2 * (style.strokeWidth || 1)}
      fill=${style.fillColor}
      fill-opacity=${style.fillOpacity || 1}
      stroke=${style.strokeColor}
      stroke-width=${style.strokeWidth || 1}>`;

  const backgroundImage = `data:image/svg+xml,${encodeURIComponent(xmlSerializer.serializeToString(icon))}`;

  const inlineStyle = `
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
    width: ${style.width}px;
    height: ${style.height}px;
    background-image: url(${backgroundImage});`;

  return mapp.utils.html`<div style=${inlineStyle}>`;
}