PageSourceSearch

https://schuylkillriver.org/wp-content/plugins/wp_trailpress/public/js/script.js?ver=1713989008

js schuylkillriver.org collected 2026-09-24 09:53:07 UTC 72,234 bytes, 2,447 lines download raw bytes

1var map = null;
2
3jQuery(document).ready(function($) {
4	if ($('#TP_Map').length > 0) {
5		map = new TPMap('TP_Map', 'map-canvas');
6	}
7});
8
9jQuery(function($) {
10	$('body').on('click', '.toggle-next', function(e) {
11		// console.log('toggle');
12		$(this).toggleClass('open');
13		$(this)
14			.next('.toggle-me')
15			.slideToggle();
16	});
17
18	$('#TP_Map').on('click', '.infowindow a.btn', function(e) {
19		// console.log('click');
20		e.preventDefault();
21		if ($(this).data('term')) {
22			var term = $(this).data('term');
23			$.getJSON(wpRestAPI.root + 'trailpress/v1/town/' + term, function(data) {
24				var html = data.html;
25
26				$('.results-wrap').addClass('open');
27				$('.map-results.level.level1').addClass('open');
28				$('.map-results.level.level1 .result').html(html);
29			});
30		} else {
31			var url = btoa($(this).attr('href'));
32			$.getJSON(wpRestAPI.root + 'trailpress/v1/poi/' + url, function(data) {
33				var html = data.html;
34
35				$('.results-wrap').addClass('open');
36				$('.map-results.level.level1').addClass('open');
37				$('.map-results.level.level1 .result').html(html);
38			});
39		}
40	});
41	$('#TP_Map').on('click', '.trigger-sidebar', function(e) {
42		// console.log('click');
43		e.preventDefault();
44
45		if ($(this).data('term')) {
46			var term = $(this).data('term');
47			map.selectShapebyID(term);
48			$.getJSON(wpRestAPI.root + 'trailpress/v1/town/' + term, function(data) {
49				var html = data.html;
50
51				$('.results-wrap').addClass('open');
52				$('.map-results.level.level1').addClass('open');
53				$('.map-results.level.level1 .result').html(html);
54			});
55		} else {
56			var url = btoa($(this).attr('href'));
57			var id = $(this).data('id');
58			map.selectShapebyID(id);
59			$.getJSON(wpRestAPI.root + 'trailpress/v1/poi/' + url, function(data) {
60				var html = data.html;
61
62				$('.results-wrap').addClass('open');
63				$('.map-results.level.level1').addClass('open');
64				$('.map-results.level.level1 .result').html(html);
65			});
66		}
67	});
68	$('.result-content a.back').on('click', function(e) {
69		e.preventDefault();
70		$('.results-wrap').removeClass('open');
71		$('.map-results.level.level1').removeClass('open');
72	});
73
74	$('.map-key .button-wrap button').on('mouseover', function(e) {
75		$('#itin-dropdown .select2-container').css('width', '248px');
76		$('[data-panel]').removeClass('show');
77		$('button.arrow').removeClass('arrow');
78		var show = this.dataset.show;
79		var button = $(this);
80		var panels = document.querySelectorAll('[data-panel=' + show + ']');
81		button.addClass('arrow');
82		panels.forEach(function(panel) {
83			$(panel).addClass('show');
84			$(panel).on('mouseleave', function(e) {
85				if ($(this).find('.select2-container--open').length > 0) return;
86				button.removeClass('arrow');
87				$(this).removeClass('show');
88			});
89		});
90		/*
91        if( $(this).hasClass('icon-map-refresh') ) {
92            e.preventDefault();
93        }
94        if( $(this).hasClass('icon-map-help') ) {
95            $(this).toggleClass('arrow').closest('.map-key').find('.key-details').toggleClass('show');
96        }
97        if( $(this).hasClass('tpicon-Attributes') ) {
98            $(this).toggleClass('arrow').closest('.map-key').find('.trail-attributes').toggleClass('show');
99        }
100        if( $(this).hasClass('icon-plan-trip') ) {
101            $(this).closest('.button-wrap').find('.icon-map-help').addClass('arrow');
102            $(this).closest('.map-key').find('.key-details').addClass('show');
103
104            setTimeout(function(){
105                $('.plan-trip-container select').first().select2('open');
106            },1000);
107        }
108        */
109	});
110
111	$('.key-details .close-key').on('click', function() {
112		$(this)
113			.closest('.key-details')
114			.toggleClass('show');
115		$(this)
116			.closest('.map-key')
117			.find('.icon-map-help')
118			.toggleClass('arrow');
119	});
120
121	$('.map-title .close-flyout').on('click', function() {
122		$(this).addClass('hide');
123		if ($('.results-wrap').hasClass('open')) {
124			$('.results-wrap span').text('<');
125		} else {
126			$('.results-wrap span').text('>');
127		}
128
129		if ($('.results-wrap').hasClass('open')) {
130			$('.results-wrap').removeClass('open');
131		}
132
133		$('.map-results.level1').removeClass('open');
134	});
135
136	$('.results-wrap .map-toggle').on('click', function() {
137		if ($('.results-wrap').hasClass('open')) {
138			$(this)
139				.find('span')
140				.text('<');
141		} else {
142			$(this)
143				.find('span')
144				.text('>');
145		}
146
147		$(this)
148			.closest('.results-wrap')
149			.toggleClass('open');
150		if (
151			$(this)
152				.closest('.results-wrap')
153				.hasClass('open')
154		) {
155			$('.close-flyout').removeClass('hide');
156		} else {
157			$('.close-flyout').addClass('hide');
158		}
159	});
160
161	$('.plan-trip-container select:first-of-type')
162		.select2()
163		.on('select2:select', function(e) {
164			var mile = $(this)
165				.find(':selected')
166				.data('mile');
167
168			$(this)
169				.siblings('select')
170				.find('option')
171				.each(function() {
172					if (mile && parseFloat($(this).data('mile')) >= parseFloat(mile)) {
173						$(this).attr('disabled', 'disabled');
174					} else {
175						$(this).removeAttr('disabled');
176					}
177				});
178		});
179	$('.plan-trip-container select:nth-of-type(2)')
180		.select2()
181		.on('select2:select', function(e) {
182			var mile = $(this)
183				.find(':selected')
184				.data('mile');
185
186			$(this)
187				.siblings('select')
188				.find('option')
189				.each(function() {
190					if (mile && $(this).data('mile') <= mile) {
191						$(this).attr('disabled', 'disabled');
192					} else {
193						$(this).removeAttr('disabled');
194					}
195				});
196		});
197
198	$('.results-wrap section h3').on('click', function() {
199		$(this)
200			.closest('section')
201			.toggleClass('active')
202			.siblings()
203			.removeClass('active');
204	});
205
206	$('button.tp-plan-trip-now').on('click', function(e) {
207		var world = this;
208		e.preventDefault();
209		world.start = $('.plan-trip-container select.trailhead-start').val();
210		world.end = $('.plan-trip-container select.trailhead-end').val();
211
212		if (!world.start || !world.end) return;
213
214		// ajax = $.ajax({
215		//     type     : "POST",
216		//     url      : bloginfo.ajaxurl,
217		//     dataType : 'json',
218		//     data     : {
219		//         action  : 'plan_trip',
220		//         start   : start,
221		//         end     : end
222		//     },
223		//     success: function(data) {
224		//         if( typeof console!=="undefined" ) console.log( data );
225		//     },
226		//     error: function(jqXHR, textStatus, errorThrown){
227		//         if( typeof console!=="undefined" ) console.log( textStatus );
228		//     }
229		// });
230
231		$.magnificPopup.open({
232			items: {
233				src: bloginfo.ajaxurl,
234			},
235			type: 'ajax',
236			ajax: {
237				settings: {
238					type: 'POST',
239					url: bloginfo.ajaxurl,
240					dataType: 'json',
241					data: {
242						action: 'tp_plan_itin',
243						start: world.start,
244						end: world.end,
245						map: map.target.dataset.map,
246					},
247				},
248			},
249			removalDelay: 300,
250			// mainClass: 'mfp-fade',
251			mainClass: 'plan-trip-popup',
252			callbacks: {
253				open: function() {
254					$('body').addClass('plan-trip-popup-open');
255					$.magnificPopup.instance.close = function() {
256						$('body').removeClass('plan-trip-popup-open');
257						$.magnificPopup.proto.close.call(this);
258					};
259				},
260				parseAjax: function(mfpResponse) {
261					// mfpResponse.data is a "data" object from ajax "success" callback
262					// for simple HTML file, it will be just String
263					// You may modify it to change contents of the popup
264					// For example, to show just #some-element:
265					// mfpResponse.data = $(mfpResponse.data).find('#some-element');
266
267					// mfpResponse.data must be a String or a DOM (jQuery) element
268
269					mfpResponse.data = mfpResponse.data.data.content;
270				},
271				ajaxContentAdded: async function() {
272					$('.plan-trip-results a.expand').on('click', function(e) {
273						e.preventDefault();
274						$('.directions .amenities ul').toggleClass('long');
275						if (
276							$('.directions .amenities ul')
277								.first()
278								.hasClass('long')
279						) {
280							$(this).html($(this).data('expand'));
281						} else {
282							$(this).html($(this).data('collapse'));
283						}
284					});
285
286					var itinmap = new TPMap('TP_Itin_Map', 'itin-canvas');
287
288					itinmap.mode = 'nopopup';
289
290					var line = $('#TP_Itin_Map').data('line');
291					var trip = await itinmap.drawItinerary([world.start, world.end], line);
292					var total = trip.reduce(function(acc, val) {
293						return acc + val;
294					}, 0);
295					// $('.stats > h5').html(total.toFixed(1)+'<span>miles</span>');
296					// $('.trailpoints.end h5').html(total.toFixed(1)+'<span>mi</span>');
297					if (total > 13) $('.plan-trip-results .notice').show();
298				},
299			},
300		});
301	});
302	$('button.tp-pick-itin-now').on('click', function(e) {
303		e.preventDefault();
304		var itin = $('.plan-trip-container select.itinerary').val();
305
306		if (!itin) return;
307
308		// ajax = $.ajax({
309		//     type     : "POST",
310		//     url      : bloginfo.ajaxurl,
311		//     dataType : 'json',
312		//     data     : {
313		//         action  : 'plan_trip',
314		//         start   : start,
315		//         end     : end
316		//     },
317		//     success: function(data) {
318		//         if( typeof console!=="undefined" ) console.log( data );
319		//     },
320		//     error: function(jqXHR, textStatus, errorThrown){
321		//         if( typeof console!=="undefined" ) console.log( textStatus );
322		//     }
323		// });
324
325		$.magnificPopup.open({
326			items: {
327				src: bloginfo.ajaxurl,
328			},
329			type: 'ajax',
330			ajax: {
331				settings: {
332					type: 'POST',
333					url: bloginfo.ajaxurl,
334					dataType: 'json',
335					data: {
336						action: 'tp_plan_itin',
337						itin: itin,
338						map: map.target.dataset.map,
339					},
340				},
341			},
342			removalDelay: 300,
343			// mainClass: 'mfp-fade',
344			mainClass: 'plan-trip-popup',
345			callbacks: {
346				open: function() {
347					$('body').addClass('plan-trip-popup-open');
348					$.magnificPopup.instance.close = function() {
349						$('body').removeClass('plan-trip-popup-open');
350						$.magnificPopup.proto.close.call(this);
351					};
352				},
353				parseAjax: function(mfpResponse) {
354					// mfpResponse.data is a "data" object from ajax "success" callback
355					// for simple HTML file, it will be just String
356					// You may modify it to change contents of the popup
357					// For example, to show just #some-element:
358					// mfpResponse.data = $(mfpResponse.data).find('#some-element');
359
360					// mfpResponse.data must be a String or a DOM (jQuery) element
361
362					mfpResponse.data = mfpResponse.data.data.content;
363				},
364				ajaxContentAdded: async function() {
365					$('.plan-trip-results a.expand').on('click', function(e) {
366						e.preventDefault();
367						$('.directions .amenities ul').toggleClass('long');
368						if (
369							$('.directions .amenities ul')
370								.first()
371								.hasClass('long')
372						) {
373							$(this).html($(this).data('expand'));
374						} else {
375							$(this).html($(this).data('collapse'));
376						}
377					});
378
379					var itinmap = new TPMap('TP_Itin_Map', 'itin-canvas');
380					await itinmap.getItinerary();
381					var total = itinmap.trip.reduce(function(acc, val) {
382						return acc + val;
383					}, 0);
384					$('.stats > h5').html(total.toFixed(1) + '<span>miles</span>');
385					$('.trailpoints.end h5').html('<strong>' + total.toFixed(1) + '</strong><span>mi</span>');
386					if (total > 13) $('.plan-trip-results .notice').show();
387				},
388			},
389		});
390	});
391
392	$('button.tp-pick-itin-loop-now').on('click', function(e) {
393		e.preventDefault();
394		var itin = $('.plan-trip-container select.itinerary-loop').val();
395
396		if (!itin) return;
397
398		// ajax = $.ajax({
399		//     type     : "POST",
400		//     url      : bloginfo.ajaxurl,
401		//     dataType : 'json',
402		//     data     : {
403		//         action  : 'plan_trip',
404		//         start   : start,
405		//         end     : end
406		//     },
407		//     success: function(data) {
408		//         if( typeof console!=="undefined" ) console.log( data );
409		//     },
410		//     error: function(jqXHR, textStatus, errorThrown){
411		//         if( typeof console!=="undefined" ) console.log( textStatus );
412		//     }
413		// });
414
415		$.magnificPopup.open({
416			items: {
417				src: bloginfo.ajaxurl,
418			},
419			type: 'ajax',
420			ajax: {
421				settings: {
422					type: 'POST',
423					url: bloginfo.ajaxurl,
424					dataType: 'json',
425					data: {
426						action: 'tp_plan_itin',
427						itin: itin,
428						map: map.target.dataset.map,
429					},
430				},
431			},
432			removalDelay: 300,
433			// mainClass: 'mfp-fade',
434			mainClass: 'plan-trip-popup',
435			callbacks: {
436				open: function() {
437					$('body').addClass('plan-trip-popup-open');
438					$.magnificPopup.instance.close = function() {
439						$('body').removeClass('plan-trip-popup-open');
440						$.magnificPopup.proto.close.call(this);
441					};
442				},
443				parseAjax: function(mfpResponse) {
444					// mfpResponse.data is a "data" object from ajax "success" callback
445					// for simple HTML file, it will be just String
446					// You may modify it to change contents of the popup
447					// For example, to show just #some-element:
448					// mfpResponse.data = $(mfpResponse.data).find('#some-element');
449
450					// mfpResponse.data must be a String or a DOM (jQuery) element
451
452					mfpResponse.data = mfpResponse.data.data.content;
453				},
454				ajaxContentAdded: async function() {
455					$('.plan-trip-results a.expand').on('click', function(e) {
456						e.preventDefault();
457						$('.directions .amenities ul').toggleClass('long');
458						if (
459							$('.directions .amenities ul')
460								.first()
461								.hasClass('long')
462						) {
463							$(this).html($(this).data('expand'));
464						} else {
465							$(this).html($(this).data('collapse'));
466						}
467					});
468
469					var itinmap = new TPMap('TP_Itin_Map', 'itin-canvas');
470					await itinmap.getItinerary();
471
472					var total = itinmap.trip.reduce(function(acc, val) {
473						return acc + val;
474					}, 0);
475					$('.stats > h5').html(total.toFixed(1) + '<span>miles</span>');
476					$('.trailpoints.end h5').html('<strong>' + total.toFixed(1) + '</strong><span>mi</span>');
477					if (total > 13) $('.plan-trip-results .notice').show();
478				},
479			},
480		});
481	});
482
483	$('.map-results').on('click', '.open-cesium-modal', function(e) {
484		// console.log('cesium!');
485		e.preventDefault();
486		var lat = $(this).data('lat');
487		var lng = $(this).data('lng');
488		var kml = $(this).data('kml');
489		var title = $(this).data('title');
490
491		$.magnificPopup.open({
492			items: {
493				src: bloginfo.ajaxurl,
494			},
495			type: 'ajax',
496			ajax: {
497				settings: {
498					type: 'POST',
499					url: bloginfo.ajaxurl,
500					dataType: 'json',
501					data: {
502						action: 'tp_cesium_modal',
503						lat: lat,
504						lng: lng,
505						kml: kml,
506						title: title,
507					},
508				},
509			},
510			removalDelay: 300,
511			closeOnContentClick: false,
512			// mainClass: 'mfp-fade',
513			mainClass: 'plan-trip-popup',
514			callbacks: {
515				open: function() {
516					$('body').addClass('plan-trip-popup-open');
517					$.magnificPopup.instance.close = function() {
518						$('body').removeClass('plan-trip-popup-open');
519						$.magnificPopup.proto.close.call(this);
520					};
521				},
522				parseAjax: function(mfpResponse) {
523					// mfpResponse.data is a "data" object from ajax "success" callback
524					// for simple HTML file, it will be just String
525					// You may modify it to change contents of the popup
526					// For example, to show just #some-element:
527					// mfpResponse.data = $(mfpResponse.data).find('#some-element');
528
529					// mfpResponse.data must be a String or a DOM (jQuery) element
530
531					mfpResponse.data = mfpResponse.data.data.content;
532				},
533				ajaxContentAdded: async function() {},
534			},
535		});
536	});
537
538	$('.trip-considerations').on('click', function(e) {
539		e.preventDefault();
540
541		$.magnificPopup.open({
542			items: {
543				src: bloginfo.ajaxurl,
544			},
545			type: 'ajax',
546			ajax: {
547				settings: {
548					type: 'POST',
549					url: bloginfo.ajaxurl,
550					dataType: 'json',
551					data: {
552						action: 'tp_trip_considerations',
553					},
554				},
555			},
556			removalDelay: 300,
557			closeOnContentClick: false,
558			// mainClass: 'mfp-fade',
559			mainClass: 'plan-trip-popup',
560			callbacks: {
561				open: function() {
562					$('body').addClass('plan-trip-popup-open');
563					$.magnificPopup.instance.close = function() {
564						$('body').removeClass('plan-trip-popup-open');
565						$.magnificPopup.proto.close.call(this);
566					};
567				},
568				parseAjax: function(mfpResponse) {
569					// mfpResponse.data is a "data" object from ajax "success" callback
570					// for simple HTML file, it will be just String
571					// You may modify it to change contents of the popup
572					// For example, to show just #some-element:
573					// mfpResponse.data = $(mfpResponse.data).find('#some-element');
574
575					// mfpResponse.data must be a String or a DOM (jQuery) element
576
577					// console.log(mfpResponse.data.data.content);
578
579					mfpResponse.data = mfpResponse.data.data.content;
580				},
581				ajaxContentAdded: async function() {},
582			},
583		});
584	});
585});
586
587class TPMap {
588	constructor(selector, canvas) {
589		var world = this;
590		this.featurekml = null;
591		this.zoomlevel = 13;
592		this.icons = this.getIcons();
593		this.places = [];
594		this.towns = [];
595		this.superlayer = [];
596		this.filtered = [];
597		this.target = document.getElementById(selector);
598		this.mapelem = document.getElementById(canvas);
599		this.mode = 'static';
600		this.selectmode = 'single';
601		this.drawingManager = false;
602		this.map = new google.maps.Map(this.mapelem, {
603			zoom: 10,
604			center: new google.maps.LatLng(40.323446, -75.64131),
605			disableDefaultUI: true,
606			zoomControl: true,
607			zoomControlOptions: {
608				position: google.maps.ControlPosition.LEFT_BOTTOM,
609			},
610		});
611		this.dataLayer = null;
612		this.itemList = [];
613		this.count = 0;
614		this.selectedShapes = [];
615		this.polys = [];
616		this.stickypolyline = {};
617		this.stickypolylinearray = [];
618		this.linesegment = {
619			start: {},
620			end: {},
621		};
622		/*
623    this.texteditor = {
624      title : document.getElementById('tp_input_title'),
625      excerpt : document.getElementById('tp_input_excerpt'),
626      layer:  document.getElementById('tp_select_layer'),
627      more : document.getElementById('tp_checkbox_more'),
628      lock : document.getElementById('tp_checkbox_lock_feature'),
629      link : document.getElementById('tp_edit_link'),
630      icon : document.getElementById('tp_select_icon'),
631      iconSize: document.getElementById('tp_icon_size'),
632      iconColor: document.getElementById('tp_icon_color'),
633      iconColorAlpha: document.getElementById('tp_icon_color_alpha'),
634      iconBackground: document.getElementById('tp_icon_bg_color'),
635      iconBackgroundAlpha: document.getElementById('tp_icon_bg_color_alpha'),
636      iconPadding: document.getElementById('tp_icon_padding'),
637      lineWeight:  document.getElementById('tp_line_weight'),
638      lineColor: document.getElementById('tp_line_color'),
639      lineColorAlpha: document.getElementById('tp_line_color_alpha'),
640      outlineWeight:  document.getElementById('tp_outline_weight'),
641      outlineColor: document.getElementById('tp_outline_color'),
642      outlineColorAlpha: document.getElementById('tp_outline_color_alpha'),
643      fillColor: document.getElementById('tp_fill_color'),
644      fillColorAlpha: document.getElementById('tp_fill_color_alpha'),
645    };
646    */
647
648		/*
649    this.layerEditor = {
650      id: document.getElementById('tp_layer_id'),
651      title: document.getElementById('tp_layer_name'),
652      icon: document.getElementById('tp_layer_icon'),
653      color: document.getElementById('tp_layer_color'),
654      default: document.getElementById('tp_layer_default')
655    }
656    */
657		this.icons = [];
658		this.selectedIcon = null;
659		this.layers = {};
660		this.sortable = false;
661		this.hiddenlayers = [];
662		if (this.target.dataset.itin) {
663		} else {
664			Array.from(document.getElementsByClassName('trailpress_poi_type_check')).forEach(function(layer) {
665				layer.addEventListener('click', event => world.toggleLayer(event, layer));
666				if (!layer.checked) {
667					world.hiddenlayers.push(parseInt(layer.dataset.id));
668				}
669			});
670			Array.from(document.getElementsByClassName('trailpress_poi_attribute_check')).forEach(function(layer) {
671				layer.addEventListener('click', event => world.triggerFilter(event, layer));
672			});
673			//document.getElementsByClassName('tpicon-WaterTrailLandings')[0].addEventListener("click", (event) => world.toggleLandings(event));
674			document.getElementsByClassName('icon-map-trailtowns')[0].addEventListener('click', event => world.toggleTowns(event));
675			document.getElementsByClassName('icon-map-places')[0].addEventListener('click', event => world.togglePlaces(event));
676
677			Array.from(document.getElementsByClassName('tpicon-highlight')).forEac
677h(function(button) {
678				if (button.classList.contains('active')) {
679					button.style.backgroundColor = button.dataset.highlight;
680				}
681				button.style.borderColor = button.dataset.highlight;
682				button.addEventListener('click', event => world.toggleLayerGroup(event, button));
683			});
684		}
685
686		if (this.target.dataset.itin) {
687			this.mode = 'nopopup';
688			this.start();
689		} else {
690			this.start();
691		}
692		this.buildInfoWindow();
693
694		google.maps.event.addListener(this.map, 'zoom_changed', function() {
695			var zoom = world.map.getZoom();
696
697			if (zoom < world.zoomlevel) {
698				world.itemList.forEach(function(item) {
699					if (typeof item.marker !== 'undefined') {
700						item.marker.hide();
701						item.setVisible(true);
702					}
703				});
704			} else {
705				world.itemList.forEach(function(item) {
706					if (typeof item.marker !== 'undefined') {
707						item.marker.show();
708						item.setVisible(false);
709					}
710				});
711			}
712		});
713	}
714	async start() {
715		await this.getLayers();
716		await this.getPOIs();
717		this.getTowns();
718		this.getPlacesToVisit();
719	}
720	triggerFilter(event, layer) {
721		var world = this;
722		var filters = [];
723		world.filtered.forEach(function(item) {
724			if (!item) return;
725			item.setMap(world.map);
726		});
727		world.filtered = [];
728
729		Array.from(document.getElementsByClassName('trailpress_poi_attribute_check')).forEach(function(checkbox) {
730			if (checkbox.checked) {
731				filters.push(parseInt(checkbox.dataset.id));
732			}
733		});
734		if (filters.length < 1) return false;
735		Object.keys(world.layers).forEach(function(layer) {
736			if (typeof world.layers[layer] == 'undefined') return;
737			if (world.layers[layer].meta.featureType != 'supertype') return;
738			if (world.layers[layer].name != 'Water Trail Landing') return;
739			world.layers[layer].features.forEach(function(feature) {
740				var amenarray = [];
741				amenarray = feature.amenities.map(a => a.term_id);
742				if (!arrayContainsArray(amenarray, filters)) {
743					var shape = world.getShapebyID(feature.ID);
744					if (shape.getMap() == null) return;
745					world.filtered.push(shape);
746					shape.setMap(null);
747				}
748			});
749		});
750	}
751	toggleLayerGroup(event, button) {
752		// console.log('click');
753		var world = this;
754		var listid = 'group-' + button.dataset.type;
755		var list = document.getElementById(listid);
756
757		if (!list) {
758			world.toggleLayer(event, button);
759		} else {
760			Array.prototype.forEach.call(list.children, function(child, index) {
761				var checkbox = child.querySelector('input');
762				var active = button.classList.contains('active');
763				world.toggleLayer(event, checkbox, active);
764				if (active) {
765					checkbox.disabled = true;
766				} else {
767					checkbox.disabled = false;
768				}
769			});
770		}
771		if (button.classList.contains('active')) {
772			button.classList.remove('active');
773			button.style.backgroundColor = null;
774		} else {
775			button.classList.add('active');
776			button.style.backgroundColor = button.dataset.highlight;
777		}
778		return;
779	}
780	/*
781  toggleLandings(event) {
782    var button = event.target;
783    var world = this;
784    if (button.classList.contains('active')) {
785      button.classList.remove('active');
786      this.superlayer.forEach(function(layer) {
787        var items = world.layers[layer];
788        var features = items.features;
789        if (typeof features == 'undefined') return;
790        features.forEach(function(item) {
791          var shape = world.getShapebyID(item.ID);
792          shape.setMap(null);
793        });
794      });
795    } else {
796      button.classList.add('active');
797      this.superlayer.forEach(function(layer) {
798        var items = world.layers[layer];
799        console.log(items);
800        var features = items.features;
801        if (typeof features == 'undefined') return;
802        features.forEach(function(item) {
803          var shape = world.getShapebyID(item.ID);
804          shape.setMap(world.map);
805        });
806      });
807    }
808
809
810
811    if (button.classList.contains('active')) {
812      button.classList.remove('active');
813      this.places.forEach(function(place) {
814        place.setMap(null);
815      })
816    } else {
817      button.classList.add('active');
818      this.places.forEach(function(place) {
819        place.setMap(world.map);
820      })
821    }
822    var world = this;
823    var items = this.layers[layer.dataset.id];
824    if (typeof items == 'undefined') return;
825    var features = items.features;
826    if (typeof features == 'undefined') return;
827    features.forEach(function(item) {
828      var shape = world.getShapebyID(item.ID);
829      if (layer.checked == true) {
830          shape.setMap(world.map);
831          if (shape.marker) {
832            shape.marker.setMap(world.map);
833          }
834      } else {
835          shape.setMap(null);
836          shape.marker.setMap(null);
837      }
838    })
839
840  }
841  */
842	togglePlaces(event) {
843		var button = event.target;
844		var world = this;
845		if (button.classList.contains('active')) {
846			button.classList.remove('active');
847			this.places.forEach(function(place) {
848				place.setMap(null);
849			});
850		} else {
851			button.classList.add('active');
852			this.places.forEach(function(place) {
853				place.setMap(world.map);
854			});
855		}
856	}
857
858	toggleTowns(event) {
859		var button = event.target;
860		var world = this;
861		if (button.classList.contains('active')) {
862			button.classList.remove('active');
863			this.towns.forEach(function(place) {
864				place.setMap(null);
865			});
866		} else {
867			button.classList.add('active');
868			this.towns.forEach(function(place) {
869				place.setMap(world.map);
870			});
871		}
872	}
873	/*
874  SidePanelToggle() {
875    var panel = document.getElementById("tp_side_panel");
876
877    panel.classList.toggle('show');
878  }
879  LayerPanelToggle() {
880    var panel = document.getElementById("tp_poi_list_panel");
881    this.putIconsInSelect(this.layerEditor.icon, null, 'tp_layer_radio_', false);
882    //this.getPOIList();
883    panel.classList.toggle('show');
884  }
885  ClosePanel(e) {
886    e.target.parentNode.classList.toggle('show');
887  }
888  */
889
890	toggleLayer(event, layer, disable = null) {
891		var world = this;
892		var items = this.layers[layer.dataset.id];
893		if (typeof items == 'undefined') return;
894		var features = items.features;
895		if (typeof features == 'undefined') return;
896		features.forEach(function(item) {
897			var shape = world.getShapebyID(item.ID);
898			if (typeof shape == 'undefined') return;
899
900			var makeactive = false;
901			if (disable === true) {
902				makeactive = false;
903			} else if (layer.checked === true) {
904				makeactive = true;
905			} else if (layer.checked === false) {
906				makeactive = false;
907			} else if (layer.classList.contains('active')) {
908				makeactive = false;
909			} else {
910				makeactive = true;
911			}
912			if (makeactive) {
913				shape.setMap(world.map);
914				if (shape.marker) {
915					shape.marker.setMap(world.map);
916				}
917			} else {
918				shape.setMap(null);
919				if (shape.marker) {
920					shape.marker.setMap(null);
921				}
922			}
923		});
924	}
925
926	async objectToGeoJson(event) {
927		var json = {};
928		this.dataLayer = new google.maps.Data();
929		switch (event.type) {
930			case google.maps.drawing.OverlayType.MARKER:
931				this.dataLayer.add(
932					new google.maps.Data.Feature({
933						geometry: new google.maps.Data.Point(event.overlay.getPosition()),
934					})
935				);
936				break;
937			case google.maps.drawing.OverlayType.POLYGON:
938				this.dataLayer.add(
939					new google.maps.Data.Feature({
940						geometry: new google.maps.Data.Polygon([event.overlay.getPath().getArray()]),
941					})
942				);
943				break;
944			case google.maps.drawing.OverlayType.POLYLINE:
945				this.dataLayer.add(
946					new google.maps.Data.Feature({
947						geometry: new google.maps.Data.LineString(event.overlay.getPath().getArray()),
948					})
949				);
950				break;
951		}
952		var dataLayer = this.dataLayer;
953		json.json = await this.LayertoJSON(dataLayer);
954
955		return json;
956	}
957
958	async polyToGeoJson(poly) {
959		var json = {};
960		this.dataLayer = new google.maps.Data();
961		if (poly instanceof google.maps.Polygon) {
962			this.dataLayer.add(
963				new google.maps.Data.Feature({
964					geometry: new google.maps.Data.Polygon([poly.getPath().getArray()]),
965				})
966			);
967		} else if (poly instanceof google.maps.Marker) {
968			this.dataLayer.add(
969				new google.maps.Data.Feature({
970					geometry: new google.maps.Data.Point(poly.getPosition()),
971				})
972			);
973		} else if (poly instanceof google.maps.Polyline) {
974			this.dataLayer.add(
975				new google.maps.Data.Feature({
976					geometry: new google.maps.Data.LineString(poly.getPath().getArray()),
977				})
978			);
979		}
980
981		json.json = await this.LayertoJSON(this.dataLayer);
982
983		return json;
984	}
985
986	stickyMarkerCallback(e, poly) {
987		//console.log(poly);
988		var point = this.findClosestPointOnPath(e.latLng, this.generateIntermediatePointsOnPolyline(poly.stickyParent, poly));
989		poly.setPosition(point);
990
991		if (poly.marker) {
992			poly.marker.setOverlayPosition(point);
993		}
994	}
995
996	setMarker(poly, icon, map = this.map, caution = false) {
997		var html = icon.html;
998
999		if (typeof poly.marker !== 'undefined') {
1000			// console.log(poly.marker);
1001			poly.marker.onRemove();
1002		}
1003		if (caution) {
1004			var chtml = caution.html;
1005			chtml = [chtml.slice(0, 13), 'caution-level ', chtml.slice(13)].join('');
1006			html = chtml + html;
1007		}
1008		//console.log(html);
1009		poly.meta.iconRGBA = this.hexToRGB(poly.meta.iconColor, parseInt(poly.meta.iconColorAlpha) * 0.01);
1010		poly.marker = new StaticOverlay(map, poly.getPosition(), html, poly);
1011	}
1012
1013	getShapebyID(id) {
1014		var target = this.itemList.find(o => o.id === id);
1015		return target;
1016	}
1017	selectShapebyID(id) {
1018		var target = this.itemList.find(o => o.id === id);
1019		// console.log(id, target);
1020		var bounds = new google.maps.LatLngBounds();
1021		var world = this;
1022		if (target instanceof google.maps.Marker) {
1023			this.map.setZoom(13);
1024			this.map.panTo(target.getPosition());
1025		} else {
1026			target.getPath().forEach(function(e) {
1027				bounds.extend(e);
1028				world.map.fitBounds(bounds);
1029			});
1030		}
1031		this.selectShape(null, target);
1032	}
1033	async selectShape(event, shape) {
1034		/*
1035    this.selectedIcon = null;
1036
1037    if (typeof shape.meta !== 'undefined' && shape.meta.icon ) {
1038      document.getElementById('tp_radio_'+shape.meta.icon).checked = true;
1039
1040      if (this.icons.length < 1) {
1041        this.icons = await this.getIcons();
1042      }
1043      this.selectedIcon = this.icons.find(o => o.slug === shape.meta.icon);
1044    }
1045    */
1046
1047		Array.from(document.getElementsByClassName('tp_sidebar_input_group')).forEach(function(panel) {
1048			panel.classList.remove('current');
1049		});
1050		if (typeof this.target.dataset.poi !== 'undefined') {
1051			if (shape.id != this.target.dataset.poi) return;
1052		}
1053		switch (this.selectmode) {
1054			case 'single':
1055				this.deselectShape();
1056				this.selectedShapes.push(shape);
1057
1058				break;
1059
1060			case 'multi':
1061				document.getElementById('tp_marker_group').classList.add('current');
1062				document.getElementById('tp_line_group').classList.add('current');
1063				document.getElementById('tp_poly_group').classList.add('current');
1064				if (this.selectedShapes.find(o => o.id === shape.id)) {
1065					this.selectedShapes = this.selectedShapes.filter(function(value, index, arr) {
1066						return value != shape;
1067					});
1068					this.setOpacity(shape, 0.5);
1069				} else {
1070					this.selectedShapes.push(shape);
1071
1072					this.setOpacity(shape, 1);
1073				}
1074				break;
1075		}
1076	}
1077
1078	setOpacity(shape, opacity) {
1079		if (typeof shape.setOpacity === 'function') {
1080			shape.setOpacity(opacity);
1081		} else {
1082			shape.setOptions({ strokeOpacity: opacity });
1083		}
1084	}
1085	deselectShape() {
1086		var world = this;
1087
1088		this.selectmode = 'single';
1089		this.polys.forEach(function(poly) {
1090			var ops = [];
1091			if (parseInt(poly.meta.lineColorAlpha) > 0) {
1092				ops.push(parseInt(poly.meta.lineColorAlpha));
1093			}
1094			if (parseInt(poly.meta.iconAlpha) > 0) {
1095				ops.push(parseInt(poly.meta.iconAlpha));
1096			}
1097			if (parseInt(poly.meta.fillColorAlpha) > 0) {
1098				ops.push(parseInt(poly.meta.fillColorAlpha));
1099			}
1100
1101			world.setOpacity(poly, (ops.length > 0 ? Math.min(...ops) : 100) * 0.01);
1102		});
1103		if (this.selectedShapes.length < 1) return;
1104		if (typeof this.selectedShapes[0].setEditable === 'function') {
1105			this.selectedShapes[0].setEditable(false);
1106		} else {
1107			this.selectedShapes[0].setDraggable(false);
1108		}
1109		this.selectedShapes = [];
1110	}
1111
1112	setCenter() {
1113		var center = this.map.getCenter();
1114		document.getElementById('TPCenterLat').value = center.lat();
1115		document.getElementById('TPCenterLng').value = center.lng();
1116	}
1117
1118	setZoom() {
1119		var zoomLevel = this.map.getZoom();
1120		document.getElementById('TPCenterZoom').value = zoomLevel;
1121	}
1122
1123	setSideBar(item) {
1124		var sidebar = document.getElementById('mapSideBar');
1125		sidebar.classList.add('show');
1126
1127		sidebar.innerHTML =
1128			'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc id cursus metus aliquam eleifend. Ornare lectus sit amet est placerat in. Dolor sed viverra ipsum nunc aliquet bibendum enim. Congue nisi vitae suscipit tellus mauris a diam. Massa massa ultricies mi quis hendrerit dolor magna eget. Nec ullamcorper sit amet risus nullam eget felis eget nunc. Sit amet est placerat in egestas erat. Eu turpis egestas pretium aenean pharetra. Dignissim cras tincidunt lobortis feugiat vivamus at augue eget. Amet justo donec enim diam vulputate. Cursus euismod quis viverra nibh. Non curabitur gravida arcu ac tortor dignissim. Gravida quis blandit turpis cursus in hac habitasse platea dictumst. Cursus eget nunc scelerisque viverra mauris in aliquam sem fringilla. Ornare quam viverra orci sagittis eu. Donec adipiscing tristique risus nec feugiat in fermentum posuere. Eget aliquet nibh praesent tristique magna sit amet purus gravida. Sed odio morbi quis commodo. Varius quam quisque id diam vel quam elementum pulvinar etiam. Nisi est sit amet facilisis magna etiam tempor orci eu. Amet dictum sit amet justo donec. Non nisi est sit amet facilisis magna. Risus feugiat in ante metus dictum at tempor commodo ullamcorper.';
1129
1130		sidebar.append('Yo');
1131	}
1132
1133	getUrlVars() {
1134		var vars = {};
1135		var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
1136			vars[key] = value;
1137		});
1138		return vars;
1139	}
1140
1141	async getItinerary(page = 1) {
1142		var atts = this.getUrlVars();
1143		var map = this.target.dataset.itin;
1144		var line = this.target.dataset.line;
1145
1146		this.polyline = await this.getFeature(line);
1147		if (!map) {
1148			map = atts.map ? atts.map : atts.post;
1149		}
1150		var url = wpRestAPI.root + 'trailpress/v1/itinerary/' + map;
1151
1152		var world = this;
1153		return new Promise(async function(resolve, reject) {
1154			var request = new XMLHttpRequest();
1155			request.open('GET', url);
1156			request.setRequestHeader('Content-Type', 'application/json');
1157			request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1158			request.onload = function() {
1159				if (this.status >= 200 && this.status < 400) {
1160					//world.renderPOIs(JSON.parse(this.response));
1161					resolve(world.createPolylineBetweenPoints(JSON.parse(this.response), world.polyline));
1162				} else {
1163					// If fail
1164					// console.log(this.response);
1165				}
1166			};
1167			request.onerror = function(error) {
1168				// console.log(error);
1169			};
1170			request.send();
1171		});
1172	}
1173
1174	async drawItinerary(points = [], line) {
1175		var world = this;
1176		this.polyline = await this.getFeature(line);
1177
1178		await this.getPointstoFeatures(points).then(response => world.createPolylineBetweenPoints(response, world.polyline, points));
1179		return new Promise(async function(resolve, reject) {
1180			resolve(world.trip);
1181		});
1182	}
1183
1184	async getPointstoFeatures(points) {
1185		var world = this;
1186		return new Promise(async function(resolve, reject) {
1187			var set = [];
1188			for (const point of points) {
1189				var thing = await world.getFeature(point);
1190				set.push(thing);
1191			}
1192			resolve(set);
1193		});
1194	}
1195
1196	async getFeature(id = null) {
1197		if (id == null) return false;
1198		// console.log(id);
1199		var url = wpRestAPI.root + 'trailpress/v1/feature/' + id;
1200
1201		var world = this;
1202
1203		return new Promise(function(resolve, reject) {
1204			var request = new XMLHttpRequest();
1205			var world = this;
1206			request.open('GET', url);
1207			request.setRequestHeader('Content-Type', 'application/json');
1208			request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1209			request.onload = function() {
1210				if (this.status >= 200 && this.status < 400) {
1211					//world.renderPOIs(JSON.parse(this.response));
1212					resolve(JSON.parse(this.response));
1213				} else {
1214					// If fail
1215					reject(this.response);
1216				}
1217			};
1218			request.onerror = function(error) {
1219				reject(error);
1220			};
1221			request.send();
1222		});
1223	}
1224
1225	getTowns() {
1226		var atts = this.getUrlVars();
1227
1228		var url = wpRestAPI.root + 'trailpress/v1/river-towns/';
1229
1230		var request = new XMLHttpRequest();
1231		var world = this;
1232		request.open('GET', url);
1233		request.setRequestHeader('Content-Type', 'application/json');
1234		request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1235		request.onload = function() {
1236			var markerdot = {
1237				path: google.maps.SymbolPath.CIRCLE,
1238				strokeColor: '#fff',
1239				fillColor: '#6db220',
1240				fillOpacity: 1,
1241				strokeWeight: 2,
1242				scale: 7,
1243			};
1244			if (this.status >= 200 && this.status < 400) {
1245				var towns = JSON.parse(this.response);
1246				towns.forEach(function(town) {
1247					var latlng = { lat: parseFloat(town.meta.lat), lng: parseFloat(town.meta.lng) };
1248					var marker = new google.maps.Marker({
1249						position: latlng,
1250						icon: markerdot,
1251						map: null,
1252						title: town.name,
1253						id: town.term_id,
1254					});
1255					world.towns.push(marker);
1256					world.itemList.push(marker);
1257					if (world.mode != 'nopopup') {
1258						var setupTownInfoWindow = function() {
1259							console.log('town touch');
1260							world.InfoWindow.setContent(town.infowindow);
1261							world.InfoWindow.close(world.map, marker);
1262							world.InfoWindow.open(world.map, marker);
1263						};
1264						marker.addListener('mouseover', setupTownInfoWindow);
1265						marker.addListener('click', setupTownInfoWindow);
1266					}
1267				});
1268			} else {
1269				// If fail
1270				// console.log(this.response);
1271			}
1272		};
1273		request.onerror = function(error) {
1274			// console.log(error);
1275		};
1276		request.send();
1277	}
1278
1279	getPlacesToVisit() {
1280		var atts = this.getUrlVars();
1281
1282		var url = wpRestAPI.root + 'trailpress/v1/places-to-visit/';
1283
1284		var request = new XMLHttpRequest();
1285		var world = this;
1286		request.open('GET', url);
1287		request.setRequestHeader('Content-Type', 'application/json');
1288		request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1289		request.onload = function() {
1290			var markerdot = {
1291				path: google.maps.SymbolPath.CIRCLE,
1292				strokeColor: '#fff',
1293				fillColor: '#b8d10a',
1294				fillOpacity: 1,
1295				strokeWeight: 2,
1296				scale: 7,
1297			};
1298			if (this.status >= 200 && this.status < 400) {
1299				if (this.response == '') return;
1300				var towns = JSON.parse(this.response);
1301				towns.forEach(function(town) {
1302					var latlng = { lat: parseFloat(town.address.lat), lng: parseFloat(town.address.lng) };
1303					var marker = new google.maps.Marker({
1304						position: latlng,
1305						icon: markerdot,
1306						map: null,
1307						title: town.post_title,
1308						id: town.ID,
1309					});
1310					world.itemList.push(marker);
1311					world.places.push(marker);
1312					if (world.mode != 'nopopup') {
1313						var setPlacesInfoWindow = function() {
1314							world.InfoWindow.setContent(town.infowindow);
1315							world.InfoWindow.close(world.map, marker);
1316							world.InfoWindow.open(world.map, marker);
1317						};
1318						marker.addListener('mouseover', setPlacesInfoWindow);
1319						marker.addListener('click', setPlacesInfoWindow);
1320					}
1321				});
1322			} else {
1323				// If fail
1324				// console.log(this.response);
1325			}
1326		};
1327		request.onerror = function(error) {
1328			// console.log(error);
1329		};
1330		request.send();
1331	}
1332
1333	getPOIs(page = 1) {
1334		var atts = this.getUrlVars();
1335		var map = this.target.dataset.map;
1336		if (!map) {
1337			map = atts.map ? atts.map : atts.post;
1338		}
1339		var url = wpRestAPI.root + 'trailpress/v1/map/' + map + '?status=publish' + (page != 1 ? '&page=' + page : '');
1340
1341		var request = new XMLHttpRequest();
1342		var world = this;
1343		request.open('GET', url);
1344		request.setRequestHeader('Content-Type', 'application/json');
1345		request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1346		request.onload = function() {
1347			if (this.status >= 200 && this.status < 400) {
1348				// console.log('getPOIs response');
1349				// console.log(JSON.parse(this.response));
1350				world.renderPOIs(JSON.parse(this.response));
1351				var pages = request.getResponseHeader('X-WP-TotalPages');
1352
1353				var atts = world.getUrlVars();
1354				// console.log(world.target.dataset.poi);
1355				if (typeof atts.poi !== 'undefined' || world.target.dataset.poi !== 'undefined') {
1356					var thispoi = world.target.dataset.poi !== 'undefined' ? world.target.dataset.poi : atts.poi;
1357					var target = world.polys.find(o => o.id === parseInt(thispoi));
1358					var bounds = new google.maps.LatLngBounds();
1359					if (target instanceof google.maps.Marker) {
1360						world.map.setZoom(13);
1361						world.map.panTo(target.getPosition());
1362					} else if (typeof target != 'undefined') {
1363						target.getPath().forEach(function(e) {
1364							bounds.extend(e);
1365							world.map.fitBounds(bounds);
1366						});
1367					}
1368					//world.selectShape(null, target);
1369				}
1370				if (page != pages && pages != 0) {
1371					page++;
1372					setTimeout(function() {
1373						world.getPOIs(page);
1374					}, 200);
1375				}
1376			} else {
1377				// If fail
1378				// console.log(this.response);
1379			}
1380		};
1381		request.onerror = function(error) {
1382			// console.log(error);
1383		};
1384		request.send();
1385	}
1386
1387	async getLayers(id = null) {
1388		if (id == null) {
1389			var none = this.layers.none;
1390			this.layers = {};
1391			this.layers.none = none;
1392		}
1393		var atts = this.getUrlVars();
1394		var map = this.target.dataset.map;
1395		if (!map) {
1396			map = atts.map ? atts.map : atts.post;
1397		}
1398		var url = wpRestAPI.root + 'trailpress/v1/map/' + map + '/layer/' + (id ? id + '/' : '');
1399
1400		var request = new XMLHttpRequest();
1401		var world = this;
1402		request.open('GET', url);
1403		request.setRequestHeader('Content-Type', 'application/json');
1404		request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1405		request.onload = function() {
1406			if (this.status >= 200 && this.status < 400) {
1407				if (this.response == null) return false;
1408				var layers = JSON.parse(this.response);
1409
1410				layers.forEach(function(layer) {
1411					if (id == null) {
1412						if (layer.count == 0 && layer.parent != 0) return;
1413						//world.texteditor.layer.options[world.texteditor.layer.options.length] = new Option(layer.name, layer.term_id);
1414						world.getLayers(layer.term_id);
1415						world.layers[layer.term_id] = layer;
1416						if (layer.meta) {
1417							//if (typeof layer.meta.side_bar_list !== 'undefined') {
1418							if (layer.meta.featureType == 'supertype') {
1419								world.superlayer.push({
1420									id: layer.term_id,
1421									name: layer.name,
1422									marker: layer.meta.supericon_color,
1423									default: layer.meta.front_end_list_default[0] == 1 ? true : false,
1424								});
1425							}
1426							//}
1427						}
1428					} else {
1429						if (!world.layers[id].features) {
1430							world.layers[id].features = [];
1431						}
1432						world.layers[id].features.push(layer);
1433					}
1434					//world.getPOIList();
1435				});
1436			} else {
1437				// If fail
1438				// console.log(this.response);
1439			}
1440		};
1441		request.onerror = function(error) {
1442			// console.log(error);
1443		};
1444		request.send();
1445	}
1446	async getIcons() {
1447		var world = this;
1448
1449		return new Promise(function(resolve, reject) {
1450			if (typeof world.icons != 'undefined' && world.icons.length > 0) {
1451				resolve(world.icons);
1452			}
1453			var atts = world.getUrlVars();
1454			var url = wpRestAPI.root + 'trailpress/v1/icons/';
1455
1456			var request = new XMLHttpRequest();
1457
1458			request.open('GET', url);
1459			request.setRequestHeader('Content-Type', 'application/json');
1460			request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1461			request.onload = function() {
1462				if (this.status >= 200 && this.status < 400) {
1463					var icons = this.response;
1464					world.icons = JSON.parse(icons);
1465					resolve(JSON.parse(icons));
1466				} else {
1467					// If fail
1468					reject(this.response);
1469				}
1470			};
1471			request.onerror = function(error) {
1472				reject(error);
1473			};
1474			request.send();
1475		});
1476	}
1477
1478	async getItineraries() {
1479		var world = this;
1480
1481		var atts = this.getUrlVars();
1482		var map = this.target.dataset.map;
1483		if (!map) {
1484			map = atts.map ? atts.map : atts.post;
1485		}
1486		var url = wpRestAPI.root + 'trailpress/v1/map/' + map + '/itineraries/';
1487
1488		return new Promise(function(resolve, reject) {
1489			var request = new XMLHttpRequest();
1490
1491			request.open('GET', url);
1492			request.setRequestHeader('Content-Type', 'application/json');
1493			request.setRequestHeader('X-WP-Nonce', wpRestAPI.nonce);
1494			request.onload = function() {
1495				if (this.status >= 200 && this.status < 400) {
1496					resolve(JSON.parse(this.response));
1497				} else {
1498					// If fail
1499					reject(this.response);
1500				}
1501			};
1502			request.onerror = function(error) {
1503				reject(error);
1504			};
1505			request.send();
1506		});
1507	}
1508
1509	async putIconsInSelect(select_elem, value, prefix = 'tp_radio_', update = true) {
1510		select_elem.innerHTML = null;
1511		var world = this;
1512		//console.log(this.icons.length);
1513		if (this.icons.length < 1) {
1514			this.icons = await this.getIcons();
1515		}
1516
1517		this.icons.forEach(function(icon) {
1518			var label = document.createElement('label');
1519			var radio = document.createElement('input');
1520			radio.setAttribute('type', 'radio');
1521			radio.setAttribute('name', prefix + 'option');
1522			radio.setAttribute('id', prefix + icon.slug);
1523			radio.value = icon.slug;
1524
1525			label.innerHTML = icon.html;
1526			label.prepend(radio);
1527			select_elem.append(label);
1528			if (update) {
1529				radio.addEventListener('change', event => world.updateMarkerIcon(event, icon));
1530			}
1531		});
1532
1533		select_elem.value = value;
1534	}
1535	loadKML(item) {
1536		var details = unserialize(item.meta.detail);
1537		if (this.featurekml) this.featurekml.setMap(null);
1538		if (details.cesiumkml == '') return;
1539
1540		this.featurekml = new google.maps.KmlLayer(details.cesiumkml, {
1541			suppressInfoWindows: true,
1542			preserveViewport: true,
1543			map: this.map,
1544		});
1545	}
1546
1547	buildInfoWindow() {
1548		var world = this;
1549		var maxWidth = window.outerWidth - 160;
1550		world.InfoWindow = new google.maps.InfoWindow({
1551			size: new google.maps.Size(300, 160),
1552			content: '<span style="min-width:180px">loading</span>',
1553			maxWidth: 440,
1554			maxHeight: 260,
1555		});
1556	}
1557
1558	createPath(feature) {
1559		var geometry = feature.getGeometry();
1560		var coordinates = [];
1561		geometry.forEachLatLng(function(latlng) {
1562			coordinates.push({ lat: latlng.lat(), lng: latlng.lng() });
1563		});
1564		return coordinates;
1565	}
1566
1567	async renderPOIs(input) {
1568		var world = this;
1569		input.forEach(function(item, i) {
1570			var dataLayer = new google.maps.Data();
1571			//console.log( 'item.feature' );
1572			//console.log( item.feature );
1573			if ('FeatureCollection' === item.feature.type) {
1574				item.feature.features.forEach(function(feature) {
1575					dataLayer.addGeoJson(feature);
1576				});
1577			} else {
1578				dataLayer.addGeoJson(item.feature);
1579			}
1580			var meta = item.meta;
1581			//console.log(meta);
1582			//console.log(item.meta);
1583			/* Object.keys(item.meta).forEach(function(key) {
1584          meta[key] = item.meta[key][0];
1585        });
1586        */
1587			var visiblemap = world.map;
1588
1589			if (item.layer.length > 0) {
1590				if (world.hiddenlayers.indexOf(item.layer[0].term_id) != -1) {
1591					visiblemap = null;
1592				}
1593			}
1594
1595			//console.log( 'FEATURES');
1596			//console.log( features );
1597			//features.forEach(async function(feature) {
1598			dataLayer.forEach(async function(feature) {
1599				var poly;
1600				var caution = '';
1601
1602				switch (feature.getGeometry().getType()) {
1603					case 'Point':
1604						// console.log('POINT');
1605						// console.log(world.createPath(feature));
1606						poly = new google.maps.Marker({
1607							map: visiblemap,
1608							//position: feature.getGeometry().i||feature.getGeometry().g||feature.getGeometry().h
1609							position: world.createPath(feature)[0],
1610						});
1611						poly.meta = meta;
1612						if (item.meta.isSticky) {
1613							poly.setOptions({
1614								label: '',
1615							});
1616						}
1617
1618						var superlayer = false;
1619						if (item.layer.length > 0) {
1620							var superlayerid = item.layer[0].parent != 0 ? item.layer[0].parent : item.layer[0].term_id;
1621							superlayer = world.superlayer.find(o => o.id === superlayerid);
1622						}
1623						if (superlayer) {
1624							var markerdot = {
1625								path: google.maps.SymbolPath.CIRCLE,
1626								strokeColor: '#fff',
1627								fillColor: superlayer.marker,
1628								fillOpacity: 1,
1629								strokeWeight: 2,
1630								scale: 7,
1631							};
1632							poly.setIcon(markerdot);
1633						}
1634						if (item.caution.length > 0) {
1635							item.caution.forEach(async function(caut) {
1636								if (world.icons.length < 1) {
1637									world.icons = await world.getIcons();
1638								}
1639								if (world.icons.length > 0) {
1640									caution = world.icons.find(o => o.slug === caut.meta.icon);
1641								}
1642							});
1643						}
1644						if (item.meta.icon) {
1645							if (world.icons.length < 1) {
1646								world.icons = await world.getIcons();
1647								//console.log(world.icons);
1648							}
1649							var icon = world.icons.find(o => o.slug === item.meta.icon);
1650							world.setMarker(poly, icon, visiblemap, caution);
1651						}
1652						if (world.mode != 'nopopup') {
1653							var setPointInfoWindow = function() {
1654								world.loadKML(item);
1655								world.InfoWindow.setContent(item.infowindow);
1656								world.InfoWindow.close(world.map, poly);
1657								world.InfoWindow.open(world.map, poly);
1658							};
1659							poly.addListener('mouseover', setPointInfoWindow);
1660							poly.addListener('click', setPointInfoWindow);
1661						}
1662						if (typeof superlayer != 'undefined' && superlayer != false && superlayer.default == false) {
1663							poly.setMap(null);
1664						}
1665
1666						break;
1667					case 'Polygon':
1668						poly = new google.maps.Polygon({
1669							map: visiblemap,
1670							paths: world.createPath(feature),
1671						});
1672						poly.meta = meta;
1673						//console.log(meta);
1674						poly.setOptions({
1675							strokeWeight: meta.outlineWeight ? meta.outlineWeight : 2,
1676							strokeColor: meta.outlineColor ? meta.outlineColor : '#000000',
1677							strokeOpacity: meta.outlineColorAlpha ? meta.outlineColorAlpha : 1,
1678							fillColor: meta.fillColor ? meta.fillColor : '#0000ff',
1679							fillOpacity: parseInt(meta.fillColorAlpha ? meta.fillColorAlpha : 100) * 0.01,
1680						});
1681
1682						break;
1683					case 'LineString':
1684						poly = new google.maps.Polyline({
1685							map: visiblemap,
1686							//path:feature.getGeometry().i||feature.getGeometry().g||feature.getGeometry().h
1687							path: world.createPath(feature),
1688						});
1689						poly.meta = meta;
1690						//console.log(poly);
1691						poly.setOptions({
1692							strokeWeight: meta.lineWeight ? meta.lineWeight : 2,
1693							strokeColor: meta.lineColor ? meta.lineColor : '#000000',
1694							strokeOpacity: parseInt(meta.lineColorAlpha ? meta.lineColorAlpha : 100) * 0.01,
1695						});
1696						break;
1697
1698					case 'MultiLineString':
1699						var collection = world.createPath(feature);
1700						var raw = [];
1701						var order = [];
1702						collection.forEach(function(line, key) {
1703							var start = line.i[0];
1704							var end = line.i[line.i.length - 1];
1705							raw[key] = [];
1706							collection.forEach(function(aline, akey) {
1707								var aend = aline.i[aline.i.length - 1];
1708								var astart = aline.i[akey];
1709								var e = world.haversineDistance([start.lat(), start.lng()], [astart.lat(), astart.lng()]);
1710								var s = world.haversineDistance([end.lat(), end.lng()], [astart.lat(), astart.lng()]);
1711								var cs = world.haversineDistance([start.lat(), start.lng()], [0, 0]);
1712								var csa = world.haversineDistance([astart.lat(), astart.lng()], [0, 0]);
1713								var ce = world.haversineDistance([aend.lat(), aend.lng()], [0, 0]);
1714								var path = collection[akey].i;
1715								var reverse = null;
1716								if (csa > ce) {
1717									path = path.reverse();
1718									reverse = true;
1719								}
1720								raw[key][akey] = {
1721									e: e,
1722									cs: cs,
1723									csa: csa,
1724									ce: ce,
1725									s: s,
1726									key: akey,
1727									path: path,
1728									reverse: reverse,
1729								};
1730							});
1731						});
1732						raw.forEach(function(calcs, key) {
1733							order[key] = {};
1734							order[key].key = key;
1735							var before = calcs.sort(function(a, b) {
1736								return a.e > b.e ? 1 : -1;
1737							});
1738							order[key].order = before;
1739							order[key].longest = before[before.length - 1];
1740						});
1741
1742						var longest = order.sort(function(a, b) {
1743							return a.longest.e < b.longest.e ? 1 : -1;
1744						});
1745						var path = [];
1746						longest[0].order.forEach(function(val) {
1747							// console.log(val.key, val.reverse, val);
1748							path = path.concat(val.path);
1749						});
1750						poly = new google.maps.Polyline({
1751							map: visiblemap,
1752							path: path,
1753						});
1754
1755						poly.meta = meta;
1756						//console.log(poly);
1757						poly.setOptions({
1758							strokeWeight: meta.lineWeight ? meta.lineWeight : 2,
1759							strokeColor: meta.lineColor ? meta.lineColor : '#000000',
1760							strokeOpacity: parseInt(meta.lineColorAlpha ? meta.lineColorAlpha : 100) * 0.01,
1761						});
1762
1763						break;
1764
1765					default:
1766						// console.log(feature.getGeometry().getType());
1767						return false;
1768						break;
1769				}
1770
1771				if (item.layer.length > 0) {
1772					var lay = item.layer[0].term_id;
1773					poly.layer = lay;
1774				} else {
1775					if (typeof world.layers.none === 'undefined') {
1776						world.layers.none = {
1777							term_id: 0,
1778							name: 'No Layer',
1779							parent: 0,
1780							features: [],
1781							meta: {},
1782						};
1783					}
1784					world.layers.none.features.push(item);
1785				}
1786				poly.title = item.post_title;
1787				poly.excerpt = item.post_excerpt;
1788				poly.isLocked = item.isLocked;
1789				poly.readMore = item.readMore;
1790				poly.meta.stickyParentid = item.meta.stickyParentid;
1791				poly.id = item.ID;
1792				//world.setEvents(poly);
1793				world.itemList.push(poly);
1794			});
1795		});
1796	}
1797	async createPolylineBetweenPoints(markers, polyline = false, path = false) {
1798		if (!path) {
1799			path = this.target.dataset.path.split(',');
1800		}
1801
1802		if (typeof polyline == 'undefined') polyline = this.polyline;
1803		var markerarray = [];
1804		var x = 1;
1805		var world = this;
1806
1807		// console.log(markers);
1808
1809		var dataLayer = new google.maps.Data();
1810		var features = dataLayer.addGeoJson(polyline.feature);
1811		var polyline = features[0].getGeometry().i || features[0].getGeometry().g || features[0].getGeometry().h;
1812		var lastmarker = null;
1813
1814		path.forEach(function(id) {
1815			var point = markers.find(o => o.ID == id);
1816			var dataLayer = new google.maps.Data();
1817			var features = dataLayer.addGeoJson(point.feature);
1818			var place = features[0].getGeometry().i || features[0].getGeometry().g || features[0].getGeometry().h;
1819			markerarray.push(place);
1820			var marker = new google.maps.Marker({
1821				map: world.map,
1822				position: place,
1823				label: x.toString(),
1824			});
1825			x++;
1826			lastmarker = marker;
1827			marker.setMap(world.map);
1828		});
1829
1830		var sourcearray = polyline;
1831		var world = this;
1832		var legs = [];
1833		this.trip = [];
1834		markerarray.forEach(function(pt, key) {
1835			var current_point = pt;
1836
1837			var next_point = markerarray[key + 1];
1838			// console.log(next_point);
1839
1840			if (typeof next_point != 'undefined') {
1841				var pathstart = world.findClosestPointOnPath(current_point, sourcearray);
1842				var pathend = world.findClosestPointOnPath(next_point, sourcearray);
1843				var startindex = sourcearray.indexOf(pathstart);
1844				var endindex = sourcearray.indexOf(pathend);
1845
1846				var newpath = sourcearray.slice(Math.min(...[startindex, endindex]), Math.max(...[startindex, endindex]) + 1);
1847				if (startindex > endindex) {
1848					legs.push([current_point, ...newpath.slice(1, -1).reverse(), next_point]);
1849				} else {
1850					legs.push([current_point, ...newpath.slice(1, -1), next_point]);
1851				}
1852			}
1853		});
1854		var bounds = new google.maps.LatLngBounds();
1855
1856		legs.forEach(function(returnarray, key) {
1857			var color = 'ff0000';
1858			// console.log(color);
1859			var flightPath = new google.maps.Polyline({
1860				path: returnarray,
1861				geodesic: true,
1862				strokeColor: '#' + color,
1863				strokeOpacity: 1.0,
1864				strokeWeight: 2,
1865			});
1866
1867			flightPath.setMap(world.map);
1868
1869			flightPath.getPath().forEach(function(e) {
1870				bounds.extend(e);
1871			});
1872			var km = google.maps.geometry.spherical.computeLength(flightPath.getPath().getArray()) * 0.001;
1873			var mi = km / 1.609344;
1874			world.trip.push(mi);
1875		});
1876		if (path.length < 2) {
1877			this.map.setZoom(13);
1878			this.map.panTo(markerarray.slice(-1)[0]);
1879			return;
1880		}
1881		world.map.fitBounds(bounds);
1882
1883		/*
1884    var pathstart = this.findClosestPointOnPath(start.getPosition(), path);
1885    var pathend = this.findClosestPointOnPath(end.getPosition(), path);
1886
1887    var startindex = path.indexOf(pathstart);
1888    var endindex = path.indexOf(pathend);
1889
1890    var newpath = path.slice(Math.min(...[startindex, endindex]), Math.max(...[startindex, endindex])+1);
1891
1892    this.dataLayer = new google.maps.Data();
1893    this.dataLayer.add(new google.maps.Data.Feature({
1894      geometry: new google.maps.Data.LineString(newpath)
1895    }));
1896
1897    var dataLayer = this.dataLayer;
1898    json.json = await this.LayertoJSON(dataLayer);
1899    json.meta = {}
1900
1901    var poly = await new google.maps.Polyline({
1902      map: this.map,
1903      path:newpath,
1904      strokeColor: '#aa0022',
1905      strokeWeight:8,
1906    })
1907    poly.meta = {}
1908    json.meta.isSegment = true;
1909    json.meta.stickyParentid = this.stickypolyline.id;
1910    poly.meta.stickyParentid = this.stickypolyline.id;
1911    poly.stickyParent = this.stickypolyline;
1912
1913    poly.id = await this.addItemToDatabase(json, poly);
1914
1915    this.setEvents(poly);
1916    this.getLayers();
1917    */
1918	}
1919	/*
1920  async createPolylineBetweenPoints(markers, polyline = false, path = false, map = false) {
1921
1922    console.log(markers, polyline, path);
1923
1924    var markerarray = [];
1925
1926    if (!path) {
1927      path = this.target.dataset.path.split(',');
1928    }
1929
1930    var x = 1;
1931    var world = this;
1932
1933    var dataLayer = new google.maps.Data();
1934    var features = dataLayer.addGeoJson(polyline.feature);
1935    var polyline = features[0].getGeometry().i;
1936
1937    path.forEach( function(id) {
1938      console.log(id);
1939      var point = markers.find(o => o.ID == id);
1940      var dataLayer = new google.maps.Data();
1941      var features = dataLayer.addGeoJson(point.feature);
1942      var place = features[0].getGeometry().i;
1943      markerarray.push(place)
1944      var marker = new google.maps.Marker({
1945        map: world.map,
1946        position: place,
1947        label: x.toString()
1948      });
1949      x++;
1950
1951      marker.setMap(world.map);
1952    })
1953
1954    var sourcearray = polyline;
1955    var world = this;
1956    var legs = []
1957    console.log(markerarray);
1958    markerarray.forEach(function(pt, key) {
1959      var current_point = pt;
1960
1961      var next_point = markerarray[key + 1];
1962      console.log(next_point);
1963
1964      if (typeof(next_point) != 'undefined') {
1965
1966        var pathstart = world.findClosestPointOnPath(current_point, sourcearray);
1967        var pathend = world.findClosestPointOnPath(next_point, sourcearray);
1968        var startindex = sourcearray.indexOf(pathstart);
1969        var endindex = sourcearray.indexOf(pathend);
1970
1971
1972
1973        var newpath = sourcearray.slice(Math.min(...[startindex, endindex]), Math.max(...[startindex, endindex])+1);
1974        if (startindex > endindex) {
1975          legs.push([current_point, ...newpath.slice(1, -1).reverse(), next_point])
1976        } else {
1977          legs.push([current_point,  ...newpath.slice(1, -1), next_point])
1978
1979        }
1980      }
1981    })
1982    var bounds = new google.maps.LatLngBounds();
1983    legs.forEach(function(returnarray, key) {
1984      var color = 'ff0000'
1985      var flightPath = new google.maps.Polyline({
1986        path: returnarray,
1987        geodesic: true,
1988        strokeColor: '#' + color,
1989        strokeOpacity: 1.0,
1990        strokeWeight: 2
1991      });
1992
1993      flightPath.setMap(world.map);
1994
1995      flightPath.getPath().forEach(function(e){
1996          bounds.extend(e);
1997
1998      })
1999      var km = google.maps.geometry.spherical.computeLength(flightPath.getPath().getArray())*.001;
2000      var mi = km/1.609344;
2001      console.log(mi+'mi, '+km+'km')
2002
2003    })
2004    world.map.fitBounds(bounds);
2005
2006  }
2007  */
2008
2009	async getPolylineBetweenPoints(start, end, polyline) {
2010		var path = polyline.getPath().getArray();
2011
2012		var pathstart = this.findClosestPointOnPath(start.getPosition(), path);
2013		var pathend = this.findClosestPointOnPath(end.getPosition(), path);
2014
2015		var startindex = path.indexOf(pathstart);
2016		var endindex = path.indexOf(pathend);
2017
2018		var newpath = path.slice(Math.min(...[startindex, endindex]), Math.max(...[startindex, endindex]) + 1);
2019
2020		var json = {};
2021		this.dataLayer = new google.maps.Data();
2022		this.dataLayer.add(
2023			new google.maps.Data.Feature({
2024				geometry: new google.maps.Data.LineString(newpath),
2025			})
2026		);
2027
2028		var dataLayer = this.dataLayer;
2029		json.json = await this.LayertoJSON(dataLayer);
2030		json.meta = {};
2031
2032		var poly = await new google.maps.Polyline({
2033			map: this.map,
2034			path: newpath,
2035			strokeColor: '#aa0022',
2036			strokeWeight: 8,
2037		});
2038		poly.meta = {};
2039		json.meta.isSegment = true;
2040		json.meta.stickyParentid = this.stickypolyline.id;
2041		poly.meta.stickyParentid = this.stickypolyline.id;
2042		poly.stickyParent = this.stickypolyline;
2043
2044		poly.id = await this.addItemToDatabase(json, poly);
2045
2046		this.setEvents(poly);
2047		this.getLayers();
2048	}
2049	generateIntermediatePointsOnPolyline(polyline, poly = null) {
2050		if (typeof polyline === 'undefined' && poly != null) {
2051			polyline = this.polys.find(o => o.id === parseInt(poly.meta.stickyParentid));
2052		}
2053		if (typeof polyline === 'undefined') {
2054			alert('something broke');
2055			return;
2056		}
2057		//console.log(polyline)
2058		var sourcearray = polyline.getPath().getArray();
2059		var returnarray = [];
2060		var world = this;
2061
2062		sourcearray.forEach(function(pt, key) {
2063			var current_point = pt;
2064			var next_point = sourcearray[key + 1];
2065
2066			if (typeof next_point !== 'undefined') {
2067				var distanceBetween = google.maps.geometry.spherical.computeDistanceBetween(current_point, next_point);
2068				var count = Math.round(distanceBetween / 5);
2069				//Get a 10th of the difference in latitude
2070				var lat_incr = (next_point.lat() - current_point.lat()) / count;
2071
2072				//Get a 10th of the difference in longitude
2073				var lng_incr = (next_point.lng() - current_point.lng()) / count;
2074
2075				//Add the current point to the new padded_points array
2076				returnarray.push(current_point);
2077
2078				//Now add 10 points at lat_incr & lng_incr intervals between current and next points
2079				//We add this to the new padded_points array
2080				for (var i = 1; i <= count; i++) {
2081					var new_pt = new google.maps.LatLng(current_point.lat() + i * lat_incr, current_point.lng() + i * lng_incr);
2082					returnarray.push(new_pt);
2083				}
2084			}
2085		});
2086
2087		return returnarray;
2088	}
2089
2090	findClosestPointOnPath(drop_pt, polylinearray, offset = 0) {
2091		var distances = new Array(); //Stores the distances of each pt on the path from the marker point
2092		var distance_keys = new Array(); //Stores the key of point on the path that corresponds to a distance
2093
2094		//For each point on the path
2095		polylinearray.forEach(function(path_pt, key) {
2096			//Find the distance in a linear crows-flight line between the marker point and the current path point
2097			var R = 6371; // km
2098			var dLat = (path_pt.lat() - drop_pt.lat()).toRad();
2099			var dLon = (path_pt.lng() - drop_pt.lng()).toRad();
2100			var lat1 = drop_pt.lat().toRad();
2101			var lat2 = path_pt.lat().toRad();
2102
2103			var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
2104			var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2105			var d = R * c;
2106			//Store the distances and the key of the pt that matches that distance
2107			distances[key] = d;
2108			distance_keys[d] = key;
2109		});
2110		//Return the latLng obj of the second closest point to the markers drag origin. If this point doesn't exist snap it to the actual closest point as this should always exist
2111		return polylinearray[distance_keys[Math.min(...distances) + offset]];
2112	}
2113
2114	hexToRGB(hex, alpha) {
2115		if (typeof hex == 'undefined') return 'rgb(0,0,0)';
2116		var r = parseInt(hex.slice(1, 3), 16),
2117			g = parseInt(hex.slice(3, 5), 16),
2118			b = parseInt(hex.slice(5, 7), 16);
2119
2120		if (alpha != null) {
2121			return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
2122		} else {
2123			return 'rgb(' + r + ', ' + g + ', ' + b + ')';
2124		}
2125	}
2126	_degToRad(deg) {
2127		var world = this;
2128		return (deg * Math.PI) / 180;
2129	}
2130
2131	haversine(number) {
2132		var world = this;
2133		if (isNaN(number)) throw Error(`Input '${number}' is not a number`);
2134		return Math.pow(Math.sin(number / 2), 2);
2135	}
2136
2137	/**
2138	 * Calculate the haversine distance between 2 points
2139	 * on the Earth, using radius of 6371 km
2140	 *
2141	 * @param {Array[{ lat,lon }]} point1 - lat, lon are mandatory
2142	 * @param {Array[{ lat,lon }]} point2 - lat, lon are mandatory
2143	 * @return {float} distance
2144	 */
2145	haversineDistance(_point1, _point2) {
2146		var world = this;
2147		if (!_point1 || _point1.length !== 2) throw Error(`Point 1 input is not valid: ${_point1.toString()}`);
2148		if (!_point2 || _point2.length !== 2) throw Error(`Point 2 input is not valid: ${_point2.toString()}`);
2149
2150		_point1 = _point1.map(item => {
2151			if (isNaN(item)) throw Error(`Point 1 input '${item}' is not a number`);
2152			return world._degToRad(item);
2153		});
2154
2155		_point2 = _point2.map(item => {
2156			if (isNaN(item)) throw Error(`Point 2 input '${item}' is not a number`);
2157			return world._degToRad(item);
2158		});
2159
2160		const radius = 6371;
2161		const point1 = { lat: _point1[0], lon: _point1[1] };
2162		const point2 = { lat: _point2[0], lon: _point2[1] };
2163
2164		const a = world.haversine(point2.lat - point1.lat);
2165		const b = Math.cos(point1.lat) * Math.cos(point2.lat) * world.haversine(point2.lon - point1.lon);
2166		const distance = 2 * radius * Math.sqrt(a + b);
2167
2168		return distance;
2169	}
2170	removeDuplicates(array) {
2171		return array.filter((a, b) => array.indexOf(a) === b);
2172	}
2173}
2174
2175if (typeof Number.prototype.toRad === 'undefined') {
2176	Number.prototype.toRad = function() {
2177		return (this * Math.PI) / 180;
2178	};
2179}
2180
2181function truncate(str, no_words) {
2182	return str
2183		.split(' ')
2184		.splice(0, no_words)
2185		.join(' ');
2186}
2187
2188function unserialize(data) {
2189	//  discuss at: https://locutus.io/php/unserialize/
2190	// original by: Arpad Ray (mailto:[email protected])
2191	// improved by: Pedro Tainha (https://www.pedrotainha.com)
2192	// improved by: Kevin van Zonneveld (https://kvz.io)
2193	// improved by: Kevin van Zonneveld (https://kvz.io)
2194	// improved by: Chris
2195	// improved by: James
2196	// improved by: Le Torbi
2197	// improved by: Eli Skeggs
2198	// bugfixed by: dptr1988
2199	// bugfixed by: Kevin van Zonneveld (https://kvz.io)
2200	// bugfixed by: Brett Zamir (https://brett-zamir.me)
2201	// bugfixed by: philippsimon (https://github.com/philippsimon/)
2202	//  revised by: d3x
2203	//    input by: Brett Zamir (https://brett-zamir.me)
2204	//    input by: Martin (https://www.erlenwiese.de/)
2205	//    input by: kilops
2206	//    input by: Jaroslaw Czarniak
2207	//    input by: lovasoa (https://github.com/lovasoa/)
2208	// improved by: Rafał Kukawski
2209	//      note 1: We feel the main purpose of this function should be
2210	//      note 1: to ease the transport of data between php & js
2211	//      note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
2212	//   example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
2213	//   returns 1: ['Kevin', 'van', 'Zonneveld']
2214	//   example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
2215	//   returns 2: {firstName: 'Kevin', midName: 'van'}
2216	//   example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
2217	//   returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
2218	//   example 4: unserialize(undefined)
2219	//   returns 4: false
2220	//   example 5: unserialize('O:8:"stdClass":1:{s:3:"foo";b:1;}')
2221	//   returns 5: { foo: true }
2222
2223	var utf8Overhead = function(str) {
2224		var s = str.length;
2225		for (var i = str.length - 1; i >= 0; i--) {
2226			var code = str.charCodeAt(i);
2227			if (code > 0x7f && code <= 0x7ff) {
2228				s++;
2229			} else if (code > 0x7ff && code <= 0xffff) {
2230				s += 2;
2231			}
2232			// trail surrogate
2233			if (code >= 0xdc00 && code <= 0xdfff) {
2234				i--;
2235			}
2236		}
2237		return s - 1;
2238	};
2239	var readUntil = function(data, offset, stopchr) {
2240		var i = 2;
2241		var buf = [];
2242		var chr = data.slice(offset, offset + 1);
2243
2244		while (chr !== stopchr) {
2245			if (i + offset > data.length) {
2246				throw Error('Invalid');
2247			}
2248			buf.push(chr);
2249			chr = data.slice(offset + (i - 1), offset + i);
2250			i += 1;
2251		}
2252		return [buf.length, buf.join('')];
2253	};
2254	var readChrs = function(data, offset, length) {
2255		var i, chr, buf;
2256
2257		buf = [];
2258		for (i = 0; i < length; i++) {
2259			chr = data.slice(offset + (i - 1), offset + i);
2260			buf.push(chr);
2261			length -= utf8Overhead(chr);
2262		}
2263		return [buf.length, buf.join('')];
2264	};
2265	function _unserialize(data, offset) {
2266		var dtype;
2267		var dataoffset;
2268		var keyandchrs;
2269		var keys;
2270		var contig;
2271		var length;
2272		var array;
2273		var obj;
2274		var readdata;
2275		var readData;
2276		var ccount;
2277		var stringlength;
2278		var i;
2279		var key;
2280		var kprops;
2281		var kchrs;
2282		var vprops;
2283		var vchrs;
2284		var value;
2285		var chrs = 0;
2286		var typeconvert = function(x) {
2287			return x;
2288		};
2289
2290		if (!offset) {
2291			offset = 0;
2292		}
2293		dtype = data.slice(offset, offset + 1);
2294
2295		dataoffset = offset + 2;
2296
2297		switch (dtype) {
2298			case 'i':
2299				typeconvert = function(x) {
2300					return parseInt(x, 10);
2301				};
2302				readData = readUntil(data, dataoffset, ';');
2303				chrs = readData[0];
2304				readdata = readData[1];
2305				dataoffset += chrs + 1;
2306				break;
2307			case 'b':
2308				typeconvert = function(x) {
2309					const value = parseInt(x, 10);
2310
2311					switch (value) {
2312						case 0:
2313							return false;
2314						case 1:
2315							return true;
2316						default:
2317							throw SyntaxError('Invalid boolean value');
2318					}
2319				};
2320				readData = readUntil(data, dataoffset, ';');
2321				chrs = readData[0];
2322				readdata = readData[1];
2323				dataoffset += chrs + 1;
2324				break;
2325			case 'd':
2326				typeconvert = function(x) {
2327					return parseFloat(x);
2328				};
2329				readData = readUntil(data, dataoffset, ';');
2330				chrs = readData[0];
2331				readdata = readData[1];
2332				dataoffset += chrs + 1;
2333				break;
2334			case 'n':
2335				readdata = null;
2336				break;
2337			case 's':
2338				ccount = readUntil(data, dataoffset, ':');
2339				chrs = ccount[0];
2340				stringlength = ccount[1];
2341				dataoffset += chrs + 2;
2342
2343				readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10));
2344				chrs = readData[0];
2345				readdata = readData[1];
2346				dataoffset += chrs + 2;
2347				if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) {
2348					throw SyntaxError('String length mismatch');
2349				}
2350				break;
2351			case 'a':
2352				readdata = {};
2353
2354				keyandchrs = readUntil(data, dataoffset, ':');
2355				chrs = keyandchrs[0];
2356				keys = keyandchrs[1];
2357				dataoffset += chrs + 2;
2358
2359				length = parseInt(keys, 10);
2360				contig = true;
2361
2362				for (i = 0; i < length; i++) {
2363					kprops = _unserialize(data, dataoffset);
2364					kchrs = kprops[1];
2365					key = kprops[2];
2366					dataoffset += kchrs;
2367
2368					vprops = _unserialize(data, dataoffset);
2369					vchrs = vprops[1];
2370					value = vprops[2];
2371					dataoffset += vchrs;
2372
2373					if (key !== i) {
2374						contig = false;
2375					}
2376
2377					readdata[key] = value;
2378				}
2379
2380				if (contig) {
2381					array = new Array(length);
2382					for (i = 0; i < length; i++) {
2383						array[i] = readdata[i];
2384					}
2385					readdata = array;
2386				}
2387
2388				dataoffset += 1;
2389				break;
2390			case 'O': {
2391				// O:<class name length>:"class name":<prop count>:{<props and values>}
2392				// O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
2393				readData = readUntil(data, dataoffset, ':'); // read class name length
2394				dataoffset += readData[0] + 1;
2395				readData = readUntil(data, dataoffset, ':');
2396
2397				if (readData[1] !== '"stdClass"') {
2398					throw Error('Unsupported object type: ' + readData[1]);
2399				}
2400
2401				dataoffset += readData[0] + 1; // skip ":"
2402				readData = readUntil(data, dataoffset, ':');
2403				keys = parseInt(readData[1], 10);
2404
2405				dataoffset += readData[0] + 2; // skip ":{"
2406				obj = {};
2407
2408				for (i = 0; i < keys; i++) {
2409					readData = _unserialize(data, dataoffset);
2410					key = readData[2];
2411					dataoffset += readData[1];
2412
2413					readData = _unserialize(data, dataoffset);
2414					dataoffset += readData[1];
2415					obj[key] = readData[2];
2416				}
2417
2418				dataoffset += 1; // skip "}"
2419				readdata = obj;
2420				break;
2421			}
2422			default:
2423				throw SyntaxError('Unknown / Unhandled data type(s): ' + dtype);
2424		}
2425		return [dtype, dataoffset - offset, typeconvert(readdata)];
2426	}
2427
2428	try {
2429		if (typeof data !== 'string') {
2430			return false;
2431		}
2432
2433		return _unserialize(data, 0)[2];
2434	} catch (err) {
2435		console.error(err);
2436		return false;
2437	}
2438}
2439
2440function arrayContainsArray(superset, subset) {
2441	if (0 === subset.length) {
2442		return false;
2443	}
2444	return subset.every(function(value) {
2445		return superset.indexOf(value) >= 0;
2446	});
2447}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.