Search code examples
javascriptmultidimensional-arraynested-loopsgeojsonmapbox-gl-js

Format complex nested objects


I have posted previously in the gis forum (Alt below) though there is little activity so i am trying my luck here. This is also, fundamentally i think, a js object array question. There are numerous similar questions but i just can't get a solution that works for the object structure i have to deal with.

Caveats aside;

The issue: Get the information returned from a (unknown) number of objects nested within an object array parsed so they can be extracted, formatted and displayed in a human readable way. E.g;

Layer id  <--- currently returned as val.layer.id (see snippet)
key1 : value1 <
key2 : value2 <-- returns in console.log as Object at val.properties
key3 : value3 <

Layer id
key1 : value1
key2 : value2

Layer id...

I just need each 'features.layer.id' with it's associated 'features.properties', the keys and values of which are unknown and differ between layers (an object located at features.properties). These feature positions are consistent in MapBox and so a solution here should be applicable to future users.

Current code; Commented out are a scattering of my attempts to access and display the required values. The 'features' elementID is the info panel. features is otherwise the returned nested object (see sample).

concattedjson currently produced an error ("unexpected token N") on first letter of first layer title.

map.on('click', function (e) {
    map.featuresAt(e.point, {radius: 5, layer: lyrlist}, function (err, features) {
        if (err) throw err;

        var keys = Object.keys(features);
        var val = "";

        for (var i = 0; i < keys.length; i++) {
            val = features[keys[i]];
            //document.getElementById('features').innerHTML = '<b>'+val.layer.id+'</b>';
            //console.log(val.layer.id,val.properties);
            //console.log(val.properties); shows each layer properties on click
            //console.log(val.layer.id); shows each layer title on click
            //console.log(val);
            var lyrid = val.layer.id;
            var prop = val.properties;
            concattedjson = JSON.stringify(JSON.parse(lyrid).concat(JSON.parse(prop)));
        }   
    document.getElementById('features').innerHTML = concattedjson   
    //document.getElementById('features').innerHTML = JSON.stringify(val.layer, ['id'], 2);     
    //document.getElementById('features').innerHTML = JSON.stringify(val.properties, null, 2);          
    });
});

Sample of JSON containing two 'layers'

    [
  {
    "layer": {
      "id": "Nature Improvement Area",
      "minzoom": 7,
      "interactive": true,
      "paint": {
        "fill-opacity": 0.3,
        "fill-color": "hsl(0, 24%, 24%)"
      },
      "type": "fill",
      "source": "mapbox://mbbdev.8uf2j3ka",
      "source-layer": "lcr_nia_v1_region",
      "layout": {
        "visibility": "visible"
      }
    },
    "type": "Feature",
    "geometry": null,
    "properties": {
      "NIA_Focu00": "Netherley Brook and Ditton Brook Corridor",
      "NIA_Focu01": "INSERT LINK TO PROFILE DOC",
      "NIA_Focus_": "07"
    },
    "id": 16
  },
  {
    "layer": {
      "id": "Liverpool City Region",
      "minzoom": 6,
      "interactive": true,
      "paint": {
        "fill-opacity": 0.2,
        "fill-antialias": true,
        "fill-color": "hsl(0, 4%, 40%)"
      },
      "type": "fill",
      "source": "mapbox://mbbdev.67id5f6x",
      "source-layer": "lcr_district_boundary_region",
      "filter": [
        "==",
        "$type",
        "Polygon"
      ]
    },
    "type": "Feature",
    "geometry": null,
    "properties": {
      "AREA_HA": 8618.7,
      "NAME": "Knowsley"
    },
    "id": 1
  }
]

Solution

  • Here's how you iterate the features object, and create something human readable out of it. Explanation in the comments:

    map.on('click', function (e) {
        map.featuresAt(e.point, {
            radius: 5,
        }, function (err, features) {
            if (err) throw err;
    
            // Grab the 'ul' element with ID 'features' from the DOM            
            var featureList = document.getElementById('features');
    
            // Empty the list on every click
            featureList.innerHTML = '';
    
            // Iterate the features array
            for (var i = 0; i < features.length; i++) {
    
                // Create a listitem for each feature
                var featureItem = document.createElement('li');
    
                // Set the feature's listitem's content to the layer's ID
                featureItem.textContent = features[i].layer.id;
    
                // Append the featureitem to the featurelist
                featureList.appendChild(featureItem);
    
                // Create a new list for the item's properties
                var propertyList = document.createElement('ul');
    
                // Append the list to the feature's listitem
                featureItem.appendChild(propertyList);
    
                // Create convenience var for the properties object
                var properties = features[i].properties;
    
                // Iterate the properties object
                for (var property in properties) {
    
                    // Create new listitem for every property
                    var propertyItem = document.createElement('li');
    
                    // Set property's listitem's textcontent to key/value
                    propertyItem.textContent = property + ': ' + properties[property];
    
                    // Append property's listitem to the feature's propertylist.
                    propertyList.appendChild(propertyItem);
    
                }
    
            }
    
        });
    });
    

    Here's a working example on Plunker: http://plnkr.co/edit/iqntvRFTcWK1hgpzPBvX?p=preview

    You might want to read this if you want to grasp the concept of object properties and how to access them:

    https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors