2

Given two divs: a map view and a sidepanel with each row corresponding to a feature name, how can I ensure that a click event on the sidepanel row selects the corresponding feature in the map?

This is doable with popups, but I want to select the feature, not create a marker or popup. Using Angular and angular-leaflet-directive, I have this view:

<div class="preview-wrapper container-fluid">
    <div class="row">
        <div class="col-md-8">
            <leaflet id="map-preview" geojson="geojson" center="nyc"></leaflet>
        </div>
        <div class="col-md-4">
            <div class="toolbox panel panel-default">
                <div class="panel-heading">
                    <div class="panel-title text-center">Toolbox</div>
                </div>
                <div class="panel-body">
                    <div class="row" ng-repeat="segment in trails" ng-click="toolboxTrailSelect(segment)">{{ segment.properties.name }}</div>
                </div>
            </div>
        </div>
    </div>
</div>

As you can see, the ng-repeat just puts out the feature name as defined in the controller:

angular.module('dataUpload')
.controller('MapPreviewCtrl', function($scope, $log, $window, leafletData, fileReader) {
    "use strict";


    $scope.toolboxTrailSelect = function(obj) {
        $log.debug(obj);
        var latLng = new L.LatLng(obj.geometry.coordinates[0][0], obj.geometry.coordinates[0][1]);

        leafletData.getMap('map-preview').then(function(map) {
            map.fireEvent('click', {
                latlng : latLng,
                containerPoint : map.latLngToContainerPoint(latLng),
                layerPoint : map.latLngToLayerPoint(latLng)
            });
        });
    };

    leafletData.getMap('map-preview').then(function(map) {
        mapContainer = map;
        var kml = fileReader.getLocalFile();

        if (kml === null)
            $log.error("No file loaded");
        else {
            var data = getData();

            try {
                map.fitBounds(data.latlng);

                angular.extend($scope, {
                    geojson : {
                        data : data.geojson,
                        style : {
                            stroke : true,
                            weight : 5,
                            color : '#000000'
                        },
                        onEachFeature : function(feature, layer) {
                            layer.on('click', function(e) {
                                    $log.debug('Path click event: ' + e);
                                }
                            );
                        }
                    }
                });

            } catch (e) {
                $log.error(e.message);
                $window.alert(e.message);
            }
        }
    });
});

When the controller is instanciated, it adds the geometry and properties of all features to the scope, which is then iterated over in the ng-repeat directive.

Right now, a click handler is defined on each feature, and outputs a debug message. Each row in the sidepanel has a click event which is handled by toolboxTrailSelect.

The intention of this is for a click event in the sidepanel to get the first lat/lng coordinate pair and fire off a click event based on that coordinate pair. This is event is to be handled by the map feature's click event.

I get the debug message from the feature click event, and from the side panel click event. But I don't get the feature click handler debug message when the side panel click occurs.

Jason
  • 11,263
  • 21
  • 87
  • 181

1 Answers1

0

It looks like I was on the right track, but missed several key pieces. Recording the answer here in case someone else encounters the same situation.

First, include object-hash in your project. Since GeoJSON does not always contain an ID, this allows a hash value based on the feature you're mapping.

Since I need to fit the map bounds to the paths, I create an array of LatLng points to be used by map.fitBounds(). As a result, I'm able to use the feature hash and value in the scope object.

<div class="row" ng-repeat="segment in trails" 
     ng-click="toolboxTrailSelect(segment)">{{ segment.properties.name }}</div>

    $scope.toolboxTrailSelect = function(obj) {
        var hash = objectHash.sha1(obj);
        leafletData.getMap('map-preview').then(function(map) {
            map._layers[hash].fire('click');
        });
    };

The leaflet map object contains an internal value _layers which is a map of ID to features. When iterating over the feature set in

           onEachFeature : function(feature, layer) {
               layer._leaflet_id = objectHash.sha1(feature);
               layer.on('click', function(e) {
                   $log.debug('Path click event: ' + e);
               });
           }

I can add my own id to the _leaflet_id value which is then stored inside map._layers.

Now, since each feature is its own layer, all I need to do is hash the incoming object from the click event, get its corresponding layer from the map and fire a click event on it.

Jason
  • 11,263
  • 21
  • 87
  • 181