0
votes

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);
  }
}
2

2 Answers

1
votes

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();
      }
    });
  }
}
0
votes

If you want to bind the object for the select input, you should use [ngValue] to explicitly set the value object:

<div class="map"
     leaflet
     [leafletLayers]="layers"
     [leafletFitBounds]="fitBounds"></div>

<div class="form-group">
  <select class="form-control" id="selectRegion"
          (change)="onChange()"
          [(ngModel)]="selected" >
    <option *ngFor="let region of regions"
            [ngValue]="region">{{ region }}</option>
  </select>
</div>

But, you still have the issue that now you have to set selected to the region object when you click on the map. So, you might need to change how your map click code works to ensure you can get a reference to the right object when you click the map.