Search code examples
angulardrop-down-menuleafletbootstrap-selectngx-leaflet

Updating ngx-leaflet map based on Bootstrap select input


I have an app that updates a Bootstrap select input dropdown menu when you click a polygon on the map in ngx-leaflet based on this example. I want to be able to also select a polygon name from the select input and have the same functionality as the click event--in this case, using fitBounds to pan and zoom to the selected polygon.

I added event binding to my HTML template, which detects each new selection from the dropdown menu. I passed a new function, onChange() to that event. But I'm stumped where to go from here. In the click event, I am able to use e.target to access the bounds of the selected polygon. But inside onChange(), all I have access to is the name of the selected polygon, but I don't actually have the geometry associated with that polygon. So how am I able to use the dropdown select input to select a polygon name and have the map update the polygon associated with that name? (Please note that I am hoping for a flexible response, as I would like to do more than just fitBounds() in my actual app outside of this example.)

Here is my example code:

polygons.geojson (in assets folder)

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "stroke": "#555555",
        "stroke-width": 2,
        "stroke-opacity": 1,
        "fill": "#555555",
        "fill-opacity": 0.5,
        "name": "poly1"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -121.95098876953125,
              46.82966386051541
            ],
            [
              -121.78482055664061,
              46.82966386051541
            ],
            [
              -121.78482055664061,
              46.91368905872705
            ],
            [
              -121.95098876953125,
              46.91368905872705
            ],
            [
              -121.95098876953125,
              46.82966386051541
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "stroke": "#555555",
        "stroke-width": 2,
        "stroke-opacity": 1,
        "fill": "#555555",
        "fill-opacity": 0.5,
        "name": "poly2"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -121.77726745605469,
              46.83107318799318
            ],
            [
              -121.62963867187499,
              46.83107318799318
            ],
            [
              -121.62963867187499,
              46.913220009605624
            ],
            [
              -121.77726745605469,
              46.913220009605624
            ],
            [
              -121.77726745605469,
              46.83107318799318
            ]
          ]
        ]
      }
    }
  ]
}

app.component.html

<div class="map"
  leaflet
  [leafletLayers]="layers"
     [leafletFitBounds]="fitBounds"></div>
<div class="form-group">
  <select [(ngModel)]="selected" class="form-control" id="selectRegion" [value]="clicked" (change)="onChange()">
    <option *ngFor="let region of regions">{{ region }}</option>
  </select>
</div>

app.component.ts

import {Component, NgZone, OnInit} from '@angular/core';
import { HttpClient } from '@angular/common/http';

import * as L from 'leaflet';

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

  layers: L.Layer[];
  fitBounds = [[46.67, -122.25], [47.01, -121.302]];
  regions = [];
  clicked = '';
  selected = '';

  constructor(private http: HttpClient, private zone: NgZone) { }

  ngOnInit() {

    // read in geojson of poly
    this.http.get<any>('/assets/polygons.geojson')
      .subscribe(poly => {

        const tileLayer = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png', {
          subdomains: 'abcd',
          maxZoom: 19
        });

        const polyLayer = L.geoJSON(poly, {
          onEachFeature: this.onEachFeature.bind(this)
        });

        this.layers = [ tileLayer, polyLayer ];
      });
  }

  // loop through each feature of polyLayer
  onEachFeature(feature, layer) {
    this.zone.run(() => {
      // push polygon names to regions array
      this.regions.push(feature.properties.name);

      layer.on('click', <LeafletMouseEvent> (e) => {
        this.zone.run(() => {
          this.fitBounds = e.target.getBounds();
          this.clicked = e.target.feature.properties.name;
        });
      });
    });
  }

  onChange() {
    console.log(this.selected);
  }
}

Solution

  • I was able to solve this by first initializing polyLayer as a null attribute at the top of my App Component class before constructor(). So I updated instances of polyLayer to this.polyLayer throughout the rest of the code. With this, I am now able to access the polygons inside onChange(), filter with eachLayer(), and fit the bounds of the map:

    app.component.ts update

    import {Component, NgZone, OnInit} from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    import * as L from 'leaflet';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
    
      layers: L.Layer[];
      fitBounds = [[46.67, -122.25], [47.01, -121.302]];
      regions = [];
      clicked = '';
      selected = '';
      polyLayer = null;
    
      constructor(private http: HttpClient, private zone: NgZone) { }
    
      ngOnInit() {
    
        // read in geojson of poly
        this.http.get<any>('/assets/polygons.geojson')
          .subscribe(poly => {
    
            const tileLayer = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png', {
              subdomains: 'abcd',
              maxZoom: 19
            });
    
            this.polyLayer = L.geoJSON(poly, {
              onEachFeature: this.onEachFeature.bind(this)
            });
    
            this.layers = [ tileLayer, this.polyLayer ];
          });
      }
    
      // loop through each feature of polyLayer
      onEachFeature(feature, layer) {
        this.zone.run(() => {
          // push polygon names to regions array
          this.regions.push(feature.properties.name);
    
          layer.on('click', <LeafletMouseEvent> (e) => {
            this.zone.run(() => {
              this.fitBounds = e.target.getBounds();
              this.clicked = e.target.feature.properties.name;
            });
          });
        });
      }
    
      onChange() {
    
        let that = this;
    
        // console.log(this.polyLayer._layers);
        this.polyLayer.eachLayer(function(layer){
          if(layer.feature.properties.name === that.selected){
            that.fitBounds = layer.getBounds();
          }
        });
      }
    }