0
votes

I am trying to add a legend to a map created by Asymmetrik/ngx-leaflet. The map is created by following the tutorial in https://github.com/Asymmetrik/ngx-leaflet . There are two different layers and for each layer there shoud be a different legend. The code is made by using angular CLI and leaflet. There is a map component. The map.component.ts file is as follows:

import {Component, Input, OnChanges, OnInit} from '@angular/core';
import {circle, geoJSON, GeoJSONOptions, latLng, Layer, LeafletMouseEvent, polygon, tileLayer} from 'leaflet';
import * as L from 'leaflet';
import {SimpleResult} from '../../models/SimpleResult';
import {HttpClient} from '@angular/common/http';
import {IDrilldownResult} from '../../models/DrilldownResult';

@Component({
  selector: 'app-map-chart',
  templateUrl: './map-chart.component.html',
  styleUrls: ['./map-chart.component.css']
})
export class MapChartComponent implements OnInit, OnChanges {

  @Input() private data: IDrilldownResult;
  public options: any;
  public layersControl = {
    baseLayers: { }
  };

  private getColor(value, max, min) {
    const val = (value - min) / (max - min) ;
    const hue = (val * 120).toString(10);
    return ['hsl(', hue, ',100%,50%)'].join('');
  }

  constructor(
    private http: HttpClient
  ) { }

  ngOnInit() {
    this.createChart();
    /*if (this.data) {
      this.updateChart();
    }*/
  }

  ngOnChanges() {
    this.updateChart();
  }

  private createChart() {
    this.options = {
      layers: [
        tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
      ],
      zoom: 6,
      center: latLng(51.5167, 9.9167)
    };
  }

  private createGermanyLayer() {
    this.http.get('assets/bundeslaender.geojson')
      .subscribe((res: any) => {
        const deIndex = this.data.children.findIndex(e => e.name === 'de');
        const germanData = this.data.children[deIndex];
        res.features.forEach(feat => {
          const stateIndex = germanData.children.findIndex(e => {
            if (e.name) {
              return e.name.toLowerCase() === feat.properties.NAME_1.toLowerCase();
            }
          });
          feat.properties.SALES = germanData.children[stateIndex].label;
        });
        const max = Math.max.apply(Math, res.features.map(feat => feat.properties.SALES));
        const min = Math.min.apply(Math, res.features.map(feat => feat.properties.SALES));

        const geoJsonGermanyLayer = {
          id: 'geoJSON',
          name: 'Geo JSON Polygon',
          enabled: true,
          layer: geoJSON(
            res as any,
            {
              style: (d) => {
                const color = this.getColor(d.properties.SALES, max, min);
                return ({
                  color: color,
                  weight: 1
                });
              },
              onEachFeature: (feature, layer) => {
                layer.bindPopup('<h5>' + feature.properties.NAME_1 + '</h5><p>Revenue: ' + feature.properties.SALES.toFixed(2) + '</p>');
              }
            })
        };
        this.layersControl.baseLayers['Germany'] = geoJsonGermanyLayer.layer;

        // begining of legend

        const v1 = min;
        const v2 = min + Math.round((max - min ) / 2);
        const v3 = max;
        const legend = new (L.Control.extend({
          options: { position: 'bottomright' }
        }));
        // const legend = L.control({position: 'bottomright'});
        const vm = this;
        legend.onAdd = function (map) {
          const div = L.DomUtil.create('div', 'legend');
          const labels = [
            'Sales greater than ' + v1,
            'Sales greater than ' + v2,
            'Sales equal or less than ' + v3
          ];
          const grades = [v1 + 1, v2 + 1, v3 ];
          div.innerHTML = '<div><b>Legend</b></div>';
          for (let i = 0; i < grades.length; i++) {
            div.innerHTML += '<i style="background:' + vm.getColor(grades[ i ], this.max, this.min) + '"> &nbsp; &nbsp;</i> &nbsp; &nbsp;'
            + labels[i] + '<br/>';
          }
          return div;
        };
        legend.addTo(geoJsonGermanyLayer);
        // end of legend


      });



  }

  private createEuropeLayer() {
    this.http.get('assets/europe.geojson')
      .subscribe((res: any) => {
        res.features.forEach(feat => {
          const countryIndex = this.data.children.findIndex(e => {
            if (e.name) {
              return e.name.toLowerCase() === feat.properties.FIPS.toLowerCase() || e.name.toLowerCase() === feat.properties.ISO2.toLowerCase();
            }
          });
          feat.properties.SALES = countryIndex !== -1 ? this.data.children[countryIndex].label : undefined;
        });
        const max = Math.max.apply(Math, res.features.filter(feat => feat.properties.SALES !== undefined).map(feat => feat.properties.SALES));
        const min = Math.min.apply(Math, res.features.filter(feat => feat.properties.SALES !== undefined).map(feat => feat.properties.SALES));
        const maxLog = Math.log(max);
        const minLog = Math.log(min);

        const geoJsonEuropeLayer = {
          id: 'geoJSON',
          name: 'Geo JSON Polygon',
          enabled: true,
          layer: geoJSON(
            res as any,
            {
              style: (d) => {
                const color = this.getColor(Math.log(d.properties.SALES), maxLog, minLog);
                return ({
                  color: color,
                  weight: 1
                });
              },
              onEachFeature: (feature, layer) => {
                const sales = feature.properties.SALES !== undefined ? feature.properties.SALES.toFixed(2) : 'No orders';
                layer.bindPopup('<h5>' + feature.properties.NAME + '</h5>' +
            '<p>Revenue: ' + sales + '</p>');
              }
            })
        };
        this.layersControl.baseLayers['Europe'] = geoJsonEuropeLayer.layer;
      });
  }

  private updateChart() {
    this.createGermanyLayer();
    this.createEuropeLayer();
  }

}

The legend does not appear on the page. The console shows the follwoing error: cannot read property 'bottomright' of undefined as shown in the image below:

enter image description here

The map is shown correctly but there is no legend. I appreciate if you tell me what is wrong with my code and why the legend is not showing. Thank you for your attention.

1
There's a lot to unwind in your sample code. It might help if you simplify the code to focus specifically on getting a legend to work with little to no other code. Also, immediately I notice you are adding the legend control to the layer. You can only add controls to the map, not a layer.reblace
thanks for the guidance. Before and after the legend I have added some comment to show where the problem is. Can you please be more specific about adding the legend to map itself? The values inside the leend need to change for each of the layers.E. Erfan

1 Answers

4
votes

OK I find the answer myself based on the comment made. Legend can only be added to the map itself and not the layer. But the map is not available when you use the following code:

private createChart() {
    this.options = {
        layers: [
            tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
       ],
        zoom: 6,
        center: latLng(51.5167, 9.9167)
    };
}

In order to get the map made by leaflet itself, you need to bind it. This is done in template file:

<div style="height: 700px;"
     leaflet
     [leafletOptions]="options"
     [leafletLayersControl]="layersControl"
     (leafletMapReady)="onMapReady($event)">
</div>

and then I defined onMapReady() function as follows:

onMapReady(map: Map) {
    this.updateChart();
    // Do stuff with map
    map.on('baselayerchange', (eventLayer) => {
      const v1 = this.min;
      const v2 = this.min + Math.round((this.max - this.min ) / 2);
      const v3 = this.max;
      const legend = new (L.Control.extend({
        options: { position: 'bottomright' }
      }));

      const vm = this;
      legend.onAdd = function (map) {
        const div = L.DomUtil.create('div', 'legend');
        const labels = [
          'Sales greater than ' + v1,
          'Sales greater than ' + v2,
          'Sales equal or less than ' + v3
        ];
        const grades = [v1+ 1, v2+ 1, v3 ];
        div.innerHTML = '<div><b>Legend</b></div>';
        for (let i = 0; i < grades.length; i++) {
          div.innerHTML += '<i style="background:' + vm.getColor(grades[ i ], v3, v1) + '"> &nbsp; &nbsp;</i> &nbsp; &nbsp;'
        + labels[i] + '<br/>';
        }
        return div;
      };
      legend.addTo(map);
    });

}

The legend appears only after the map is ready. Map is the first thing that is being created Then the layers appear. Therefore, I called updateChart() in onMapReady() to have access to min and max values of each layer.

Still there is an issue which is another legend is added when the layer is changed. But it is not relevant to this question.