How to show / hide polylines with markers in leaflet

Viewed 909

With leaflet 1.6.0 and I make listing of locations(markers and polylines) grouped by country and I need to make filtering by country, when clicking on country button . I use layerGroup and managed to show markers and polylines with method like:

    drawGroupedAdLocationsMarkers() {
        let polylinePoints = []  // I get all info about all Polylines
        let loop_index = 0

        this.groupedAdLocations.forEach(nextGroupedAdLocations => { // draw all groupedAdLocations
            this.groupedCountriesList[this.groupedCountriesList.length] = {
                key: nextGroupedAdLocations.country,
                label: this.countriesList[nextGroupedAdLocations.country],
            }
            let markersList = []
            let polylinesList = []
            nextGroupedAdLocations.adLocations.forEach(nextAddLocation => { // draw all nextAddLocation
                let priorPoint = null // eslint-disable-line
                let priorMarker = null // eslint-disable-line
                if (loop_index > 0) {
                    priorPoint = this.groupedAdLocations[loop_index - 1]
                    priorMarker= nextMarker
                }
                polylinePoints[polylinePoints.length] = [nextAddLocation.lat, nextAddLocation.lng]
                let nextMarker= this.showLocationMarker(nextAddLocation)
                markersList[markersList.length] = nextMarker
                polylinesList[polylinesList.length] = this.showLocationDirections(polylinePoints, nextGroupedAdLocations.country)
                loop_index++
            }) // nextGroupedAdLocations.adLocations.forEach(nextAddLocation => { // draw all nextAddLocation

            polylinesList.map((nextPolyline) => {
                markersList.push(nextPolyline);
            });

            let newMarkersLayerGroup = this.leaflet.layerGroup(markersList).addTo(this.locationsMap);
            this.layerGroupsMarkersArray[this.layerGroupsMarkersArray.length] = {
                country: nextGroupedAdLocations.country,
                layersObj: newMarkersLayerGroup
            }
        }) // this.groupedAdLocations.forEach(nextGroupedAdLocations => { // draw all groupedAdLocations


        let radius = 10;
        let polyline = new this.leaflet.Polyline(polylinePoints, {
            color: 'green',
            opacity: 1,
            weight: 2,
            customData: {
                type:'polyline'
                // point_id: point.id,
                // prior_point_id: priorPoint ? priorPoint.id : null,
            },
            offset: radius
        });
        polyline.on('click', function (event) {
            event.stopPropagation();
            window.event.cancelBubble = true;
            // showModal(event)
            // alert('Polyline clicked!');
        });
        // Add polyline to featuregroup
        polyline.addTo(this.locationsMap);
    }, // drawGroupedAdLocationsMarkers() {

and methods for markers/polylines creation :

    showLocationMarker(nextAddLocation) {
        let icon_size = 32
        if (nextAddLocation.featured) {
            icon_size = 48
        }
        var markerIcon = this.leaflet.icon({
            iconUrl: (!nextAddLocation.featured ? '/images/location.png' : '/images/location_featured.png'),
            iconSize: [icon_size, icon_size], // size of the icon
            // shadowSize:   [50, 64], // size of the shadow
            iconAnchor: [icon_size, icon_size], // point of the icon which will correspond to marker's location
            // shadowAnchor: [4, 62],  // the same for the shadow
            popupAnchor: [0, 0] // point from which the popup should open relative to the iconAnchor
        });

        let nextMarker = this.leaflet.marker(
            [nextAddLocation.lat, nextAddLocation.lng],
                {
                    icon: markerIcon,
                    customData:{add_location_id: nextAddLocation.id,type:'marker'}
                })
            .addTo(this.locationsMap)
            .bindPopup(nextAddLocation.content)
            .on('mouseover', this.locationMarkerOnMouseOver)
            .on('click', this.locationMarkerOnClick)
            .on('popupopen', this.locationMarkerOnPopupOpen)

        // circleMarker

        if (nextAddLocation.featured) {
            nextMarker.bindTooltip("Featured Location").openTooltip();
        }

        let self = this
        this.locationsMap.on('zoomend', function (/*e*/) {

            self.current_zoom = self.locationsMap.getZoom()
        });

        if (nextAddLocation.opened) {
            nextMarker.openPopup()
        }
        return nextMarker
    }, // showLocationMarker(nextAddLocation) {

    showLocationDirections(polylinePoints, country) {
        let radius = 10;
        let polyline = new this.leaflet.Polyline(polylinePoints, {
            color: 'green',
            opacity: 1,
            weight: 2,
            customData: {
                type:'polyline'
                // point_id: point.id,
                // prior_point_id: priorPoint ? priorPoint.id : null,
            },
            offset: radius
        });
        // Add click listener
        polyline.on('click', function (event) {
            event.stopPropagation();
            window.event.cancelBubble = true;
        });
        // Add polyline to featuregroup
        polyline.addTo(this.locationsMap);
        let decorator = this.leaflet.polylineDecorator(polyline, { // eslint-disable-line
            patterns: [
                // defines a pattern of 10px-wide dashes, repeated every 20px on the line
                {
                    offset: 0,
                    repeat: 50,
                    symbol: this.leaflet.Symbol.arrowHead({
                        pixelSize: 10,
                        polygon: false,
                        pathOptions: {stroke: true}
                    })
                }
            ]
        }).addTo(this.locationsMap)

        this.locationsMap.fitBounds(polyline.getBounds());
        return polyline;
    },  // showLocationDirections(polylinePoints) {

as result I see map with markers/polylines : https://prnt.sc/t1751f

clicking on method for filters markers are hidden/shown but polylines are always visible with method :

filterByGroupedCountry(country_key, country_label) {
    let self = this
    this.layerGroupsMarkersArray.forEach(nextLayerMarkerGroup => { // draw all layerGroupsMarkersArray
        if (nextLayerMarkerGroup.country === country_key) {
            let layersObj = nextLayerMarkerGroup.layersObj

            if (self.locationsMap.hasLayer(layersObj)) {
                self.locationsMap.removeLayer(layersObj);
            } else {
                self.locationsMap.addLayer(layersObj);
            }
            return
        }
    }) // this.layerGroupsMarkersArray.forEach(nextLayerMarkerGroup => { // draw all layerGroupsMarkersArray

Is it incorrect way to push to markers array all polylines, as I wrote above:

...
polylinesList.map((nextPolyline) => {
    markersList.push(nextPolyline);
});

let newMarkersLayerGroup = this.leaflet.layerGroup(markersList).addTo(this.locationsMap);
...

Which way is correct ?

BLOCK # 2:

I remade, and it works ok if I have only 1 group of data. But it works in wrong way if I have more 1 groups. Say if I have 2 coutry groups with set of locations for any countries on page opened(after drawGroupedAdLocationsMarkers was run) I see markers with polylines shown ok. When I click to hide 1st country group(method filterByGroupedCountry) only markers are hidden, but polylines and decorators are still visible. When I click to hide 2nd(last) country group all markers polylines and decorators are hidden. I suppose that was a wrong way to create LayerGroup with one array

       let newMarkersLayerGroup = this.leaflet.layerGroup(markersList); 

if I added to markersList all polylines and decorators, but which is the valid way ?

        drawGroupedAdLocationsMarkers() {
            let polylinePoints = []  // I get all info about all Polylines
            let loop_index = 0

            this.groupedCountriesList= []
            this.groupedAdLocations.forEach(nextGroupedAdLocations => { // draw all groupedAdLocations
                this.groupedCountriesList[this.groupedCountriesList.length] = {   // keep list of countries for filtering countries list
                    key: nextGroupedAdLocations.country,
                    label: this.countriesList[nextGroupedAdLocations.country],
                }
                let markersList = []
                let polylinesList = []
                let decoratorsList = []  // init markers, polylines, decorators Lists withing one country group
                nextGroupedAdLocations.adLocations.forEach(nextAdLocation => { // draw all adLocations inside of one country group
                    let priorPoint = null // eslint-disable-line
                    let priorMarker = null // eslint-disable-line
                    if (loop_index > 0) {
                        priorPoint = this.groupedAdLocations[loop_index - 1]
                        priorMarker= nextMarker
                    }
                    polylinePoints[polylinePoints.length] = [nextAdLocation.lat, nextAdLocation.lng]

                    // add new marker and add it to markersList
                    let nextMarker= this.showLocationMarker(nextAdLocation, nextGroupedAdLocations.country)
                    markersList[markersList.length] = nextMarker

                    let radius = 10; // Add new polyline based on point of nextAdLocation
                    let polyline = new this.leaflet.Polyline(polylinePoints, {
                        color: 'green',
                        opacity: 1,
                        weight: 2,
                        customData: {
                            add_location_id: nextAdLocation.id,
                            type:'polyline',
                            country:nextGroupedAdLocations.country
                        },
                        offset: radius
                    });
                    polyline.on('click', function (event) {
                        event.stopPropagation();
                        window.event.cancelBubble = true;
                    });
                    // polyline.addTo(this.locationsMap);

                    // add new decorator for polyline created above
                    let decorator = this.leaflet.polylineDecorator(polyline, { // eslint-disable-line
                        patterns: [
                            // defines a pattern of 10px-wide dashes, repeated every 20px on the line
                            {
                                offset: 0,
                                repeat: 50,
                                symbol: this.leaflet.Symbol.arrowHead({
                                    pixelSize: 10,
                                    polygon: false,
                                    pathOptions: {stroke: true},
                                    customData: {
                                        add_location_id: nextAdLocation.id,
                                        type:'polyline',
                                        country:nextGroupedAdLocations.country
                                    },
                                })
                            }
                        ]
                    })
                    // decorator.addTo(this.locationsMap)

                    this.locationsMap.fitBounds(polyline.getBounds());
                    // add created polyline to polylinesList
                    polylinesList[polylinesList.length] = polyline

                    // add created decorator to decoratorsList
                    decoratorsList[decoratorsList.length] = decorator
                    loop_index++
                }) // nextGroupedAdLocations.adLocations.forEach(nextAdLocation => { // draw all adLocations inside of one country group

                polylinesList.map((nextPolyline) => {
                    markersList.push(nextPolyline);
                });
                decoratorsList.map((nextDecorator) => {
                    markersList.push(nextDecorator);
                });

                // create layer Group with polylinesList, markersList and decoratorsList
                let newMarkersLayerGroup = this.leaflet.layerGroup(markersList); 
                this.locationsMap.addLayer(newMarkersLayerGroup);
                this.layerGroupsMarkersArray[this.layerGroupsMarkersArray.length] = {
                    country: nextGroupedAdLocations.country,
                    layersObj: newMarkersLayerGroup
                }

            }) // this.groupedAdLocations.forEach(nextGroupedAdLocations => { // draw all groupedAdLocations

        }, // drawGroupedAdLocationsMarkers() {

        showLocationMarker(nextAdLocation, country) {
            let icon_size = 32
            if (nextAdLocation.featured) {
                icon_size = 48
            }
            var markerIcon = this.leaflet.icon({
                iconUrl: (!nextAdLocation.featured ? '/images/location.png' : '/images/location_featured.png'),
                iconSize: [icon_size, icon_size], // size of the icon
                // shadowSize:   [50, 64], // size of the shadow
                iconAnchor: [icon_size, icon_size], // point of the icon which will correspond to marker's location
                // shadowAnchor: [4, 62],  // the same for the shadow
                popupAnchor: [0, 0] // point from which the popup should open relative to the iconAnchor
            });

            let nextMarker = this.leaflet.marker(
                [nextAdLocation.lat, nextAdLocation.lng],
                    {
                        icon: markerIcon,
                        customData:{
                            add_location_id: nextAdLocation.id,
                            type:'marker',
                            country:country
                        }
                    })
                .addTo(this.locationsMap)
                .bindPopup(nextAdLocation.content)
                .on('mouseover', this.locationMarkerOnMouseOver)
                .on('click', this.locationMarkerOnClick)
                .on('popupopen', this.locationMarkerOnPopupOpen)

            if (nextAdLocation.featured) {
                nextMarker.bindTooltip("Featured Location").openTooltip();
            }

            let self = this
            this.locationsMap.on('zoomend', function (/*e*/) {
                self.current_zoom = self.locationsMap.getZoom()
            });

            if (nextAdLocation.opened) {
                nextMarker.openPopup()
            }
            return nextMarker
        }, // showLocationMarker(nextAdLocation) {


        filterByGroupedCountry(country_key, country_label) {
            let self = this
            this.layerGroupsMarkersArray.forEach(nextLayerMarkerGroup => { // draw all layerGroupsMarkersArray
                if (nextLayerMarkerGroup.country === country_key) {
                    console.log('FOUND country_key::')
                    console.log(country_key)
                    let layersObj = nextLayerMarkerGroup.layersObj
                    console.log(0)
                    if (self.locationsMap.hasLayer(layersObj)) {
                        console.log(-1)
                        self.locationsMap.removeLayer(layersObj);
                    } else {
                        console.log(-2)
                        self.locationsMap.addLayer(layersObj);
                    }
                    return
                }
            }) // this.layerGroupsMarkersArray.forEach(nextLayerMarkerGroup => { // draw all layerGroupsMarkersArray

        }

BLOCK # 3: I made online demo Please open http://ads.my-demo-apps.tk/login Credentials are already in inputs. Just click “Login” After that redirect to http://ads.my-demo-apps.tk/test2 You must see map with some points/polylines and listing of 4 countries below Try to click on countries one by one. You will see as relative markers are hidden( or shown again if to click on counries link again ): https://prnt.sc/t8dsxb But polylines are not hidden as I expected

Click all countries - then all are hidden. I gave code description in BLOCK # 2:

Thanks!

1 Answers

There is an logic error in your code, which is actually quite simple to fix.
Looking on your jsfiddle example one can see that the same polylines are added multiple times, actually each time markers of another country are created.
What needs to be done is to reinitialize polylinePoints variable with an empty array for each outer loop execution. You can checkout it out in this jsfiddle.

this.groupedAdLocations.forEach(nextGroupedAdLocations => { 
    let markersList = []
    let polylinesList = []
    polylinePoints = [] // THIS was missing
    nextGroupedAdLocations.adLocations.forEach(nextAdLocation => {
        ...

If you want cities from different countries to also be connected, then you need to adjust this solution and make sure you don't add the same polyline mutliple times.

BUT and there is a very important but. As @IvanSanchez has already mentioned in his comment this code is very, very, very convoluted. It is hard to read, it is full of mistakes and really error-prone.
If you do not work alone and there is a software engineer with more experience, then please ask him for help. They could and should help you with refactoring this code.

I'll just name couple of examples:

  • Using Array.forEach with custom loop_index++ logic. Instead of simply using second parameter of forEach callback function.
  • polylinesList.map((nextPolyline) => { markersList.push(nextPolyline);});
    Calling map, where there is no mapping happening and forEach or simple loop is more appropriate.
  • polylinesList[polylinesList.length] = polyline. Maybe in JS it is just a matter of taste, but I would simply use push function here.
  • Very inefficient and confusing design of data structures. Having same data in multiple places, making this redundancy error-prone. It'd be much better to have a single source of truth.
  • Last, but not least - this code is really convoluted.

Take care and keep on learning :)

Related