1

I had a hard time trying to create a good title for my issue since it is a little hard to explain. But I will do my best.

I'm trying to create a web map to show three distinct layers, each from a different time period. The user choose a year and then the layer is loaded. For this to happen Geoserver is serving three layers using WFS. Now what follows is weird.

  • Sometimes when the web map is loaded, or refreshed, some layers are not encoded.
  • Sometimes layers end-up in the wrong year option.
  • But sometimes everything works, but requires that I refresh the page several times.

My code is not behaving as it should all the time. At first I thought that this should be something related to Geoserver (I posted this question a while ago following this line of though). But now I suspect that the issue is in my Javascript code, I friend of mine mentioned something relate to syncronous and asycrounouns code but I have not idea how to start debugging it.

This is my code (changed a few things to make it easier to read):

http://plnkr.co/edit/g1bHqJ1F2TcOlzieW1G8

<html>

<head>
<title>Mapping Past Denver</title>
<link rel="stylesheet" href="Plugins/leaflet-0.7.3/leaflet.css" />
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" href="Plugins/Leaflet.label-master/dist/leaflet.label.css" />
<link rel="stylesheet" href="Plugins/fullscreen/Control.FullScreen.css" />
<script src="Plugins/leaflet-0.7.3/leaflet-src.js"></script>
<script src="Plugins/jquery-1.11.2.min.js"></script>
<script src="Plugins/Leaflet.label-master/dist/leaflet.label.js"></script>
<script src="Plugins/fullscreen/Control.FullScreen.js"></script>
</head>

<body>
<div id="map">
    <script>
        $(document).ready(function () {
            var map = L.map('map', {
                fullscreenControl: true,
                fullscreenControlOptions: {
                    position: 'topleft'
                }
            }).setView([39.752171, -104.998817], 17);

            //the base map
            L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
                maxZoom: 20,
                attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
                    '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
                    'Imagery © <a href="http://mapbox.com">Mapbox</a>',
                id: 'examples.map-20v6611k'
            }).addTo(map);

            //create the datasets that will hold the layers      
            var dataset1 = new L.layerGroup();
            var dataset2 = new L.layerGroup();
            var dataset3 = new L.layerGroup();
            //JSON request 
            $.ajax({
                url: "http://localhost:8080/geoserver/PastDenver/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=PastDenver:dataset3&maxFeatures=300&outputFormat=text/javascript&format_options=callback:getJson",
                dataType: 'jsonp',
                jsonpCallback: 'getJson',
                success: function (response) {
                    WFSLayers = L.geoJson(response, {
                        style: function (feature) {
                            return {
                                weight: 5,
                                color: '#6e7ce8',
                                weight: 2,
                                opacity: 1,
                                dashArray: '3',
                                fillOpacity: 0.7,
                            };
                        },
                        onEachFeature: effects
                    }).addTo(dataset1);
                }
            });


            $.ajax({
                url: "http://localhost:8080/geoserver/PastDenver/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=PastDenver:dataset1&maxFeatures=300&outputFormat=text/javascript&format_options=callback:getJson",
                dataType: 'jsonp',
                jsonpCallback: 'getJson',
                success: function (response) {
                    WFSLayer2 = L.geoJson(response, {
                        style: function (feature) {
                            return {
                                weight: 5,
                                color: '#e31424',
                                weight: 2,
                                opacity: 1,
                                dashArray: '3',
                                fillOpacity: 0.7,
                            };
                        },
                        onEachFeature: effects
                    }).addTo(dataset2);
                }
            });


            $.ajax({
                url: "http://localhost:8080/geoserver/PastDenver/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=PastDenver:dataset2&maxFeatures=300&outputFormat=text/javascript&format_options=callback:getJson",
                dataType: 'jsonp',
                jsonpCallback: 'getJson',
                success: function (response) {
                    WFSLayer3 = L.geoJson(response, {
                        style: function (feature) {
                            return {
                                weight: 5,
                                color: '#14e324',
                                weight: 2,
                                opacity: 1,
                                dashArray: '3',
                                fillOpacity: 0.7,
                            };
                        },
                        onEachFeature: effects
                    }).addTo(dataset3);
                }
            });


            //hover effect and popup effect
            function effects(feature, layer) {
                popupOptions = {
                    maxWidth: 200
                };
                if (feature.properties.name !== null) {
                    layer.bindLabel(feature.properties.name, popupOptions, {
                        noHide: true
                    });
                };
                layer.bindPopup("<b>Name: </b>" + feature.properties.name + "<br><b>Description: </b>" + feature.properties.descr + "<br><b>Floors: </b>" + feature.properties.floors + "<br><b>Material: </b>" + feature.properties.material);
                layer.on({
                    click: zoomToFeature
                });
            };


            //zoom to feature
            function zoomToFeature(e) {
                map.fitBounds(e.target.getBounds());
            }

            //group the datasets
            var overlayData = {
                "1887": dataset1,
                "1925": dataset2,
                "1961": dataset3
            };


            //create the layer control
            L.control.layers(overlayData, null, {
                collapsed: false
            }).addTo(map);

        });
    </script>
</div>


I did some quick research and it seems that the part that may causing issue is the AJAX call, more and more I'm suspecting that this a case of asynchronous code. Still, I haven't found a clear way to fix it. Also, I don't understand why my ajax calls are putting the data in different variables (sometime 1887 data is attributed to dataset3).


It seems that the issue is indeed AJAX making the requests at the same time. But yet, I haven't find a clear way to fix this issue. Probably deferred objects is the best solution here but I'm finding trouble applying it to the code.

2

After seeking for a more broad (jQuery and AJAX related) answer here I assigned to my jsonpCallbacks unique values, like the following:

//JSON request 
                $.ajax({
                    url: "http://localhost:8080/geoserver/PastDenver/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=PastDenver:dataset3&maxFeatures=300&outputFormat=text/javascript&format_options=callback:getJson1",
                    dataType: 'jsonp',
                    jsonpCallback: 'getJson1',
                    success: function (response) {
                        WFSLayers = L.geoJson(response, {
                            style: function (feature) {
                                return {
                                    weight: 5,
                                    color: '#6e7ce8',
                                    weight: 2,
                                    opacity: 1,
                                    dashArray: '3',
                                    fillOpacity: 0.7,
                                };
                            },
                            onEachFeature: effects
                        }).addTo(dataset1);
                    }
                });

For each layer I assigned a specific callback name. Now everything seems to be working just fine, doesn't matter hoe many time I refresh the page, no errors are thrown into the console.

I will make this an answer since it solved my problem, not sure if this is a logical fix or not though.

1

Two pieces of advice (which may or may not constitute an answer--haven't tested your code myself):

Use Promise Objects

Available within jQuery (as well as many other js libraries). Read the docs: essentially, a promise object allows you to work with variables that point to data that may still be loading asynchronously.

E.g.

var promiseData = $.Deferred();

$.ajax(url, {
  success: function(data){
    promiseData.resolve(data);
    // optionally, do other stuff with the data once it's fully loaded
  },
  error: function(jqXHR, textStatus, errorThrown){
    promiseData.reject(errorThrown);
  }
});

To interact with the promise object outside of the $.ajax() success callback, use the .done() handler:

promiseData.done(function(data){
  console.log(data);
});

Abstract Code into Functions

There's a lot of different ways to do this, but if you see yourself writing the same code over and over, chances are you could encapsulate that logic once in a function that you call with different argument parameters.

One approach would be something like:

var loadData = function(url, styleFunction){
  var wfsLayer = $.Deferred();
  $.ajax({
    url: url,
    dataType: 'jsonp',
    jsonpCallback: 'getJson',
    success: function(data){
      wfsLayer = L.geojson(data, {
        style: styleFunction,
        onEachFeature: function(feature, layer){
          <insert onEachFeature code>
        }
      }).addTo(dataSet1);
    }
  });

  return promiseData;
};

Then call the function with the correct arguments

var firstDataSet = loadData('http://localhost:8080...', function(feature){
  return {
    weight: 5,
    color: '#6e7ce8',
    opacity: 1
  };
});

and because we're working with promise objects, if you need to interact with firstDataSet, use the .done() handler.

There are likely still bugs in the code that this doesn't address, but organizing your code better should make it a lot easier to spot them.

  • Thanks for the info! So if I want to manipulate the data once it is loaded outside the ajax call I add it to the dataset using the .done handler? How can I add the Json to a data set using the .done handler? – Ricardo Oliveira Feb 25 '15 at 18:20
  • By 'dataset' you mean a L.layerGroup object? In that case, probably best to create the object within the ajax success callback: promiseData.resolve(L.layerGroup(data)); . It's entirely possible that you will never need to use the .done() handler. The two safe places to interact with asynchronously loaded data are either w/i the success callback defined within the ajax call, or outside of the ajax call and within the .done() handler. Try rewriting your code and share as a plunker for specific feedback. – James Conkling Feb 25 '15 at 21:50
  • I did some re-arranging in the code, here is the latest format plnkr.co/edit/g1bHqJ1F2TcOlzieW1G8 I couldn't quite understand how to applied deferred objects in it, but it seems that this is easiest way to fix the bug. What I don't understand is why data that is supposed to got to a specif var ends in another. – Ricardo Oliveira Feb 26 '15 at 19:25
0

Your map is being shown in the browser before all the GeoJSON is loaded into your layers.

Try wrapping your JavaScript with jQuery's ready() method.

$(document).ready(function(){

  <put all your code in here>

 }); 
  • I did what you suggested and the error persists. Not sure if in the same degree of occurrence. Should I post the code updated? – Ricardo Oliveira Feb 24 '15 at 17:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.