Passed
Push — master ( 0fc13e...231296 )
by Sébastien
01:32
created

app.js ➔ loadLocations   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 14
rs 9.9

1 Function

Rating   Name   Duplication   Size   Complexity  
A app.js ➔ ... ➔ $.ajax.success 0 3 1
1
/*global: $, mapboxgl, MapboxDirections, turf*/
2
3
$(window).on("scroll", function() {
4
  var scrollValue = $(window).scrollTop();
5
  if (scrollValue > 220) {
6
    $(".navbar").addClass("affix");
7
  } else {
8
    $(".navbar").removeClass("affix");
9
  }
10
});
11
12
/*Load location (stores2)*/
13
function loadLocations() {
14
  locations = null;
0 ignored issues
show
Bug introduced by
The variable locations seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.locations.
Loading history...
15
  $.ajax({
16
    async: false,
17
    global: false,
18
    url: "https://leipzig-einkaufen.de/location.json",
19
    //"url": "http://localhost/vectortiles/museen.json",
20
    dataType: "json",
21
    success: function(data) {
22
      locations = data;
0 ignored issues
show
Bug introduced by
The variable locations seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.locations.
Loading history...
23
    }
24
  });
25
  return locations;
26
}
27
28
var stores2 = loadLocations();
29
// Set bounds to Leipzig, Germany
30
var bounds = [
31
  [12.179, 51.227], // Southwest coordinates
32
  [12.6, 51.459] // Northeast coordinates
33
];
34
35
// declare map
36
var map = new mapboxgl.Map({
37
  container: "map",
38
  style: "https://leipzig-einkaufen.de/json/style-local.json",
39
  //style: "http://localhost/vectortiles/json/style-local.json",
40
41
  center: [12.3722, 51.3272],
42
  zoom: 11,
43
  attributionControl: true,
44
  hash: false,
45
  maxZoom: 14.9,
46
  maxBounds: bounds // Sets bounds as max
47
});
48
49
/*Declare MapDirections*/
50
var mapDirections = new MapboxDirections();
51
/*MapDirections Settings*/
52
mapDirections.accessToken =
53
  "pk.eyJ1Ijoic2hldWIiLCJhIjoiWGtobTNPNCJ9.v2JwlNSGBm_KxJUKE_WLig";
54
mapDirections.unit = "metric";
55
mapDirections.proximity = false; /*proximity ??*/
56
mapDirections.interactive = false;
57
mapDirections.profile = "driving"; //, "walking", "cycling";
58
// UI controls
59
mapDirections.controls = {
60
  inputs: true,
61
  instructions: false
62
};
63
64
/*Add mapDirections Controls*/
65
map.addControl(new MapboxDirections(mapDirections), "top-left");
66
67
map.addControl(
68
  new mapboxgl.ScaleControl({
69
    maxWidth: 80,
70
    unit: "metric"
71
  })
72
);
73
74
var directionControl = document.getElementsByClassName(
75
  "mapboxgl-ctrl-directions"
76
);
77
directionControl["0"].hidden = true;
78
var ptsWithin = null;
79
80
var filterEl = document.getElementById("feature-filter");
81
var listingsEl = document.getElementById("listings");
82
var txtCategoriesEl = document.getElementById("txtCategories");
83
84
// Empty Geojson Data
85
var bufferedLinestring = {
86
  id: "0",
87
  type: "Feature",
88
  geometry: {
89
    type: "Point",
90
    coordinates: [0, 0]
91
  },
92
  properties: {}
93
};
94
95
// Functions
96
function normalizeString(string) {
97
  return string.trim().toLowerCase();
98
}
99
100
function createPopUp(currentFeature) {
101
  var popup = new mapboxgl.Popup({
0 ignored issues
show
Unused Code introduced by
The variable popup seems to be never used. Consider removing it.
Loading history...
102
    closeOnClick: true
103
  })
104
    .setLngLat(currentFeature.geometry.coordinates)
105
    .setHTML(
106
      "<h3>" +
107
        currentFeature.properties.name +
108
        "</h3>" +
109
        "<h4>" +
110
        currentFeature.properties.description +
111
        "</h4>"
112
    )
113
    .addTo(map);
114
}
115
116
function getUniqueFeatures(array, comparatorProperty) {
117
  var existingFeatureKeys = {};
118
  // Because features come from tiled vector data, feature geometries may be split
119
  // or duplicated across tile boundaries and, as a result, features may appear
120
  // multiple times in query results.
121
  var uniqueFeatures = array.filter(function(el) {
122
    if (existingFeatureKeys[el.properties[comparatorProperty]])
123
    {
124
      return false;
125
    }
126
    existingFeatureKeys[el.properties[comparatorProperty]] = true;
127
    return true;
128
129
  });
130
131
  return uniqueFeatures;
132
}
133
134
function colorLocationList(data) {
135
  // Iterate through the list of stores
136
  // WITHIN THE CALCULATED ROUTE !! and color in green
137
  if (data.length) {
138
    data.forEach(function(feature) {
139
      // Shorten data.feature.properties to just `prop`.
140
      var prop = feature.properties;
141
      var cardHeader = document.getElementById("heading" + prop.id);
142
      if (cardHeader === null) {
143
        return;
144
      }
145
146
      var cardTitle = cardHeader.getElementsByClassName("title");
147
      cardTitle[0].style.color = "#608BC7";
148
    });
149
  }
150
}
151
152
function buildLocationList(data) {
153
  // Iterate through the list of stores
154
  listingsEl.innerHTML = "";
155
  if (data.length) {
156
    data.forEach(function(feature) {
157
      // Shorten feature.properties to just `prop` so we're not writing this long form over and over again.
158
      var prop = feature.properties;
159
160
      // Select the listing container in the HTML and append a div  with the class 'item' for each store
161
      var card = listingsEl.appendChild(document.createElement("div"));
162
      card.className = "item card cardList";
163
      card.id = prop.id;
164
165
      var cardHeader = card.appendChild(document.createElement("div"));
166
      cardHeader.className = "card-header";
167
      cardHeader.setAttribute("role", "tab");
168
169
      cardHeader.setAttribute("id", "heading" + card.id);
170
      cardHeader.id = "heading" + card.id;
171
172
      var cardMb0 = cardHeader.appendChild(document.createElement("h5"));
173
      cardMb0.className = "mb-0";
174
175
      // Create a new link with the class 'title' for each store and fill it with the store address
176
      var link = cardMb0.appendChild(document.createElement("a"));
177
      link.setAttribute("data-toggle", "collapse");
178
      link.href = "#collapse" + card.id;
179
      link.setAttribute("aria-expanded", "false");
180
      link.setAttribute("aria-controls", "collapse" + card.id);
181
      link.className = "title";
182
      link.textContent = prop.name;
183
      link.dataPosition = card.id;
184
185
      var cardCollapse = card.appendChild(document.createElement("div"));
186
      cardCollapse.className = "collapse";
187
      cardCollapse.setAttribute("id", "collapse" + card.id);
188
      cardCollapse.setAttribute("role", "tabpanel");
189
      cardCollapse.setAttribute("aria-labelledby", "heading" + card.id);
190
      cardCollapse.setAttribute("data-parent", "#listings");
191
192
      if (prop.image) {
193
        var cardImg = cardCollapse.appendChild(document.createElement("img"));
194
        cardImg.className = "img-responsive img-listing";
195
        cardImg.src = prop.image;
196
        cardImg.alt = prop.name;
197
        cardImg.title = prop.name;
198
      }
199
200
      var cardBody = cardCollapse.appendChild(document.createElement("div"));
201
      cardBody.className = "card-body";
202
      cardBody.textContent = prop.description;
203
      cardBody.appendChild(document.createElement("br"));
204
205
      if (prop.url) {
206
        var linkBody = cardBody.appendChild(document.createElement("a"));
207
        linkBody.textContent = prop.name;
208
        linkBody.href = prop.url;
209
        linkBody.target = "_blank";
210
        linkBody.title = prop.name;
211
      }
212
213
      // Add an event listener for the links in the sidebar listing
214
      link.addEventListener("click", function(e) {
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
215
        // Update the currentFeature to the store associated with the clicked link
216
        var clickedListing = stores2.features[this.dataPosition];
217
218
        var popUps = document.getElementsByClassName("mapboxgl-popup");
219
        // Check if there is already a popup on the map and if so, remove it
220
        if (popUps[0]) {
221
          popUps[0].parentNode.removeChild(popUps[0]);
222
        }
223
224
        // 1. Close all other popups and display popup for clicked store
225
        createPopUp(clickedListing);
226
227
        // 2. Highlight listing in sidebar (and remove highlight for all other listings)
228
        var activeItem = document.getElementsByClassName("is-active");
229
        if (activeItem[0]) {
230
          activeItem[0].classList.remove("is-active");
231
        }
232
        this.classList.add("is-active");
233
      });
234
    });
235
  } else {
236
    var empty = document.createElement("p");
237
    empty.textContent = "Ziehen Sie die Karte, um die Ergebnisse zu füllen";
238
    listingsEl.appendChild(empty);
239
240
    // remove features filter
241
    map.setFilter("locations", ["has", "Categories"]);
242
  }
243
244
  // Populate features for the listing overlay.
245
  if (ptsWithin) {
246
    colorLocationList(ptsWithin.features);
247
  }
248
}
249
250
function filterOnRoute() {
251
  var mapDirectionsSource = map.getSource("directions");
252
  var radius = 0.6;
253
  var unit = "kilometers";
254
255
  //var distDuration = mapDirections.getDistanceAndDuration();
256
257
  // buffer the route with a area of radius 'radius'
258
  if (mapDirectionsSource._data.features.length < 2) {
259
    return;
260
  }
261
  var bufferedLinestring = turf.buffer(
262
    mapDirectionsSource._data.features[2].geometry,
263
    radius,
264
    {
265
      units: unit
266
    }
267
  );
268
269
  // update bufferedTraceSource
270
  map.getSource("bufferedTraceSource").setData(bufferedLinestring);
271
272
  // Get locations rendered on the map
273
  var features = map.queryRenderedFeatures({
274
    layers: ["locations"]
275
  });
276
277
  // use featureCollection to convert features (array of features) into a collection of features (Object type FeatureCollection);
278
  var collection = turf.featureCollection(features);
279
280
  // Filter the points to the area around the direction
281
  ptsWithin = turf.pointsWithinPolygon(collection, bufferedLinestring);
282
283
  // Populate features for the listing overlay.
284
  if (ptsWithin) {
285
    buildLocationList(features);
286
  }
287
}
288
289
function displayDirectionControls() {
290
  var directionControl = document.getElementsByClassName(
291
    "mapboxgl-ctrl-directions"
292
  );
293
  if (directionControl["0"].hidden) {
294
    directionControl["0"].hidden = false;
295
    map.setLayoutProperty("bufferedTraceLayer", "visibility", "visible");
296
297
    map.setLayoutProperty("directions-origin-point", "visibility", "visible");
298
    map.setLayoutProperty(
299
      "directions-destination-point",
300
      "visibility",
301
      "visible"
302
    );
303
    map.setLayoutProperty("directions-origin-label", "visibility", "visible");
304
    map.setLayoutProperty(
305
      "directions-destination-label",
306
      "visibility",
307
      "visible"
308
    );
309
310
    map.setLayoutProperty("directions-hover-point", "visibility", "visible");
311
    map.setLayoutProperty("directions-waypoint-point", "visibility", "visible");
312
    map.setLayoutProperty("directions-route-line", "visibility", "visible");
313
    map.setLayoutProperty("directions-route-line-alt", "visibility", "visible");
314
    filterOnRoute();
315
  } else {
316
    directionControl["0"].hidden = true;
317
    // reinitialize ptsWithin
318
    ptsWithin = null;
319
320
    map.setLayoutProperty("bufferedTraceLayer", "visibility", "none");
321
    map.setLayoutProperty("directions-origin-point", "visibility", "none");
322
    map.setLayoutProperty("directions-destination-point", "visibility", "none");
323
    map.setLayoutProperty("directions-origin-label", "visibility", "none");
324
    map.setLayoutProperty("directions-destination-label", "visibility", "none");
325
326
    map.setLayoutProperty("directions-hover-point", "visibility", "none");
327
    map.setLayoutProperty("directions-waypoint-point", "visibility", "none");
328
    map.setLayoutProperty("directions-route-line", "visibility", "none");
329
    map.setLayoutProperty("directions-route-line-alt", "visibility", "none");
330
331
    var features = map.queryRenderedFeatures({
332
      layers: ["locations"]
333
    });
334
335
    if (features) {
336
      // Populate features for the listing overlay.
337
      buildLocationList(features);
338
    }
339
  }
340
}
341
342
// Call buildlist function on initialization
343
buildLocationList(stores2.features);
344
345
// Load map
346
map.on("load", function(e) {
347
  //map.loadImage('http://localhost/vectortiles/media/diagonal-noise.png', function(error, image) {
348
349
  //var storesLocations = loadStoreLocation();
350
351
  map.loadImage(
352
    "https://leipzig-einkaufen.de/media/diagonal-noise.png",
353
    function(error, image) {
354
      if (error) throw error;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
355
      map.addImage("background_pattern", image);
356
    }
357
  );
358
359
  //map.loadImage('http://localhost/vectortiles/media/Marker_with_Shadow.png', function(error, image) {
360
  map.loadImage(
361
    "https://leipzig-einkaufen.de/media/Marker_with_Shadow.png",
362
    function(error, image) {
363
      if (error) throw error;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
364
      map.addImage("marker_z", image);
365
366
      // Add the stores2 (locations_source) to the map
367
      map.addSource("locations_source", {
368
        type: "geojson",
369
        data: stores2
370
      });
371
372
      // Add the locations_source to the map as a layer
373
      map.addLayer({
374
        id: "locations",
375
        type: "symbol",
376
        // Add a GeoJSON source containing place coordinates and information.
377
        source: "locations_source",
378
        layout: {
379
          visibility: "visible",
380
          "icon-image": "marker_z",
381
          "icon-size": 0.9,
382
          "icon-allow-overlap": true
383
        }
384
      });
385
386
      // Add the bufferedLinestring to the map as a layer
387
      map.addSource("bufferedTraceSource", {
388
        type: "geojson",
389
        data: bufferedLinestring,
390
        maxzoom: 13
391
      });
392
      map.addLayer({
393
        id: "bufferedTraceLayer",
394
        type: "fill",
395
        source: "bufferedTraceSource",
396
        layout: {
397
          visibility: "visible"
398
        },
399
        paint: {
400
          "fill-color": "rgb(0,0,0)",
401
          "fill-opacity": 1,
402
          "fill-translate": [0, 2.5],
403
          "fill-pattern": "background_pattern"
404
        }
405
      });
406
407
      // Add Fullscreen control to the map.
408
      map.addControl(new mapboxgl.FullscreenControl());
409
410
      // Add geolocate control to the map.
411
      map.addControl(
412
        new mapboxgl.GeolocateControl({
413
          positionOptions: {
414
            enableHighAccuracy: true
415
          },
416
          trackUserLocation: true
417
        })
418
      );
419
420
      // When a click event occurs on a feature in the places layer, open a popup at the
421
      // location of the feature, with description HTML from its properties.
422
      map.on("click", "locations", function(e) {
423
        var currentFeature = e.features[0];
424
        // 1. Create Popup
425
        createPopUp(currentFeature);
426
427
        // 2. Highlight listing in sidebar (and remove highlight for other listing)
428
        var activeItem = document.getElementsByClassName("is-active");
429
        if (activeItem[0]) {
430
          activeItem[0].classList.remove("is-active");
431
        }
432
433
        var headingElement = document.getElementById(
434
          "heading" + currentFeature.properties.id
435
        );
436
        if (headingElement) {
437
          headingElement.classList.add("is-active");
438
        }
439
        var collapseElement = document.getElementById(
440
          "collapse" + currentFeature.properties.id
441
        );
442
        if (collapseElement) {
443
          $(collapseElement).collapse("show");
444
        }
445
      });
446
447
      map.on("moveend", function() {
448
        // Query all the rendered points in the view
449
        var features = map.queryRenderedFeatures({
450
          layers: ["locations"]
451
        });
452
453
        if (features) {
454
          //var uniqueFeatures = getUniqueFeatures(features, "Categories");
455
456
          // Populate features for the listing overlay.
457
          buildLocationList(features);
458
459
          // Clear the input container
460
          filterEl.value = "";
461
462
          // Store the current features in sn `locations_on_map` variable to later use for filtering on `keyup`.
463
          //locations = features;
464
        }
465
      });
466
467
      map.on("mousemove", "locations", function() {
468
        // Change the cursor style as a UI indicator.
469
        map.getCanvas().style.cursor = "pointer";
470
      });
471
472
      map.on("mouseleave", "locations", function() {
473
        map.getCanvas().style.cursor = "";
474
        popup.remove();
0 ignored issues
show
Bug introduced by
The variable popup seems to be never declared. If this is a global, consider adding a /** global: popup */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
475
      });
476
477
      $(".dropdown-item").click(function() {
478
        var value = normalizeString($(this).text());
479
480
        var filtered = map.querySourceFeatures("locations_source");
481
        if (value !== "alle") {
482
          // Filter visible features that don't match the input value.
483
          filtered = filtered.filter(function(feature) {
484
            var name = normalizeString(feature.properties.name);
485
            var Categories = normalizeString(feature.properties.Categories);
486
            return name.indexOf(value) > -1 || Categories.indexOf(value) > -1;
487
          });
488
        }
489
        if (!filtered) {
490
          return;
491
        }
492
493
        var uniqueFeatures = getUniqueFeatures(filtered, "Categories");
494
        // Populate the sidebar with filtered results
495
        buildLocationList(uniqueFeatures);
496
497
        // Set the filter to populate features into the layer.
498
        map.setFilter(
499
          "locations",
500
          ["in", "name"].concat(
501
            uniqueFeatures.map(function(feature) {
502
              return feature.properties.name;
503
            })
504
          )
505
        );
506
507
        txtCategoriesEl.value = value;
508
      });
509
    }
510
  );
511
});
512
513
// Direction event listener
514
mapDirections.on("route", function() {
515
  filterOnRoute();
516
});
517
518
// Display Direction
519
$("#btnDisplayControls").on("click", function() {
520
  displayDirectionControls();
521
});
522