Search code examples
javascriptloopsopenlayers

Openlayers: Using for loop for click events with multiple maps/views on single page


I'm trying to put together a page that has several maps (one that contains all of a group of routes and one each for each route), each in a different (Bootstrap) tab. I got it to work with the most repetitive code, by repeating and independently assigning to each map the click and pointermove events I would like, but ideally I would assign these in some kind of loop.

My attempt is what I've pasted below, which works, but only for the final map - if I change the order of the maps any of them will work, but only so long as it is the last. I wasn't able to find much direction on what I'm looking for, this other question being the closest, which mentions the same issue of only working on the last map, though I wasn't able to make much use of the answer provided. Is there a way to loop through these events and have them work over multiple views/maps?

Any direction is greatly appreciated!

Javascript:

//----------/MAP 0/----------//

var myView0 = new ol.View({
    center: ol.proj.fromLonLat([-87.068669, 8.470072]),
    zoom: 3,
});

var map0 = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        }),
        vectorLayerJNY10,
        vectorLayerJNY11,
        vectorLayerJNY12,
        hoverLayers['route'],
    ],
    target: 'map0',
    view: myView0,
});

//----------/MAP 1/----------//

var myView1 = new ol.View({
    center: ol.proj.fromLonLat([-94.803044, 36.719405]),
    zoom: 3.5,
});

var map1 = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        }),
        vectorLayerJNY10,
        hoverLayers['route'],
    ],
    target: 'map1',
    view: myView1,
});

//----------/MAP 2/----------//

var myView2 = new ol.View({
    center: ol.proj.fromLonLat([-114.778097, 32.698996]),
    zoom: 4,
});

var map2 = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        }),
        vectorLayerJNY11,
        hoverLayers['route'],
    ],
    target: 'map2',
    view: myView2
});

//----------/MAP 3/----------//

var myView3 = new ol.View({
    center: ol.proj.fromLonLat([-93.988196, 19.253431]),
    zoom: 4 
});

var map3 = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        }),
        vectorLayerJNY12,
        hoverLayers['route'],
    ],
    target: 'map3',
    view: myView3
});

var maps = [map0, map1, map2, map3];
var views = [myView0, myView1, myView2, myView3]

var mapOnClick = function (event) {
    if (event.map.hasFeatureAtPixel(event.pixel) === true) {
        var name = event.map.forEachFeatureAtPixel(event.pixel, function(feature) {
            return feature.get('name');
        })
        var coordinate = event.coordinate;
        content.innerHTML = name;
        olay.setPosition(coordinate);
    } else {
        olay.setPosition(undefined);
        closer.blur();
    }
};

var elmHover = function(evt) {
    if (evt.dragging) {
        return;
    }
    var pixel = evt.map.getEventPixel(evt.originalEvent);
    var map = elem;
    hover(pixel, map);
};

var cursor = function(evt) {
    elem.getTargetElement().style.cursor = elem.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
};

var mapResize = function(event){
    setTimeout(function() {
        elem.updateSize();
    }, 200);
};

for(var i = 0; i < maps.length; i++){
    var elem = maps[i];

    var container = document.getElementById('popup' + i);
    var content = document.getElementById('popup-content' + i);
    var closer = document.getElementById('popup-closer' + i);

    var overlays = [];
    overlays[i] = new ol.Overlay({
        element: container,
        autoPan: true,
        autoPanAnimation: {
            duration: 250
        }
    });
    elem.addOverlay(overlays[i]);

    olay = overlays[i];
    view = views[i];

    elem.on('pointermove', elmHover);

    elem.on('singleclick', mapOnClick);

    elem.on('pointermove', cursor);

    closer.onclick = function() {
        layover.setPosition(undefined);
        closer.blur();
        return false;
    };

    $('.nav-tabs').on('shown.bs.tab', mapResize)

    $('.nav-tabs').on('hide.bs.tab', function(event){
        elem.setView(view);
        olay.setPosition(undefined);
    });
}

HTML (just in case :3):

<ul class="nav nav-tabs nav-justified" role="tablist">
    <li class="nav-item" >
        <a class="nav-link active" data-toggle="tab" href="#About">About</a>
    </li>
    <li class="nav-item" >
        <a class="nav-link" data-toggle="tab" href="#Journey-I">Journeys I</a>
    </li>
    <li class="nav-item" >
        <a class="nav-link" data-toggle="tab" href="#Journey-II">Journey II</a>
    </li>
    <li class="nav-item" >
        <a class="nav-link" data-toggle="tab" href="#Journey-III">Journey III</a>
    </li>
</ul>

<div class="tab-content">
    <div id="About" class="tab-pane fade show active" role="tabpanel">
        <h3>About</h3>

        <div id="map0" class="map"></div>

        <div id="popup0" class="ol-popup">
            <a href="#" id="popup-closer0" class="ol-popup-closer"></a>
            <div id="popup-content0"></div>
        </div>

    </div>

    <div id="Journey-I" class="tab-pane fade" role="tabpanel">
        <h3>Journey I</h3>

        <div id="map1" class="map"></div>

        <div id="popup1" class="ol-popup">
            <a href="#" id="popup-closer1" class="ol-popup-closer"></a>
            <div id="popup-content1"></div>
        </div>

    </div>

    <div id="Journey-II" class="tab-pane fade" role="tabpanel">
        <h3>Journey II</h3>

        <div id="map2" class="map"></div>

        <div id="popup2" class="ol-popup">
            <a href="#" id="popup-closer2" class="ol-popup-closer"></a>
            <div id="popup-content2"></div>
        </div>

    </div>

    <div id="Journey-III" class="tab-pane fade" role="tabpanel">
        <h3>Journey III</h3>

        <div id="map3" class="map"></div>

        <div id="popup3" class="ol-popup">
            <a href="#" id="popup-closer3" class="ol-popup-closer"></a>
            <div id="popup-content3"></div>
        </div>

    </div>

    <script src="{{ url_for('static', filename='JS/OLmap_ajall.js') }}" type="text/javascript"></script>

</div>

Solution

  • Like was said in the question you check, it is a problem of context.

    In the handlers of every event, you are using variables that take value on the loop, elem, olay, view. The problem is that when the events occur the value of these variables reference the last map, overlay, and view of their respective array.

    There are several ways to solve this. I will propose you to use bind method of function (JavaScript - function bind).

    var mapOnClick = function (event) {
        if (this.map.hasFeatureAtPixel(event.pixel) === true) {
            var name = this.map.forEachFeatureAtPixel(event.pixel, function(feature) {
                return feature.get('name');
            })
            var coordinate = event.coordinate;
            this.content.innerHTML = name;
            this.overlay.setPosition(coordinate);
        } else {
            this.overlay.setPosition(undefined);
            this.closer.blur();
        }
    };
    
    var elmHover = function(evt) {
        if (evt.dragging) {
            return;
        }
        var pixel = this.map.getEventPixel(evt.originalEvent);
        // var map = elem; <- what is this for?
        hover(pixel, this.map); // <- this function is not defined, look out
    };
    
    var cursor = function(evt) {
        this.map.getTargetElement().style.cursor = 
            this.map.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
    };
    
    var mapResize = function(event){
        var self = this;
        setTimeout(function() {
            self.map.updateSize();
        }, 200);
    };
    
    var closerOnClick = function() {
        this.overlay.setPosition(undefined); // I am guessing is overlay
        this.closer.blur();
        return false;
    }
    
    var onHide = function(event){
        this.map.setView(this.view); // <- don't understand this
        this.overlay.setPosition(undefined);
    }
    
    var contexts = [
        { map: map0, view: myView0 },
        { map: map1, view: myView1 },
        { map: map2, view: myView2 },
        { map: map3, view: myView3 }
    ];
    
    for(var i = 0; i < contexts.length; i++){
        contexts[i].container = document.getElementById('popup' + i);
        contexts[i].content = document.getElementById('popup-content' + i);
        contexts[i].closer = document.getElementById('popup-closer' + i);
        contexts[i].overlay = new ol.Overlay({
            element: contexts[i].container,
            autoPan: true,
            autoPanAnimation: {
                duration: 250
            }
        });
        contexts[i].map.addOverlay(contexts[i].overlay);
        contexts[i].mapOnClick = mapOnClick.bind(contexts[i]);
        contexts[i].elmHover = elmHover.bind(contexts[i]);
        contexts[i].cursor = cursor.bind(contexts[i]);
        contexts[i].mapResize = mapResize.bind(contexts[i]);
    
        contexts[i].map.on('singleclick', contexts[i].mapOnClick);
        contexts[i].map.on('pointermove', contexts[i].cursor);
        contexts[i].closer.onclick = contexts[i].closerOnClick;
    
        // don't understand why these are in the loop
        $('.nav-tabs').on('shown.bs.tab', contexts[i].mapResize);
        $('.nav-tabs').on('hide.bs.tab', contexts[i].onHide);
    }
    

    Note: There were several things that I did not understand of your code, plus you were initializing the overlays array in each iteration, and you have wrong variables names. It was long code, did not check it, so be aware there might be some issues. Is just for you to make the idea.

    I noted that you are using jQuery, you could also use proxy to keep context. Although I notice that it is deprecated.