OGC SLD/SE styling provides you with a number of possible ways to style a point on the map. For simple graphics, like circles or squares, you can use the Mark element. For more complex graphics, you can use the ExternalGraphic element.

For an ExternalGraphic element, it’s possible to map any given string to icon images by specifying an IconProvider in the SEPainterCreateOptions. To learn how to use the IconProvider, see Map ExternalGraphics to distinct icon images.

For a Mark element, LuciadRIA allows you to use a function to define the WellKnownName (WKN) of the mark. As a result, you can change the mark dynamically based on the feature properties.

Changing the Mark element with the WKN extension

Using the WKN vendor-specific extension, you can map the element to the IconProvider capability. This mapping allows you to fully customize your point styling based on the feature properties. It combines:

  • Functions: use a custom function to choose the WellKnownName of the Mark element

  • IconProvider: use the IconProvider to map the WellKnownName to an icon image

You can create your own WellKnownName in a custom function. If you want to use it to create a custom icon, make sure that your WellKnownName start with "LuciadIconProvider:\\". LuciadRIA forwards Mark elements that have a name starting with "LuciadIconProvider:\\" to the IconProvider. In your IconProvider, you can parse the WellKnownName string and return the correct icon image.

The WellKnownName gets wrapped in a JSON object, together with the mark icon options. LuciadRIA stringifies this object before sending it to the IconProvider. This allows you to use the Mark options to create your custom icon.

const iconJson = {
  url: "LuciadIconProvider://?key=value",
  iconOptions: iconOptions,
}
const uri = JSON.stringify(iconJson);

ExternalGraphic icons are pre-loaded when creating the SE FeaturePainter. Marks aren’t pre-loaded, but they are cached. Using this WellKnownName extension for Mark might negatively impact performance, especially when you’re using a lot of different icons.

Example

This example shows you how to use the Mark element with the WKN extension. It renders the labels of some big cities in the USA as icons.

MarkIconProvider
Figure 1. Use of a custom function to create a LuciadIconProvider:\\ mark rendering the labels as icons

First, you define a Function getIconProviderMark that gets called to create the WellKnownName of the mark. In this case, the WellKnownName is based on the NAME property of the feature.

Expand for the code that creates the image:

<?xml version="1.0" encoding="UTF-8"?>
<FeatureTypeStyle xmlns="http://www.opengis.net/se" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.opengis.net/se http://schemas.opengis.net/se/1.1.0/FeatureStyle.xsd"
                  version="1.1.0">
  <Rule>
    <PointSymbolizer>
      <Graphic>
        <Mark>
          <WellKnownName>
            <ogc:Function name="getIconProviderMark">
              <ogc:PropertyName>NAME</ogc:PropertyName>
            </ogc:Function>
          </WellKnownName>
        </Mark>
        <Size>32</Size>
      </Graphic>
    </PointSymbolizer>
  </Rule>
</FeatureTypeStyle>

Next, you provide the custom function getIconProviderMark and the custom IconProvider as an option to the SEPainterFactory method.

createPainterFromString(seSource, {
  functionEvaluators: {
    "getIconProviderMark": (name: string): string  => {
      return `LuciadIconProvider://?name=${name}`;
    }
  },
  iconProvider: {
    getIcon(uri: string): HTMLImageElement | HTMLCanvasElement | string {
      if (uri.indexOf("LuciadIconProvider://") > -1) {
        /**
         * In this case, the request for an icon comes from a Mark
         * The uri is a stringified JSON object:
         *
         * const iconJson = {
         *   url: "LuciadIconProvider://${name}_#ff0000",
         *   iconOptions: IconOptions,
         * }
         * const uri = JSON.stringify(iconJson);
         */
        try {
          const iconJson = JSON.parse(uri);
          const url = URL.parse(iconJson.url);
          if (url !== null) {
            const iconOptions: IconOptions = iconJson.iconOptions as IconOptions;
            const nameString = url.searchParams.get("name") ?? "-";
            const canvasNodeText: HTMLCanvasElement = document.createElement("canvas");
            canvasNodeText.width = iconOptions.width ?? 256;
            canvasNodeText.height = iconOptions.height ?? 256;
            // draw large text, to have a sharp result!
            const font = "bold 100px sans-serif";
            const ctxText = canvasNodeText.getContext("2d")!;
            // first fetch the size of the text
            ctxText.font = font;
            const textMetrics = ctxText.measureText(nameString);

            // Create a new canvas with the size of the text
            const canvasNode: HTMLCanvasElement = document.createElement("canvas");
            const width = textMetrics.actualBoundingBoxRight + 20;
            const height = textMetrics.hangingBaseline + 20;
            canvasNode.width = width;
            canvasNode.height = height;
            const ctx = canvasNode.getContext("2d")!;
            // create the background
            ctx.fillStyle = "rgba(0,0,0,0)";//transparent
            ctx.fillRect(0, 0, width, height);
            // write the text
            ctx.font = font;
            ctx.fillStyle = "rgb(255,0,0)";
            ctx.fillText(`${nameString}`, 10, height - 10);
            // draw the border
            ctx.strokeStyle = "rgb(0,0,255)";
            ctx.lineWidth = 10;
            ctx.strokeRect(0, 0, width, height);
            return canvasNode;
          }
        } catch (e) {

        }
      }
      return uri;
    }
  },
}).then(function(painter) {
  map.layerTree.addChild(new FeatureLayer(model, {
    painter: painter
  }));
});

Although the IconProvider can return HtmlCanvasElement, HtmlImageElement and string, Mark only accepts a synchronous HtmlCanvasElement.