{"id":174,"date":"2024-10-09T18:22:00","date_gmt":"2024-10-09T18:22:00","guid":{"rendered":"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/?post_type=blog&#038;p=174"},"modified":"2024-10-09T21:22:39","modified_gmt":"2024-10-09T21:22:39","slug":"implementation-of-highlights-in-the-js-sdk-for-javascript","status":"publish","type":"blog","link":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript","title":{"rendered":"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript"},"content":{"rendered":"\n<p class=\"undefined block-editor-paragraph\"><em>Highlight is an important part of interactive applications. Join us for this deep-dive into the programming techniques we use for implementing this feature in our products.<\/em><\/p>\n\n\n<div class=\"wp-block-image wp-duotone-unset-1\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/codepen.io\/dawken\/pen\/KKjjZWY\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"358\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png\" alt=\"\" class=\"wp-image-287\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-1-1024x358.png 1024w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-1-300x105.png 300w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-1-768x269.png 768w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-1-1536x537.png 1536w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-1-2048x716.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>This article comes with a <a href=\"https:\/\/codepen.io\/dawken\/pen\/KKjjZWY\" data-type=\"link\" data-id=\"https:\/\/codepen.io\/dawken\/pen\/KKjjZWY\">code sample on CodePen<\/a>. Be sure to check it out!<\/em><\/figcaption><\/figure><\/div>\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Table of Contents<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em>Introduction<\/em><\/li>\n\n\n\n<li><em>How highlights fit in the map rendering process<\/em><\/li>\n\n\n\n<li><em>Implementing highlights: a high-level view<\/em><\/li>\n\n\n\n<li><em>Build the binary mask<\/em><\/li>\n\n\n\n<li><em>Compute the signed distance field (SDF)<\/em><\/li>\n\n\n\n<li><em>Render and composite the highlight<\/em><\/li>\n\n\n\n<li><em>Possible extensions and improvements<\/em><\/li>\n\n\n\n<li><em>Conclusions<\/em><\/li>\n<\/ul>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Introduction<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Interactive applications enable the user to select, inspect, and modify their content. In GIS, content usually comes in the form of geographical entities called <em>&#8220;features&#8221;<\/em>, that are visualized as markers, fills and lines, on top of a basemap.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">More often than not, there are multiple features on the screen, but the user interacts with only a subset of them at a time. Here are some examples of ways that a user can interact with the displayed features. In all these cases, the technique of <em>&#8220;highlighting&#8221;<\/em> the feature or features involved can improve the user experience.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"650\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/09\/table-1-1024x650.png\" alt=\"\" class=\"wp-image-343\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/table-1-1024x650.png 1024w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/table-1-300x190.png 300w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/table-1-768x487.png 768w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/table-1-1536x974.png 1536w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/table-1-2048x1299.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">This article details a simplified version of the technique that we use to implement highlights in the ArcGIS Maps SDK for JavaScript. Knowledge of <a href=\"https:\/\/webgl2fundamentals.org\/webgl\/lessons\/webgl-shaders-and-glsl.html\">WebGL and GLSL shaders<\/a> is recommended but not essential to understand the principle of operation of the technique. The code sample includes 3 vertex shaders and 3 fragment shaders, but in this article we only discuss the fragment shaders because the required vertex processing is fairly generic and carries no highlight-specific concepts.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>How highlights fit in the map rendering process<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">In the ArcGIS Maps SDK for JavaScript, highlights are colored overlays that are added on top of the <a href=\"https:\/\/developers.arcgis.com\/documentation\/glossary\/basemap\/\">basemap <\/a>and <a href=\"https:\/\/developers.arcgis.com\/documentation\/glossary\/operational-layer\/\">operational layers<\/a>. They are characterized by a fill color and an outline color.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Here at Esri we are in the process of implementing multiple configurable highlight <em>&#8220;groups&#8221;<\/em>, that are applied to different features depending on the situation, based on a priority scheme. Highlights from the same group merge together, while highlights from higher priority groups override highlights from lower priority groups.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"411\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-2-1024x411.png\" alt=\"\" class=\"wp-image-306\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-2-1024x411.png 1024w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-2-300x120.png 300w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-2-768x308.png 768w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-2-1536x617.png 1536w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-2.png 1758w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">In the ArcGIS Maps SDK for JavaScript, highlights are implemented using a multi-step image processing technique powered by WebGL and GLSL shaders; the entire highlight workflow runs after the basemap and the operational layer have been drawn.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function renderMap(): void {\n\u00a0 renderBasemap();\n\u00a0 renderFeatures();\n renderHighlights();\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">We are going to take a look at how the <code>renderFeatures()<\/code> function is implemented, because rendering highlights require re-rendering the same features using different parameters and WebGL state. The defining traits of the feature rendering step are listed below.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It composites features with the basemap using standard additive blending.<\/li>\n\n\n\n<li>It uses a WebGL shader program dedicated to feature rendering.<\/li>\n\n\n\n<li>It renders all features, in their original colors. The rendering style is controlled by setting the uniform flag <code>u_OutputBinary<\/code> to <code>0<\/code>.<\/li>\n<\/ul>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function renderFeatures(): void {\n\u00a0 gl.blendEquation(gl.FUNC_ADD);\n\u00a0 gl.useProgram(featureProgram);\n\u00a0 gl.uniform1i(u_OutputBinary, 0);\n\u00a0 for (const feature of features) {\n\u00a0 \u00a0 renderFeature(feature);\n\u00a0 }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Implementation note<\/strong>. <em>Since this article is about the highlight technique, for the sake of simplicity we assume that we have a <code>renderFeature()<\/code> function that renders a given feature. Please note that in the actual ArcGIS Maps SDK for JavaScript implementation we never render individual features, because it leads to CPU-bound code that does not scale with the number of features.<\/em> Instead, we batch multiple features together and render them with a single draw call.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Here is what the features look like, prior to the application of the highlights. In the rest of the article, we are going to assume that we want to highlight all land vehicles in cyan, all vessels in yellow, and all aircrafts in magenta.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-11.png\" alt=\"\" class=\"wp-image-322\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-11.png 640w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-11-300x169.png 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption class=\"wp-element-caption\"><em>The features, without highlights applied yet.<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The shader program that renders the features has very loose requirements; anything that draws something on screen will work; even 3D shapes under perspective projections. The only requirement is that there must be a uniform <code>u_OutputBinary<\/code> that when set to <code>1<\/code> forces the fragment shader to output solid white. This functionality will be used to build the <em>&#8220;binary mask&#8221;<\/em> that is the input for the image processing algorithm.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">In the CodePen sample, we simply render markers using texture-mapped quads; the <code>u_OutputBinary<\/code> uniform forces the sampled color to become solid white, but only if it&#8217;s opaque enough; this causes only the pixels of the represented object to turn on as white, and not the entire quad.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>#version 300 es\nprecision mediump float;\n \nin vec2 v_Texcoord;\n \nuniform sampler2D u_ColorTexture;\nuniform bool u_OutputBinary;\n \nlayout(location = 0) out vec4 o_Color;\n \nvoid main(void) {\n o_Color = texture(u_ColorTexture, v_Texcoord);\n \n if (u_OutputBinary) {\n if (o_Color.a &gt; 0.1) {\n o_Color = vec4(1.0);\n } else {\n o_Color = vec4(0.0);\n }\n }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Implementing highlights: a high-level view<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Rendering a single-colored highlight is a 3-step process.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Build the binary mask.<\/li>\n\n\n\n<li>Compute the <em><a href=\"https:\/\/shaderfun.com\/2018\/03\/25\/signed-distance-fields-part-2-solid-geometry\/\">signed distance field<\/a><\/em> (SDF).<\/li>\n\n\n\n<li>Render the highlight, compositing it on top of the operational layer.<\/li>\n<\/ol>\n\n\n\n<p class=\"undefined block-editor-paragraph\">This 3-step process must itself be repeated with slightly different parameters for every highlight group. For instance, let us assume that the following highlight groups needs to be supported.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cyan, with low priority.<\/li>\n\n\n\n<li>Yellow, with intermediate priority;<\/li>\n\n\n\n<li>Magenta, with high priority.<\/li>\n<\/ul>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Then, the whole process needs to be repeated 3 times. The diagram below shows how the final image is incrementally built, step by step. In total, a case like the one above would require 12 distinct drawing operations.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"464\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/highlight-workflow-plain-2-1024x464.png\" alt=\"\" class=\"wp-image-310\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/highlight-workflow-plain-2-1024x464.png 1024w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/highlight-workflow-plain-2-300x136.png 300w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/highlight-workflow-plain-2-768x348.png 768w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/highlight-workflow-plain-2.png 1091w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The<strong> <\/strong><code>renderHighlights()<\/code> function implements all the highlight-related logic.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function renderHighlights(): void {\n for (let i = 0; i &lt; highlightGroups.length; i++) {\n buildBinaryMask(i);\n computeSDF();\n renderHighlight(i);\n }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Let us dive into each of the required steps.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Build the binary mask<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The binary mask is a raster image that is <code>0<\/code> where there are no features to highlight, and <code>1<\/code> where there are. It is produced by an iterative process that renders the features using a modified code path that outputs binary values. Building the binary mask for the group with index <code>highlightGroupIndex<\/code> is a 3-step process that runs after the highlights of all features from lower-priority groups <code>0, 1, ..., highlightGroupIndex - 1<\/code> have already been rendered and composited.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Clear a dedicated framebuffer to <code>0<\/code>.<\/li>\n\n\n\n<li>Render the features in the group <code>highlightGroupIndex<\/code> as <code>1<\/code>s. This can be done using the same shader program as when rendering features but setting <code>u_OutputBinary<\/code> to <code>1<\/code> so that the fragment shader outputs solid white instead of the original color.<\/li>\n\n\n\n<li>Clear all the features in any group with index greater than <code>highlightGroupIndex<\/code> to <code>0<\/code>s. This uses the same configuration of the previous step, but with a modified blending equation.<\/li>\n<\/ul>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function buildBinaryMask(highlightGroupIndex: number): void {\n gl.bindFramebuffer(gl.FRAMEBUFFER, binaryFramebuffer);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n setBinaryMask(highlightGroupIndex);\n clearBinaryMask(highlightGroupIndex);\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The <code>setBinaryMask()<\/code> is very similar to <code>renderFeatures()<\/code>; the only differences are that it renders only the features that are highlighted in the current group, and sets <code>u_OutputBinary<\/code><strong> <\/strong>to <code>1<\/code>, to get a binary output.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function setBinaryMask(highlightGroupIndex: number): void {\n gl.blendEquation(gl.FUNC_ADD);\n gl.useProgram(featureProgram);\n gl.uniform1i(u_OutputBinary, 1);\n for (const feature of features) {\n if (getHighlightGroupIndex(feature) === highlightGroupIndex)\n renderFeature(feature);\n }\n }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">As an example, here is what the binary mask would look like after running <code>setBinaryMask()<\/code> on a group that contains all land vehicles, like cars and motorbikes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-12.png\" alt=\"\" class=\"wp-image-323\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-12.png 640w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-12-300x169.png 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption class=\"wp-element-caption\"><em>All land vehicles are rendered as solid white, against a transparent background.<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The <code>clearBinaryMask()<\/code> is very similar to <code>setBinaryMask()<\/code>; the only differences are that it renders only the features that will need to be highlighted in <em>higher <\/em>priority groups, and that it uses the blend equation <code>gl.FUNC_REVERSE_SUBTRACT<\/code> to <em>&#8220;erase&#8221;<\/em> the <code>1<\/code>s and replacing them with <code>0<\/code>s; this is to make room for higher priority highlights that will be rendered later.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-ts\" data-lang=\"TypeScript\"><code>function clearBinaryMask(highlightIndex: number): void {\n gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);\n gl.useProgram(featureProgram);\n gl.uniform1i(u_OutputBinary, 1);\n for (const feature of features) {\n if (getHighlightIndex(feature) &gt; highlightIndex)\n renderFeature(feature);\n }\n }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">In our example above, we would use the <code>clearBinaryMask()<\/code> function to remove from the binary mask the areas that are occupied by vessels or aircrafts, so that only the pixels for land vehicles are left.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-13.png\" alt=\"\" class=\"wp-image-324\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-13.png 640w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-13-300x169.png 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption class=\"wp-element-caption\"><em>All vessels and aircrafts are cleared from the mask, leaving only parts of some of the previously rendered land vehicles.<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Compute the signed distance field (SDF)<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">This is an image processing step that takes as input the binary mask and outputs another raster where each pixel contains the distance between the original pixel and the closest pixel of opposite logical value. It uses a dedicated shader program whose vertex shader sets up a full-screen quad, and whose fragment shader explores a <code>21x21<\/code> neighborhood, that is, four <code>10x10<\/code> quadrants, around each pixel of the binary mask, and outputs a signed distance value. The value gets more negative as we move away from <code>1<\/code> into <code>0<\/code> (that is, <em>away <\/em>from the feature), and gets more positive as we move away from <code>0<\/code> into <code>1<\/code> (that is, <em>into <\/em>the feature).<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"872\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/09\/sdf-1024x872.png\" alt=\"\" class=\"wp-image-340\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/sdf-1024x872.png 1024w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/sdf-300x256.png 300w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/sdf-768x654.png 768w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/sdf-1536x1308.png 1536w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/sdf.png 1544w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">A neighborhood of pixels in the binary mask and its corresponding signed distance field (SDF).<\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The search for an opposite logical value is implemented by initializing a <code>distance<\/code> value to <code>15<\/code>, which is the maximum distance across the diagonal of a <code>10x10<\/code> quadrant, and scans all pixels, in all quadrants, until it finds one of opposite logical value; when that happens, <code>distance<\/code> gets updated.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">After the search has completed, <code>distance<\/code> receives its conventional sign.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Positive for neighborhoods centered on a <code>1<\/code>, with a <code>0<\/code> nearby.<\/li>\n\n\n\n<li>Negative for neighborhoods centered on a <code>0<\/code>, with a <code>1<\/code> nearby.<\/li>\n<\/ul>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Pixels that are far away from any pixel with opposite logical value will remain stuck at <code>+15<\/code> or <code>-15<\/code>.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">As a final step, the computed distance is scaled and biased so that it can be stored in the red channel of <code>RGBA8<\/code> texture; this last step is not required if using floating-point textures.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>#version 300 es\nprecision mediump float;\n \nin vec2 v_Texcoord;\n \nuniform vec2 u_ViewSize;\nuniform sampler2D u_ColorTexture;\n \nlayout(location = 0) out vec4 o_Color;\n \nvoid main(void) {\n float distance = 15.0;\n float center = texture(u_ColorTexture, v_Texcoord).a;\n\n for (int i = -10; i &lt;= 10; i++) {\n for (int j = -10; j &lt;= 10; j++) {\n float neighbor = texture(u_ColorTexture, v_Texcoord + vec2(float(j), float(i)) \/ u_ViewSize).a;\n float d = sqrt(float(i * i + j * j));\n if (center != neighbor &amp;&amp; d &lt; distance) {\n distance = d;\n }\n }\n }\n\n if (center &lt; 0.5) {\n distance = -distance;\n }\n\n o_Color = vec4(0.5 + 0.5 * distance \/ 15.0, 0.0, 0.0, 1.0);\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Implementation note<\/strong>. <em>The code sample uses a very generous <code>21x21<\/code> exact SDF; this allows for signed distances in the range <code>[-14, +14]<\/code> to be computed and hence enables very thick outlines. In the implementation of highlights in the ArcGIS Maps SDK for JavaScript, we adopted smaller neighborhoods, and we used an approximate SDF estimation technique that is less accurate visually, but much more efficient.<\/em><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Here is what the SDF for the previously built binary mask look like. Black is <code>0<\/code> and represents a distance of <code>-15<\/code>, while fully saturated red is <code>255<\/code> and represents a distance of <code>+15<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-14.png\" alt=\"\" class=\"wp-image-325\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-14.png 640w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-14-300x169.png 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption class=\"wp-element-caption\"><em>The SDF for land vehicles.<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Render and composite the highlight<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Highlight rendering is another full-screen pass that takes the generated SDF and colorizes it based on the preferences of the user. The fragment shader recovers the distance value stored in the red channel of the <code>RGBA8<\/code> texture, undoes scaling and bias (this step is not required if the distance was stored in a floating-point texture), and checks the distance value against the outline width specified by the user.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>If found to be less than <code>-u_OutlineWidth \/ 2.<\/code>0 then the fragment is far enough outside of the feature that it should be rendered as transparent.<\/li>\n\n\n\n<li>If found to be between <code>-u_OutlineWidth \/ 2.0<\/code> and <code>u_OutlineWidth \/ 2.0<\/code> then the fragment is on the outline of the highlight and should be rendered with a certain user-defined opacity.<\/li>\n\n\n\n<li>Otherwise, the fragment is part of the fill and should be rendered with another user-defined opacity.<\/li>\n<\/ul>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>#version 300 es\nprecision mediump float;\n \nin vec2 v_Texcoord;\n \nuniform vec2 u_ViewSize;\nuniform sampler2D u_DistanceTexture;\nuniform vec4 u_HighlightColor;\nuniform float u_OutlineWidth;\nuniform float u_FillOpacity;\nuniform float u_OutlineOpacity;\n \nlayout(location = 0) out vec4 o_Color;\n \nvoid main(void) {\n float distance = texture(u_DistanceTexture, v_Texcoord).r;\n distance -= 0.5;\n distance \/= 0.5;\n distance *= 15.0;\n\n float r;\n\n if (distance &gt; u_OutlineWidth \/ 2.0) {\n r = u_FillOpacity;\n } else if (distance &lt; -u_OutlineWidth \/ 2.0) {\n r = 0.0;\n } else {\n r = u_OutlineOpacity;\n }\n\n o_Color = u_HighlightColor;\n o_Color.a *= r;\n o_Color.rgb *= o_Color.a;\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"undefined block-editor-paragraph\">The code above colorizes an SDF using cyan and renders it on top of the image.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\" src=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-15.png\" alt=\"\" class=\"wp-image-326\" srcset=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-15.png 640w, https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/08\/image-15-300x169.png 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption class=\"wp-element-caption\"><em>The colorized SDF coincides with the areas of the screen occupied by land vehicles.<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Possible extensions and improvements<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">We tried to keep the complexity of the code in this article and in the CodePen sample to a minimum; with that said, there are a lot of improvements and optimizations that we are either already shipping in the ArcGIS Maps SDK for JavaScript, or that we are considering implementing in the future.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Approximate efficient SDF computation based on separable gaussian kernels.<\/li>\n\n\n\n<li>Different colors for the fill of a highlight and its outline.<\/li>\n\n\n\n<li>Anti-aliased outlines.<\/li>\n\n\n\n<li>Pad the outlines outward so that feature edges are not covered.<\/li>\n\n\n\n<li>Soft outlines, halos, and <em>&#8220;glow&#8221;<\/em> effect.<\/li>\n\n\n\n<li>Animating highlight colors.<\/li>\n\n\n\n<li>Animating outline size.<\/li>\n<\/ul>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><strong>Conclusions<\/strong><\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Simple and efficient highlights can be implemented using image processing techniques and some kind of distance field, either exact or approximate. Implementation is easy on WebGL-enabled platforms and can be ported to any other language or graphic APIs. Check out <a href=\"https:\/\/codepen.io\/dawken\/pen\/KKjjZWY\">the sample<\/a> and experiment with it, and tell us what your experience is. Also don&#8217;t forget to <a href=\"https:\/\/x.com\/EsriDevs\">follow EsriDevs on X<\/a>!<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Here at Esri we love web graphics and front-end technologies in general. If you love them too, please take a moment to visit our <a href=\"https:\/\/www.esri.com\/en-us\/about\/careers\/job-search?q=javascript+software+engineer&amp;role=Individual+Contributor&amp;category=ArcGIS+Product+Engineering\">job openings<\/a> for JavaScript and the Web platform.<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\">Happy coding!<\/p>\n\n\n\n<p class=\"undefined block-editor-paragraph\"><\/p>\n","protected":false},"author":4,"featured_media":0,"parent":0,"menu_order":0,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-174","blog","type-blog","status-publish","format-standard","hentry","category-software-development"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.2 (Yoast SEO v25.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Implementation of Feature Highlights in the ArcGIS Maps SDK for JavaScript - Esri Software Engineering Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript\" \/>\n<meta property=\"og:description\" content=\"Highlight is an important part of interactive applications. Join us for this deep-dive into the programming techniques we use for implementing this feature in our products. Table of Contents Introduction Interactive applications enable the user to select, inspect, and modify their content. In GIS, content usually comes in the form of geographical entities called &#8220;features&#8221;, [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript\" \/>\n<meta property=\"og:site_name\" content=\"Esri Software Engineering Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2024-10-09T21:22:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript\",\"url\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript\",\"name\":\"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript - Esri Software Engineering Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage\"},\"thumbnailUrl\":\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png\",\"datePublished\":\"2024-10-09T18:22:00+00:00\",\"dateModified\":\"2024-10-09T21:22:39+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage\",\"url\":\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png\",\"contentUrl\":\"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/#website\",\"url\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/\",\"name\":\"Esri Software Engineering Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Implementation of Feature Highlights in the ArcGIS Maps SDK for JavaScript - Esri Software Engineering Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript","og_locale":"en_US","og_type":"article","og_title":"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript","og_description":"Highlight is an important part of interactive applications. Join us for this deep-dive into the programming techniques we use for implementing this feature in our products. Table of Contents Introduction Interactive applications enable the user to select, inspect, and modify their content. In GIS, content usually comes in the form of geographical entities called &#8220;features&#8221;, [&hellip;]","og_url":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript","og_site_name":"Esri Software Engineering Blog","article_modified_time":"2024-10-09T21:22:39+00:00","og_image":[{"url":"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript","url":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript","name":"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript - Esri Software Engineering Blog","isPartOf":{"@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage"},"image":{"@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage"},"thumbnailUrl":"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png","datePublished":"2024-10-09T18:22:00+00:00","dateModified":"2024-10-09T21:22:39+00:00","breadcrumb":{"@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#primaryimage","url":"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png","contentUrl":"https:\/\/uat.esri.com\/en-us\/software-engineering\/blog\/wp-content\/uploads\/2024\/08\/image-1-1024x358.png"},{"@type":"BreadcrumbList","@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/articles\/implementation-of-highlights-in-the-js-sdk-for-javascript#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog"},{"@type":"ListItem","position":2,"name":"Implementation of feature highlights in the ArcGIS Maps SDK for JavaScript"}]},{"@type":"WebSite","@id":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/#website","url":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/","name":"Esri Software Engineering Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"text_date":"October 9, 2024","author_name":"Dario D'Amico","author_page":false,"custom_image":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/big.png","primary_product":false,"tag_data":[],"category_data":[{"term_id":3,"name":"Software development","slug":"software-development","term_group":0,"term_taxonomy_id":3,"taxonomy":"category","description":"Articles focusing on how we apply coding practices, software design, development methodologies, and tools used while building our products, systems, etc.","parent":0,"count":8,"filter":"raw"}],"product_data":{"errors":{"invalid_taxonomy":["Invalid taxonomy."]},"error_data":[]},"primary_product_link":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/","short_description":"Join us for this deep-dive into the programming techniques that we use for implementing feature highlights in our products.","image":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/app\/uploads\/2024\/09\/card.png","_links":{"self":[{"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/article\/174","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/article"}],"about":[{"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/users\/4"}],"version-history":[{"count":0,"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/article\/174\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/media?parent=174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/categories?post=174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.esri.com\/en-us\/software-engineering\/blog\/wp-json\/wp\/v2\/tags?post=174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}