· 4 years ago · Jul 22, 2021, 10:42 PM
1et map;
2let infoWindow;
3let jsonInput;
4let locations;
5
6var init = function() {
7 //load JSON into variable for future use
8 $.getJSON("/assets/by_location.geojson", function(json) {
9 jsonInput = json;
10 })
11
12 // map configurations
13 var options = {
14 zoom: 10,
15 center: new google.maps.LatLng(39.920233536515546, -75.17024233786381),
16 mapTypeId: 'terrain',
17 };
18
19 // create map
20 map = new google.maps.Map(document.getElementById("map"), options);
21 map.data.loadGeoJson("/assets/by_location.geojson", {idPropertyName: "ID"}, function(features) {
22 locations = getAllLocations();
23 showResults(map.data, locations);
24 });
25
26 // add infowindows
27 infoWindow = new google.maps.InfoWindow({});
28
29 map.data.addListener('click', (event) => {
30 const site = event.feature.getProperty('Site ID');
31 const address = event.feature.getProperty('Address');
32 const id = event.feature.getProperty('ID');
33 const position = event.feature.getGeometry().get();
34 const programs = event.feature.getProperty('Programs');
35 const phone = event.feature.getProperty('Site phone');
36 const website = event.feature.getProperty('Website');
37 const markerContent = `
38 <div class="infowindow">
39 <h3>${site}</h3>
40 <hr>
41 <p>${address}</p>
42 <a target="_blank" href="https://www.google.com/maps/dir/?api=1&destination=${encodeURI(address)}">Get directions</a>
43 <button id="${id}" class="more-btn btn align-self-center mx-auto mt-3">More details</button>
44 </div>
45 `;
46 infoWindow.setContent(markerContent);
47 infoWindow.setPosition(position);
48 infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
49 infoWindow.open(map);
50
51 var domReady = google.maps.event.addListenerOnce(infoWindow, 'domready', function() {
52 $(`#${id}`).on('click', function() {
53 enableToggle(map.data, id);
54 });
55 });
56 });
57
58 const input = document.getElementById("pac-input");
59 const searchBox = new google.maps.places.SearchBox(input);
60 const mapSearchOptions ={
61 types: ['address'],
62 componentRestrictions: {country: 'us'},
63 }
64
65 // Make the search bar into a Places Autocomplete search bar and select
66 // which detail fields should be returned about the place that
67 // the user selects from the suggestions.
68 const autocomplete = new google.maps.places.Autocomplete(input, mapSearchOptions);
69
70 autocomplete.setFields(
71 ['address_components', 'geometry', 'name']);
72 // Set the origin point when the user selects an address
73 const originMarker = new google.maps.Marker({map: map});
74 originMarker.setVisible(false);
75 let originLocation = map.getCenter();
76
77 autocomplete.addListener('place_changed', async () => {
78 originMarker.setVisible(false);
79 originLocation = map.getCenter();
80 const place = autocomplete.getPlace();
81
82 if (!place.geometry) {
83 // User entered the name of a Place that was not suggested and
84 // pressed the Enter key, or the Place Details request failed.
85 window.alert('No address available for input: \'' + place.name + '\'');
86 return;
87 }
88
89 // Recenter the map to the selected address
90 originLocation = place.geometry.location;
91 map.setCenter(originLocation);
92 map.setZoom(18);
93 console.log(place);
94
95 originMarker.setPosition(originLocation);
96 originMarker.setVisible(true);
97
98 // Use the selected address as the origin to calculate distances
99 // to each of the store locations
100 const rankedStores = await calculateDistances(map.data, originLocation);
101 showStoresList(map.data, rankedStores);
102
103 return;
104 });
105
106 var applyFiltersBtn = document.getElementById('apply-filters-btn');
107 applyFiltersBtn.addEventListener('click', function() {
108 map.data.loadGeoJson("/assets/by_location.geojson", {idPropertyName: "ID"}, function(features) {
109 applyFilters(map.data, getAllLocations());
110 });
111 });
112 var cancelFilters = document.getElementById('cancel-filters-btn');
113 cancelFilters.addEventListener('click', function() {
114 $('.dropdown-toggle').dropdown('toggle');
115 });
116};
117
118
119async function calculateDistances(data, origin) {
120 const stores = [];
121 const destinations = [];
122
123 // Build parallel arrays for the store IDs and destinations
124 data.forEach((store) => {
125 const storeNum = store.getProperty('storeid');
126 const storeLoc = store.getGeometry().get();
127
128 stores.push(storeNum);
129 destinations.push(storeLoc);
130 });
131
132 // Retrieve the distances of each store from the origin
133 // The returned list will be in the same order as the destinations list
134 const service = new google.maps.DistanceMatrixService();
135 const getDistanceMatrix =
136 (service, parameters) => new Promise((resolve, reject) => {
137 service.getDistanceMatrix(parameters, (response, status) => {
138 if (status != google.maps.DistanceMatrixStatus.OK) {
139 reject(response);
140 } else {
141 const distances = [];
142 const results = response.rows[0].elements;
143 for (let j = 0; j < results.length; j++) {
144 const element = results[j];
145 const distanceText = element.distance.text;
146 const distanceVal = element.distance.value;
147 const distanceObject = {
148 storeid: stores[j],
149 distanceText: distanceText,
150 distanceVal: distanceVal,
151 };
152 distances.push(distanceObject);
153 }
154
155 resolve(distances);
156 }
157 });
158 });
159
160 const distancesList = await getDistanceMatrix(service, {
161 origins: [origin],
162 destinations: destinations,
163 travelMode: 'DRIVING',
164 unitSystem: google.maps.UnitSystem.METRIC,
165 });
166
167 distancesList.sort((first, second) => {
168 return first.distanceVal - second.distanceVal;
169 });
170
171 return distancesList;
172 }
173
174 function showStoresList(data, stores) {
175 if (stores.length == 0) {
176 console.log('empty stores');
177 return;
178 }
179
180 let panel = document.createElement('div');
181 // If the panel already exists, use it. Else, create it and add to the page.
182 if (document.getElementById('panel')) {
183 panel = document.getElementById('panel');
184 // If panel is already open, close it
185 if (panel.classList.contains('open')) {
186 panel.classList.remove('open');
187 }
188 } else {
189 panel.setAttribute('id', 'panel');
190 const body = document.body;
191 body.insertBefore(panel, body.childNodes[0]);
192 }
193
194
195 // Clear the previous details
196 while (panel.lastChild) {
197 panel.removeChild(panel.lastChild);
198 }
199
200 stores.forEach((store) => {
201 // Add store details with text formatting
202 const name = document.createElement('p');
203 name.classList.add('place');
204 const currentStore = data.getFeatureById(store.storeid);
205 name.textContent = currentStore.getProperty('name');
206 panel.appendChild(name);
207 const distanceText = document.createElement('p');
208 distanceText.classList.add('distanceText');
209 distanceText.textContent = store.distanceText;
210 panel.appendChild(distanceText);
211 });
212
213 // Open the panel
214 panel.classList.add('open');
215
216 return;
217 }
218
219var showInfoPanel = function(id, data) {
220 const entry = data.getFeatureById(id);
221
222 var infoContent1 = `
223 <table>
224 <tr class="details-header">
225 <td><img class="mx-3" src="/assets/images/map-pin.svg" alt="map pin"></td>
226 <td>
227 <h2>${entry.getProperty('Site ID')}</h2>
228 </td>
229 </tr>
230 <tr>
231 <td></td>
232 <td class="details-header">
233 <hr>
234 <div class="row">
235 <div class="col-7 mb-4">
236 <p>${entry.getProperty('Address')}</p>
237 <a target="_blank" href="https://www.google.com/maps/dir/?api=1&destination=${encodeURI(entry.getProperty('Address'))}" class="get-directions">Get Directions ></a>
238 </div>
239 <div class="col-5">
240 <p class="distance">2.4 miles</p>
241 </div>
242 </div>
243 </td>
244 </tr>
245 `
246
247 var phoneContent = '';
248 if (entry.getProperty('Site phone') !== '') {
249 phoneContent = `
250 <tr>
251 <td class="m-4">
252 <img class="mx-3" src="/assets/images/phone.svg" alt="phone">
253 </td>
254 <td>
255 Phone: <span><a class="phone-link" href="tel:${entry.getProperty('Site phone')}">${entry.getProperty('Site phone')}</a></span>
256 </td>
257 </tr>
258 `
259 }
260
261 var infoContent2 = `
262 <tr>
263 <td></td>
264 <td>
265 <table class="program-table mt-3">
266 <tr>
267 <th>Level</th>
268 <th>Semester</th>
269 <th>School year</th>
270 </tr>
271 `
272
273 var programTable = '';
274 for (var program of Object.values(entry.getProperty('Programs'))) {
275 var programHTML = `
276 <tr>
277 <td>${program['Program level']}</td>
278 <td>${program['Program semester']}</td>
279 <td>${program['School year']}</td>
280 </tr>
281 `;
282 programTable += programHTML;
283 }
284 programTable += '</table></td></tr></table>';
285
286 var buttonsContent = `
287 <div class="d-flex justify-content-center my-5">
288 <button class="back-btn btn mx-2 px-4" id="back-button">Go Back</button>
289 </div>
290 `;
291 if (entry.getProperty('Website') !== '') {
292 buttonsContent = `
293 <div class="d-flex justify-content-center my-5">
294 <button class="back-btn btn mx-2 px-4" id="back-button">Go Back</button>
295 <a target="_blank" href="${entry.getProperty('Website')}" class="site-btn btn mx-2 px-4">Visit Site</a>
296 </div>
297 `;
298 }
299
300 var infoContent = infoContent1 + phoneContent + infoContent2 + programTable + buttonsContent;
301
302 var innerDiv = document.getElementById('info-content-inner');
303 innerDiv.innerHTML = infoContent;
304
305 $(`#back-button`).on('click', function() {
306 $('#info-content').hide();
307 $('#results-content').fadeIn('slow');
308 });
309}
310
311var getAllLocations = function() {
312 locations = [];
313
314 map.data.forEach((location) => {
315 const locationID = location.getProperty('ID');
316 locations.push(locationID);
317 });
318 return locations;
319}
320
321var showResults = function(data, locations) {
322 var resultsDiv = document.getElementById('results-list');
323
324 while (resultsDiv.lastChild) {
325 resultsDiv.removeChild(resultsDiv.lastChild);
326 }
327
328 if (locations.length == 0) {
329 alert('No locations found.')
330 showResults(data, getAllLocations());
331 return;
332 }
333
334 locations.forEach((location) => {
335 const currentLoc = data.getFeatureById(location);
336 const name = currentLoc.getProperty('Site ID');
337 const address = currentLoc.getProperty('Address');
338 const id = currentLoc.getProperty('ID');
339
340 const entryHTML = `
341 <table>
342 <tr>
343 <td>
344 <img class="mx-3" src="/assets/images/map-pin.svg" alt="map pin">
345 </td>
346 <td>
347 <h4>${name}</h4>
348 </td>
349 </tr>
350 <tr>
351 <td></td>
352 <td>
353 <p>${address}</p>
354 </td>
355 </tr>
356 </table>
357 `
358
359 const smallDiv = document.createElement('div');
360 smallDiv.classList.add('location-entry');
361 smallDiv.classList.add('m-3');
362 smallDiv.id = `entry-${id}`;
363
364 smallDiv.innerHTML = entryHTML;
365 resultsDiv.append(smallDiv);
366
367 $(`#entry-${id}`).on('click', function() {
368 enableToggle(data, id);
369 });
370 });
371
372 infoWindow.close();
373 specifyMapMarkers(data, locations);
374}
375
376var enableToggle = function(data, id) {
377 $('#info-content').hide();
378 $('#results-content').hide();
379 showInfoPanel(id, data);
380 $('#info-content').fadeIn();
381}
382
383var applyFilters = function(data, locations) {
384 var years = [], levels = [], semesters = [];
385
386 document.querySelectorAll('#filter-years input[type=checkbox]:checked')
387 .forEach((year) => years.push(year.name));
388 document.querySelectorAll('#filter-levels input[type=checkbox]:checked')
389 .forEach((level) => levels.push(level.name));
390 document.querySelectorAll('#filter-semesters input[type=checkbox]:checked')
391 .forEach((semester) => semesters.push(semester.name));
392
393 if (!years.length && !levels.length && !semesters.length) {
394 $('.dropdown-toggle').dropdown('toggle');
395 showResults(data, locations);
396 }
397
398 if (!years.length) years = ['2017-2018', '2018-2019', '2019-2020'];
399 if (!levels.length) levels = ['Novice', 'Startup', 'Explore', 'Seed', 'CommonSpace',
400 'Builder', 'Apprentice', 'Growth', 'Launch'];
401 if (!semesters.length) semesters = ['Spring', 'Summer', 'Fall']
402
403 var filteredLocations = [];
404
405 locations.forEach((location) => {
406 const currentLoc = data.getFeatureById(location);
407 if (includeLocation(currentLoc, years, levels, semesters)) {
408 filteredLocations.push(location);
409 }
410 });
411
412 $('.dropdown-toggle').dropdown('toggle');
413 $('#results-list').scrollTop(0);
414
415 $('.collapse').collapse('hide');
416 $('#filter-years input').prop('checked', false);
417 $('#filter-levels input').prop('checked', false);
418 $('#filter-semesters input').prop('checked', false);
419
420 locations = filteredLocations;
421 showResults(data, locations);
422}
423
424var includeLocation = function(location, years, levels, semesters) {
425 for (var program of Object.values(location.getProperty('Programs'))) {
426 if (years.includes(program['School year']) &&
427 levels.includes(program['Program level']) &&
428 semesters.includes(program['Program semester'])) {
429 return true;
430 }
431 }
432 return false;
433}
434
435var specifyMapMarkers = function(data, locations) {
436 data.forEach((feature) => {
437 if (!locations.includes(feature.getProperty('ID'))) {
438 data.remove(feature);
439 }
440 });
441}