Extracting legend RGB values from ArcGIS Server using JavaScript

Most technical issues that developers face these days are relatively easily overcome with a bit of web research. It’s rare that you come across something that someone else hasn’t already solved and blogged about online. This week, however, we had a problem that didn’t appear to have a readily available solution online, hopefully this blog post might help someone else that is searching for a similar solution.

Whilst putting together a web mapping application using the ESRI JavaScript API, we needed to be able to pull out the red, green and blue (RGB) components of the legend colours from a map layer. Some research revealed a few solutions in Visual Basic that could be run against ArcGIS Desktop, but we were after something we could run via the web, ideally from JavaScript. The ESRI API offers the esri.dijit.Legend class, which does a good job of pulling all the correct data from your map layer on ArcGIS Server and rendering a full legend on your page. However, it only offers startup(), refresh() and destroy() methods, with no way of picking the legend apart or interrogating its individual components. We figured that, in order to render the legend in HTML, ArcGIS Server must be making the information we wanted available to this class somehow.

The solution lies in the fact that ArcGIS Server makes all of this information available as part of one large layer description, in JSON format. This can be accessed over the web from your ArcGIS Server using the following URL:
http://<arcgis_server_name>/arcgis/rest/services/<folder>/<service>/MapServer/<layer>?f=json
which produces something like this

{"currentVersion":10.1,"id":0,"name":"CF1_ISISSoilMap","type":"Feature
 Layer","description":"","definitionExpression":"",
"geometryType":"esriGeometryPolygon","copyrightText":"",
"parentLayer":null,"subLayers":[],"minScale":0,"maxScale":0,
"drawingInfo":{"renderer":{"type":"uniqueValue","field1":
"Association_Symbol","field2":null,
"field3":null,"fieldDelimiter":", ","defaultSymbol":null,
"defaultLabel":null,"uniqueValueInfos":[{"symbol":{"type":"esriSFS"
...

At first this isn’t easily readable. Changing the URL parameter from f=json to f=pjson formats things a little better on the page, which helps:
http://<arcgis_server_name>/arcgis/rest/services/<folder>/<service>/MapServer/<layer>?f=pjson

{
 "currentVersion": 10.1,
 "id": 0,
 "name": "CF1_ISISSoilMap",
 "type": "Feature Layer",
 "description": "",
 "definitionExpression": "",
 "geometryType": "esriGeometryPolygon",
 "copyrightText": "",
 "parentLayer": null,
 "subLayers": [],
 "minScale": 0,
 "maxScale": 0,
 "drawingInfo": {
  "renderer": {
   "type": "uniqueValue",
   "field1": "Association_Symbol",
   "field2": null,
   "field3": null,
   "fieldDelimiter": ", ",
   "defaultSymbol": null,
   "defaultLabel": null,
   "uniqueValueInfos": [
    {
     "symbol": {
...

This might be fine for small amounts of JSON data, but still wasn’t that easy to navigate for the 3500 lines of text that was being output for the layer in question.

There are a number of excellent desktop and web based tools for making JSON data easier to read, a Google search for “JSON Viewer” will bring up a wide selection. One particular favourite of ours is http://jsonviewer.stack.hu/. Pasting the layer description data in, we were able to drill down into the various objects until we found exactly what we were looking for; the legend RGB values.

JSON visualiser

JSON visualiser

The section we’re interested in is:
JSON.drawingInfo.renderer.uniqueValueInfos
which is an array of each of the legend items. Each object in the array contains information on the legend label, style, colour and more. The red, green and blue component values that we were after are stored as the following, respectively:
JSON.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[0]
JSON.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[1]
JSON.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[2]

with JSON.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[3] holding an opacity value. All are integers from 0-255. In the examples above, i refers to the index of a particular legend item.

Other useful legend data include:
JSON.drawingInfo.renderer.uniqueValueInfos[i].label
JSON.drawingInfo.renderer.uniqueValueInfos[i].description

You could use the data above in a live JavaScript application to interrogate ArcGIS Server on the fly. Note that you should use the f=json parameter if you do this, for performance reasons. However, in this particular instance, we wanted to extract these values into a CSV file for storage in a database elsewhere. There was one small stumbling block that needed to be overcome and that was that not all legend items contained simple block colour, some were patterned or used repeated images instead of a plain colour fill. Luckily, the JSON data helped us filter these out by providing the following:
JSON.drawingInfo.renderer.uniqueValueInfos[i].symbol.type
For block colour legend entries, this variable is set to “esriSFS”. In this particular case, we were able to ignore any entries that weren’t equal to esriSFS.

Here is the JavaScript function for producing the required CSV. Note that this example makes use of the native JSON.parse() function which doesn’t work in some older versions of IE. For full browser compatibility, the DOJO and JQuery libraries offer suitable cross-browser alternatives.

function getRGB(){
  var XMLRequest = new XMLHttpRequest();
  var JSONURL = "<location of your JSON data>";
  XMLRequest.open("GET",JSONURL,false);
  XMLRequest.send();
  var retrievedJSON = XMLRequest.responseText;
  var retrievedObj;
  retrievedObj = JSON.parse(retrievedJSON);

  var myHTML = "Label,Red,Green,Blue<br />\n";
  for (var i=0;i<retrievedObj.drawingInfo.renderer.uniqueValueInfos.length;i++){
    myHTML += retrievedObj.drawingInfo.renderer.uniqueValueInfos[i].label;
    if (retrievedObj.drawingInfo.renderer.uniqueValueInfos[i].symbol.type == "esriSFS")
    {
      myHTML += "," + retrievedObj.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[0];
      myHTML += "," + retrievedObj.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[1];
      myHTML += "," + retrievedObj.drawingInfo.renderer.uniqueValueInfos[i].symbol.color[2];
    }
    myHTML += "<br />\n";
  }
  document.getElementById("RGBValues").innerHTML = myHTML;
}

With the following <div> required further down in the <body> of the page:

<div id="RGBValues">
  RGB Values to go here - something went wrong if you can see this.
</div>

The output looked something like this, which was copied and pasted into Excel for tweaking and then imported into our database.

CSV data output to browser

CSV data output to browser