WME Find Deleted Objects

Attempts to show the history of a venue that has been deleted in an area.

// ==UserScript==
// @name         WME Find Deleted Objects
// @namespace    https://greasyfork.org/users/32336-joyriding
// @version      2022.08.13.01
// @description  Attempts to show the history of a venue that has been deleted in an area.
// @author       Joyriding
// @include      https://beta.waze.com/*
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @exclude      https://www.waze.com/user/editor*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABY1JREFUeNqcl22MlNUVx3/nPjM7u+yKi4AsC2UNMSAfmmCwxsbERGliW0Pw5YOGGBJCYjTFREJrJGpLSH2JrbYGq7UJbT+obY0vIGg0UQyaIBFiSjCGLAhlYXdnUXFZ2J2deZ57Tj/cZ2aeHWZ3aW9yMvflOed/zzn/e+4dMTMARITJWv/WPYWZve/fImqrIXcdJN2Im+18/J26aAA4aE52jiz56Z4Fv76lPJmdKhaATAV8ctvB3OzP3lwvGm/BrCv9EGoGFHB1BZGiufyW73581/aeB69L/i/gC2s294iv7BAfL0cIAKYgKZApE+YhrIX5f2u+/faO1546+T8Bl+7acD2wG9zculYjqGOadgZ0VdubL3x+ScAX1mzuiUrnDogxFxRzOUTrUWsch1Br0745d8a3XnZ91fMssGvMaW707A5RPxfziE8QHyM+ATPEJ7h4fMIY82CG65lPy8tbcT3zAwfM4+LxK3Nj53ac3HYw1xiOCR6XV913P+hLE/ajWutWZl3J+fUPcPnyLgodEePnPSOHisz85H3aN62B9hkwOkb86HPY8VNZnAcKu/7y56ahLq++t+B8238w7cqwIXikyvDym7j8N+tpmTFNbkdLJI88gx3vy5DNFTUqXVXY+Ur5olA737YSX+lCE9AE4nFIKuAhbp9N59bpQa00TvLwk9ix4+DLkJSDLV/pcr5tJQ3MCK5XSqtRj3iPxDFiFnhSLnH2FxvJF8KnSUXp2/YR5x57CS1PPKpne0fh6AnEe7DAfvEe1Af7mZZN+goxAqAZ+AQ8qDk6r+2uV7Htn9J9YBf5Pz0BhYmcaV/cGfQBSdNpIjg1TGRFU2Ax60Z9yIvGYAJxBfFG68w6wKwzR8m/+AR0tAfDY+PIjFYA8m0u5YXW8lutDuKi7uah9skcUR+OjwmSeFBDvDI+Ug9px+Z1NVAujFJ876va2ti3cdCv2lFflySe0xQYHw+jIR/4GHyMJMHA8KHBukJLFDwdr1D8tI95d1xbJ/SJ76nZUGuQeLg5sDGIgaghmi6JgHna9++9iMHS2kLXz5bhonqpLbz2aqpvKbnS8moKRIPNQ222T1TTHMUIhphHll7NrA13TFeXOTdQpvOLT2pnXzRJRdNfv28Sj+0DLEl3aKAVZMki3Pbfw2UdU59fNaJfbQGt1D20pC5V+82AT93z+G5R+sMOQXCI5EK4Ac6P8u0f3m4KXHzuXTqOHUI81PSVMPaA0W8Lhnc3BU4v7qdRAyQQovdrWL8JBobQtQ9yxcvPc/q3b2Far7mn/7qP+f98MXwvGTJ5X7tARPXp6PW9yaSXhP1rg+PZ4/sx+1HtclCFSgxJDN4wgbM3raL1d5sY+eMbzN/1N4iikB7nUkZlHxV2gF9efYPc/YJOdR+L3bB6HknpY4xrai+LxAdRCRUNDy0zILIAikHUEjwEkCj0hSPk2m6W/TuHwnU8BTAg5c4fdrcsnvchyFLUh2kzSDSc8SgfAEVCMp1LVa3usFlv5cSZlYXhwwPVgzXpQ6AKXBg+PNR/ZOBWvD8Qdp9aixzk8wHIhcMfSmO6sardJPl88MiRWwvDh4eqNhsJOek9t3Dsq6FlA6dWUixuxFxfyHm6ARHw6bgKqAqJ9lEc2rhssP8n3aX+4lRHsDHU1Zg5IKrKwpkLcn1SuFt6Fj0PLn+xFZdY36mHFvkL/zg90p+SoCbVHeu0OU4BXWYDDnC+fcnP3eLuvwfw6ptaEzs5sM6N9O5OQTQDWO3bpZCr0XOXGaNzlt4mXfNeAcmDS+ybb9a6oS/fqVX8OqBmxjQCY2bZZ6c0CXUOaMlIwX6w4na75sb9tnD5nUChYT2X0XVZclWxzGzSfxLSyPQm8xeV7MyvNZmf8K7OTWNEmhi61Dalzn8HAB4VQHAYFYLdAAAAAElFTkSuQmCC
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @license      GPLv3
// @grant        none
// ==/UserScript==

/* global W */
/* global OpenLayers */
/* global I18n */
/* global $ */
/* global WazeWrap */
/* global require */
/* global google */
/* global Backbone */

(function() {
    'use strict';

    var settings = {};
    var _wmeFdoFeatureLayer;
    var _wmeFdoMarkerLayer;

    var projection=new OpenLayers.Projection("EPSG:900913");
    var displayProjection=new OpenLayers.Projection("EPSG:4326");

    var requestStatus = [];
    var seenVenueIds = [];
    var subcategoryToParentCategories = [];

    var flags = {
        venue: { }
    };

    var externalProviderDetails = { };

    var idMappings = {
        users: [],
        streets: [],
    }

    let userCache = [];
    var userProgress = [];
    let earliestScanDate = parseInt(new Date('2016-12-31').getTime());

    let googleMapsApi = {
        map: document.createElement('div'),
        service: null
    }

    const DAY_MS = 1000 * 60 * 60 * 24;
    const WEEK_MS = DAY_MS * 7;

    function bootstrap(tries) {
        tries = tries || 1;

        if (W && W.map &&
            W.model && W.loginManager.user &&
            WazeWrap.Ready && $ ) {
            init();
        } else if (tries < 1000)
            setTimeout(function () {bootstrap(tries++);}, 200);
    }

    function init()
    {
        let styleElements = getWmeStyles();

        var $style = $("<style>");
        $style.html([
            '<style>',
            '#wmeFdoStatusDate { margin-top:10px;margin-bottom:10px; }',
            '.spinner-disable { opacity: 0; }',
            '.wmeFdoFormInputLabel { display:inline-block;margin-right:10px;}',
            '.wmeFdoFormInput { display:inline-block; width:150px;}',
            '.wmeFdoButtonArea {margin-top:10px;margin-bottom:10px; }',
            '.wmeFdoGoogleBtn { margin-top:10px;margin-bottom:10px; }',
            '.wmeFdoRecreate a { color: #b5b5b5; cursor: pointer; text-decoration:none;}',
            '.wmeFdoRecreate a:hover { text-decoration:underline; }',
            '.wmeFdoResultDetail { position: relative; }',
            '.wmeFdoDetailControls {position: absolute;right: 17px;top: 10px;}',
            '.wmeFdoFlag a { text-decoration: none; }',
            '.wmeFdoFlag { cursor:pointer; opacity:0.2; margin-right:5px;}',
            '.wmeFdoFlag:hover { color:red; opacity:.75}',
            '.wmeFdoFlag.flagged { color: red; opacity:1}',
            '.wmeFdoHistoryContent { padding-left: 14px;padding-right: 14px; }',
            '.wmeFdo-element-history-item:not(:last-child) { margin-bottom: 5px; }',
            '.wmeFdo-element-history-item.closed .wmeFdo-tx-header {border-radius: 2px;background: #ededed;}',
            '.wmeFdo-element-history-item.closed .wmeFdo-tx-header:hover {background: rgba(255, 255, 255, 0.5);}',
            '.wmeFdo-element-history-item .wmeFdo-tx-header {display: flex;flex-direction: row; justify-content: space-between;padding: 10px;border: 1px solid #e4e4e4;border-radius: 2px 2px 0px 0px;font-size: 11px;color: #687077;line-height: 14px;background: white;transition: all 0.3s;cursor: pointer;}',
            '.wmeFdo-element-history-item .wmeFdo-tx-content {display: block;padding: 10px;padding-left: 22px; background-color: white;border: 1px solid #e4e4e4;border-top: none; font-size: 11px;}',
            '.wmeFdo-element-history-item.closed .wmeFdo-tx-content {display: none;}',
            '.wmeFdo-element-history-item.wmeFdo-tx-has-related .wmeFdo-tx-changed-attribute:last-child {margin-bottom: 10px;}',
            '.wmeFdo-element-history-item.wmeFdo-tx-has-related .wmeFdo-tx-changed-attribute:last-child { margin-bottom: 10px;}',
            '.wmeFdo-element-history-item .tx-toggle-closed {width: 30px;height: 30px;text-align: center;}',
            '.wmeFdo-element-history-item:not(.tx-has-content) .tx-content, .element-history-item:not(.tx-has-content) .tx-toggle-closed {display: none;}',
            '.wmeFdo-element-history-item .tx-toggle-closed {width: 30px;height: 30px;text-align: center;}',
            '.wmeFdo-element-history-item.closed .wmeFdo-tx-toggle-closed::after { content: "\\F107";top: 8px;}',
            '.wmeFdo-element-history-item .wmeFdo-tx-toggle-closed::after {display: inline-block;font: normal normal normal 14px/1 FontAwesome;font-size: inherit;text-rendering: auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;color: #3d3d3d;display: block;position: relative;top: 8px;content: "\\F106";font-size: 19px;}',
            '.historySummaryItem { margin-left:10px; color:#a7a7a7; }',
            '.historySummaryItem a, .historySummaryItem a:visited { color:#a7a7a7; }',
            '.wmeFdo-place-delete { filter: saturate(17) hue-rotate(65deg); }',
            '.wmeFdo-place-delete:hover { filter: brightness(110%) saturate(17) hue-rotate(65deg) !important; }',
            '.wmeFdo-place-delete:after {',
            'border-top: 3px solid #ffffff;',
            'width: 69%;',
            'height: 49%;',
            'position: absolute;',
            'bottom: 6px;',
            'left: 8px;',
            'content: "";',
            'transform: rotate(-45deg);',
            '}',
            '#wmeFdoSubTabs, .wmeFdoFlag { display: none!important; }',
            '.wmeFdoResultTypeVenue { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:3px;' + styleElements.resultTypeVenueStyle + '}',
            '.wmeFdoResultTypeCamera { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:3px;' + styleElements.resultTypeCameraStyle + '}',
            '.wmeFdoResultTypeArea { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:10px;' + styleElements.resultTypeAreaStyle + '}',
            // '.wmeFdoResultTypeResidential { opacity:0.65; margin-left:-3px;margin-right:5px;position:relative;top:4px;' + styleElements.resultTypeResidentialNew + '}',
            // '.wmeFdoResultTypeParking { opacity:0.65; margin-left:-1px;margin-right:3px;margin-bottom:6px;position:relative;top:4px;filter: saturate(0%);' + styleElements.resultTypeParkingNew + '}',
            '.wmeFdoResultTypeResidential { filter:invert(.35); margin-left:-11px;margin-right:-1px;position:relative;top:-6px;' + styleElements.resultTypeResidentialNew + '}',
            '.wmeFdoResultTypeParking { filter:invert(.35); margin-left:-11px;margin-right:-1px;position:relative;top:-6px;' + styleElements.resultTypeParkingNew + '}',
            '.wmeFdoResultDeletedNewPlace:after { position:relative;content:"+";color:black;font-size:17px;font-weight:900;text-shadow: 0px 0px 2px white;top:7px;left:8px; }',
            '.wmeFdoResultDeletedNewPlaceAlt:after { position:relative;content:"+";color:black;font-size:17px;font-weight:900;top:14px;left:20px; }',
            '.wmeFdoResultDeletedFromRequest:after { position:relative;content:"\\002b06";color:black;font-size:13px;font-weight:900;text-shadow: 0px 0px 3px white;top:7px;left:8px; }',
            '.wmeFdoResultDeletedFromRequestAlt:after { position:relative;content:"\\002b06";color:black;font-size:13px;font-weight:900;top:14px;left:20px;opacity: .8; }',


            '.wmeFdoIconFilter { transform:rotate(90deg);margin-left:-1px;margin-right:3px;margin-bottom:6px;position:relative;top:4px;filter: saturate(0%);' + styleElements.iconFilter + '}',
            //'.wmeFdoIconFilterInline { font-size:15px;margin-top:5px; }',
            '.wmeFdoIconFilterInline:before { transform:rotate(90deg);filter:saturate(0%);content:"";position:relative;display:inline-block;top:3px;margin-right:10px;' + styleElements.iconFilter + '}',
            '.wmeFdoScanAttributesDropdown { font-size:15px;margin-top:5px; }',
            '.wmeFdoScanAttributesLabel { margin-left:10px; }',
            '#wmeFdoStatusUsersList > li { margin-bottom:10px }',
            '.wmeFdoStatusUserDates { width:200px; }',
            '.wmeFdoStatusUserLatest { float:left; }',
            '.wmeFdoStatusUserEarliest { float:right; }',
            '.wmeFdoProgress {width:200px;height:5px;border:1px solid gray;border-radius:3px; }',
            '#wmeFdoScanStatusHeader .wmeFdoProgress { margin-top: 5px;margin-left: 27px; }',
            '.wmeFdoComplete { width:0%;height:4px;background-color:#5a7990; }',
            '.wmeFdoProgressIcon { transform: scale(1.3,-1) rotate(90deg); }',
            '.wmeFdoDropdownHeader { width: 80%; }',
            '#wmeFdoStatusSummary { margin-bottom:10px; }',
            '.wmeFdoHistorySummaryList > li { margin-bottom:2px; }',
            '</style>'
        ].join(' '));


        var $section = $('<div id="wmeFdoPanel">');
        $section.html(
            '<div id="wmeFdoHead">',

            '</div>'
        );

        var $navTabs = $('<ul id="wmeFdoSubTabs" class="nav nav-tabs"><li class="active"><a data-toggle="tab" href="#wmeFdoTabFind">Find</a></li>' +
                         '<li><a data-toggle="tab" href="#wmeFdoTabFlagged" id="wmeFdoTabFlagEvent">Flagged</a></li>' +
                         '<li><a data-toggle="tab" href="#wmeFdoTabSettings"><span class="fa fa-gear"></span></a></li></ul>');
        var $tabContent = $('<div class="tab-content" style="padding:5px;">');
        var $fdoTabFind = $('<div id="wmeFdoTabFind" class="tab-pane active">');
        $fdoTabFind.append([
            '<h4 style="margin-top:0px;">Find Deleted Objects</h4>',
            '<h6 style="margin-top:0px;">' + GM_info.script.version + '</h6>'
        ].join(' '));
        createSettingsCheckbox($fdoTabFind, 'wmeFdoNearbyEditors','Nearby Editors');
        $fdoTabFind.append([
            '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Editor Name</div><input type="text" id="wmeFdoEditorName" class="form-control wmeFdoFormInput"><label for="wmeFdoEditorName"></label></div><br>',
        ].join(' '));
        createSettingsCheckbox($fdoTabFind, 'wmeFdoIncludeSelf','Include Deletes By This Editor');
        createSettingsCheckbox($fdoTabFind, 'wmeFdoLimitToScreen','Limit to Screen');
        $fdoTabFind.append([
            '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Find Prior To</div><input type="text" id="wmeFdoFindDate" class="form-control wmeFdoFormInput"><label for="wmeFdoFindDate"></label></div><br>',
        ].join(' '));


        var $fdoTabFlagged = $('<div id="wmeFdoTabFlagged" class="tab-pane">');
        $fdoTabFlagged.html('<h4>Flagged Objects<h4>');
        let $fdoTabFlaggedResults = $('<div id="wmeFdoFlaggedResults>');
        $fdoTabFlagged.append($fdoTabFlaggedResults);

        var $fdoTabSettings = $('<div id="wmeFdoTabSettings" class="tab-pane">');
        $fdoTabSettings.append([
            '<h4>Settings</h4>',
            '<div id="wmeFdoSettings">',
            '<div class="controls-container" style="padding-top: 2px;">Rate Limit Delay: <input type="text" id="wmeFdoRateLimitDelay" ><label for="wmeFdoRateLimitDelay"></label></div>',
            '</div>'
        ].join(' '));

        $('#wmeFdoTabSettings').append('div').append('Reset Settings');

        $tabContent.append($fdoTabFind,$fdoTabFlagged,$fdoTabSettings);
        $section.append($navTabs, $tabContent);
        $section.append($style);

        let updateDesc = "<style>#wmeFdoUpdate > li {margin-top:6px;margin-left: -22px;}</style>This update addresses most known issues of the initial release:"
        + "<ul id='wmeFdoUpdate'>"
        + "<li><b>Better Rate Limiting</b><br>Completely rewritten API request layer.</li>"
        + "<li><b>Recreate Places</b><br>External Providers can now be added from Prod WME.</li>"
        + "<li><b>New Status Display</b><br>Shows the list of nearby editors used and the current progress. Completed scans now show correctly.</li>"
        + "<li><b>Filtering</b><br> </li>"
        + "<li><b>Result Type Icons</b><br>Different icons for Parking and Residential places</li>"
        + "</ul>"
        + "<span style='color:red'>Reminder that this is an SM+ script!</span>";

        updateDesc = 'Fixes browser freeze when starting a scan.<br><br>';

        //WazeWrap.Interface.ShowScriptUpdate("Find Deleted Objects", GM_info.script.version, updateDesc, "https://greasyfork.org/scripts/376861-wme-find-deleted-objects", "https://www.waze.com/forum/viewtopic.php?f=1286&t=275164&p=1922116#p1922116");

        new WazeWrap.Interface.Tab('FiDO', $section.html(), initializeSettings);
        prepareTab();

        WazeWrap.Interface.AddLayerCheckbox("display", "Deleted Objects", settings.Enabled, onLayerCheckboxChanged);
        onLayerCheckboxChanged(settings.Enabled);

        buildFilterSection('#wmeFdoTabFind');


        let buttons = document.createElement('div');
        buttons.className = 'wmeFdoButtonArea';
        $('#wmeFdoTabFind').append(buttons);

        var findButton = document.createElement('button');
        findButton.id = 'wmeFdoFindButton';
        findButton.textContent = 'Start Scan';
        findButton.className = 'btn btn-success center-block';
        buttons.append(findButton);

        var cancelButton = document.createElement('button');
        cancelButton.id = 'wmeFdoCancelButton';
        cancelButton.textContent = 'Cancel Scan';
        cancelButton.className = 'btn btn-warning center-block hidden';
        buttons.append(cancelButton);

        findButton.addEventListener('click', function() {
            findButton.className = 'btn btn-success center-block hidden';
            cancelButton.className = 'btn btn-danger center-block ';

            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-enable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            $('#wmeFdoTabFind').data("status","running");

            settings.Enabled = true;
            onLayerCheckboxChanged(settings.Enabled); // TODO: Fix this so the Layer menu checkbox gets updated
            clearResults();
            beginScanNew();
            findObjects();
        });

        cancelButton.addEventListener('click', function() {
            cancelButton.textContent = 'Cancelling...';
            cancelButton.className = 'btn btn-warning center-block disabled';
            $('#wmeFdoTabFind').data("status","cancelled");
            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            cancelScanNew();
            cancelScan(0);
        });

        $("#wmeFdoEditorName").keyup(function(event) {
            if (event.keyCode === 13) {
                $("#wmeFdoFindButton").click();
            }
        });

        $("#wmeFdoFindDate").keyup(function(event) {
            if (event.keyCode === 13) {
                $("#wmeFdoFindButton").click();
            }
        });

        $('#wmeFdoNearbyEditors').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            if(this.checked)
            {
                $("#wmeFdoEditorName").prop('disabled', true);
                $("#wmeFdoIncludeSelf").prop('disabled', true);
                $("#wmeFdoLimitToScreen").prop('disabled', true);

            }
            else
            {
                $("#wmeFdoEditorName").prop('disabled', false);
                $("#wmeFdoIncludeSelf").prop('disabled', false);
                $("#wmeFdoLimitToScreen").prop('disabled', false);
            }
        });


        let flaggedTab = document.getElementById('wmeFdoTabFlagEvent');
        flaggedTab.addEventListener('click', function() {
            let flagList = '';
            for (var id in flags.venue) {
                flagList = flagList + id + '<br>';
                console.log(id);
            }
            let results = document.getElementById('wmeFdoTabFlagged');
            results.append(flagList);
        });

        var $usersDiv = $( '<div id="fdoUsersPanel"></div>');

        //var dateSpan = document.createElement('div');
        //dateSpan.id = 'wmeFdoStatusDate';
        //$('#wmeFdoTabFind').append(dateSpan);

        let inputDate = document.getElementById('wmeFdoFindDate');
        inputDate.value = formatCurrentDate();

        $('#wmeFdoTabFind').append(buildResultsPanel());

        console.log($('#wmeFdoFilterResidential')[0].id);
        $('#wmeFdoFilterResidential').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            applyFiltersToResults();
        });
        $('#wmeFdoFilterParkingLot').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            applyFiltersToResults();
        });
        $('#wmeFdoFilterAnyType').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            applyFiltersToResults();
        });
        $('#wmeFdoFilterNewRejected').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            applyFiltersToResults();
        });
        $('#wmeFdoFilterDeleteRequest').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            applyFiltersToResults();
        });

        initializeSettings();
        console.log('FiDO: done');
    }

    function initializeSettings()
    {
        loadSettings();
        setChecked('wmeFdoIncludeSelf', settings.IncludeSelf);
        setChecked('wmeFdoLimitToScreen', settings.LimitToScreen);
        setChecked('wmeFdoNearbyEditors', settings.NearbyEditors);
        setChecked('wmeFdoFilterResidential', settings.FilterResidential);
        setChecked('wmeFdoFilterParkingLot', settings.FilterParkingLot);
        setChecked('wmeFdoFilterAnyType', settings.FilterAnyType);
        setChecked('wmeFdoFilterNewRejected', settings.FilterNewRejected);
        setChecked('wmeFdoFilterDeleteRequest', settings.FilterDeleteRequest);


        //   if(settings.Enabled)
        //      W.selectionManager.events.register("selectionchanged", null, drawSelection);

        $('.wmeFdoSettingsCheckbox').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            if(this.checked)
            {
                //   W.selectionManager.events.register("selectionchanged", null, drawSelection);
                //   processVenues(W.model.venues.getObjectArray());
            }
            else
            {
                //  W.selectionManager.events.unregister("selectionchanged", null, drawSelection);
                //   wmeFdoSelectedLayer.removeAllFeatures();
                // processVenues(W.model.venues.getObjectArray());
            }
        });


        if(settings.NearbyEditors)
        {
            $("#wmeFdoEditorName").prop('disabled', true);
            $("#wmeFdoIncludeSelf").prop('disabled', true);
            $("#wmeFdoLimitToScreen").prop('disabled', true);

        }
        else
        {
            $("#wmeFdoEditorName").prop('disabled', false);
            $("#wmeFdoIncludeSelf").prop('disabled', false);
            $("#wmeFdoLimitToScreen").prop('disabled', false);
        }

        $('.wmeFdoSettingsText').on('input propertychange paste', function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = parseInt($(this).val());
            saveSettings();

            //W.selectionManager.events.unregister("selectionchanged", null, drawSelection);
            //processVenues(W.model.venues.getObjectArray());
        });


    }

    function getWmeStyles() {
        // Get the sprite icons from the native WME CSS so that we can use our own document structure

        let styleElements = { };

        let $tempDiv = null;
        let tempQuerySelector = null;
        let tempComputedStyle = null;

        //.form-search .search-result-region .search-result .icon {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        $tempDiv = $('<div class="form-search">').append($('<div class="search-result-region">').append($('<div class="search-result">').append($('<div class="icon">'))));
        $('body').append($tempDiv);
        tempQuerySelector = document.querySelector('.form-search .search-result-region .search-result .icon');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector);
        styleElements.resultTypeVenueStyle =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;
        $tempDiv.remove();

        //#edit-buttons .camera .item-icon::after {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        tempQuerySelector = document.querySelector("#primary-toolbar > div > div.toolbar-group.toolbar-group-drawing > wz-menu > div > wz-menu-item.toolbar-group-item.camera.WazeControlDrawFeature > div > div.item-icon");
        tempComputedStyle = window.getComputedStyle(tempQuerySelector,'::after');
        styleElements.resultTypeCameraStyle =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;

        //#edit-buttons .toolbar-group .toolbar-group-item .drawing-controls .drawing-control.polygon:after {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        tempQuerySelector = document.querySelector("#primary-toolbar > div > div.toolbar-group.toolbar-group-venues > wz-menu > div > wz-menu-item.toolbar-group-item.car-services.WazeControlDrawFeature > div > div.drawing-controls > wz-basic-tooltip:nth-child(2) > wz-tooltip > wz-tooltip-source > wz-button").shadowRoot.querySelector("button")
        tempComputedStyle = window.getComputedStyle(tempQuerySelector,'::after');
        styleElements.resultTypeAreaStyle =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;

        // .toolbar .toolbar-button.add-house-number .item-icon {
        //background-image: url(//editor-assets.waze.com/production/img/toolbarcad3e90….png);
        //$tempDiv = $('<div class="toolbar">').append($('<div class="toolbar-button add-house-number">').append($('<div class="item-icon">')));
        //$('body').append($tempDiv);
        //tempQuerySelector = document.querySelector('.toolbar .toolbar-button.add-house-number .item-icon');
        //tempComputedStyle = window.getComputedStyle(tempQuerySelector);
        //styleElements.resultTypeResidential = `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
        //    + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
        //    + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
        //    + `width:${tempComputedStyle.getPropertyValue('width')};`
        //    + `height:${tempComputedStyle.getPropertyValue('height')};`;
        //$tempDiv.remove();

        //#edit-buttons .parking-lot .item-icon::after {
        //background-image: url(//editor-assets.waze.com/production/img/toolbarcad3e90….png);
        //tempQuerySelector = document.querySelector('#edit-buttons .parking-lot .item-icon');
        //tempComputedStyle = window.getComputedStyle(tempQuerySelector,'::after');
        //styleElements.resultTypeParking = `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
        //    + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
        //    + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
        //    + `width:${tempComputedStyle.getPropertyValue('width')};`
        //    + `height:${tempComputedStyle.getPropertyValue('height')};`;

        //#edit-panel .merge-landmarks .merge-item .do-merge:before, .edit-panel .merge-landmarks .merge-item .do-merge:before {
        //background-image: url(//editor-assets.waze.com/production/img/buttons756c103….png);
        $tempDiv = $('<div id="edit-panel">').append($('<div class="merge-landmarks">').append($('<div class="merge-item">').append($('<div class="do-merge">'))));
        $('body').append($tempDiv);
        tempQuerySelector = document.querySelector('#edit-panel .merge-landmarks .merge-item .do-merge');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector, '::before');
        styleElements.iconFilter =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;
        $tempDiv.remove();

        //#edit-panel .merge-landmarks .merge-item .icon.parking_lot:after
        $tempDiv = $('<div id="edit-panel">').append($('<div class="merge-landmarks">').append($('<div class="merge-item">').append($('<div class="icon parking_lot">'))));
        $('body').append($tempDiv);
        tempQuerySelector = document.querySelector('#edit-panel .merge-landmarks .merge-item .icon.parking_lot');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector, '::after');
        styleElements.resultTypeParkingNew =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;
        $tempDiv.remove();

        //#edit-panel .merge-landmarks .merge-item .icon.parking_lot:after
        $tempDiv = $('<div id="edit-panel">').append($('<div class="merge-landmarks">').append($('<div class="merge-item">').append($('<div class="icon residential_home">'))));
        $('body').append($tempDiv);
        tempQuerySelector = document.querySelector('#edit-panel .merge-landmarks .merge-item .icon.residential_home');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector, '::after');
        styleElements.resultTypeResidentialNew =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;
        $tempDiv.remove();

        return styleElements;
    }

    function getWmeStyle(selector) {
        let $tempDiv = null;
        let tempQuerySelector = null;
        let tempComputedStyle = null;
        let returnedStyle = null;

        //.form-search .search-result-region .search-result .icon {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        $tempDiv = $('<div class="form-search">').append($('<div class="search-result-region">').append($('<div class="search-result">').append($('<div class="icon">'))));
        $('body').append($tempDiv);
        tempQuerySelector = document.querySelector('.form-search .search-result-region .search-result .icon');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector);
        returnedStyle =
            `background-image:${tempComputedStyle.getPropertyValue('background-image')};`
            + `background-size:${tempComputedStyle.getPropertyValue('background-size')};`
            + `background-position:${tempComputedStyle.getPropertyValue('background-position')};`
            + `width:${tempComputedStyle.getPropertyValue('width')};`
            + `height:${tempComputedStyle.getPropertyValue('height')};`;
        $tempDiv.remove();

        return returnedStyle;
    }

//historyContent
    //element-history-item


    function buildResultsPanel() {
        return $('<div id="fdoResultsPanel">')
            .append($('<div class="elementHistoryContainer">')
                    .append($('<div class="wmeFdoHistoryContent">')
                            .append($('<div class="transactions">')
                                    .append($('<ul id="wmeFdoTransactionList">'))
                                   )
                           )
                   );
    }

    function buildFilterSection(parentDivName) {
        let $section = $('<div id="wmeFdoFilterSection">')
        .append($('<div class="elementHistoryContainer">')
                .append($('<div class="wmeFdoHistoryContent">')
                        .append($('<div class="transactions">')
                                .append($('<ul id="wmeFdoFilter">')
                                        .append($('<li id="wmeFdoFilterDetails" class="wmeFdo-element-history-item wmeFdo-tx-has-content wmeFdo-tx-has-related closed">')
                                                .append($('<div id="wmeFdoFilterDetailsHeader" class="wmeFdo-tx-header">')
                                                        //   .append($('<div class="flex-noshrink wmeFdoIconFilter">')
                                                        //  )
                                                        //.append($('<div class="flex-noshrink">'))
                                                        .append($('<div class="wmeFdo-tx-summary wmeFdoDropdownHeader">')
                                                                .append($('<div class="wmeFdoIconFilterInline wmeFdoScanAttributesDropdown">Filter Results</div>'))
                                                               )
                                                        .append($('<div class="flex-noshrink wmeFdo-tx-toggle-closed">')
                                                               )
                                                       )
                                                .append($('<div class="wmeFdo-tx-content">')
                                                        .append($('<div id="wmeFdoFilterDetailsUsers">'))
                                                        .append($('<div id="wmeFdoFilterDetailsPlaces">'))
                                                        .append($('<div id="wmeFdoFilterDetailsCameras">')) //<ul><li class="tx-changed-attribute">'))
                                                       )
                                               )

                                        .append($('<li id="wmeFdoScanStatus" class="wmeFdo-element-history-item wmeFdo-tx-has-content wmeFdo-tx-has-related closed">')
                                                .append($('<div id="wmeFdoScanStatusHeader" class="wmeFdo-tx-header">')
                                                        //.append($('<div class="flex-noshrink">'))
                                                        .append($('<div class="wmeFdo-tx-summary wmeFdoDropdownHeader">')
                                                                .append($('<div class="wmeFdoScanAttributesDropdown">')
                                                                        .append($('<i class="fa fa-bar-chart wmeFdoProgressIcon" aria-hidden="true"></i>')

                                                                               ).append($('<span class="wmeFdoScanAttributesLabel"><span id="wmeFdoScanCurrentStatus">Status</span></span>'))
                                                                       ).append($('<div class="wmeFdoProgress hidden"><div class="wmeFdoComplete"></div></div>')
                                                                               ))
                                                        .append($('<div class="flex-noshrink wmeFdo-tx-toggle-closed">')
                                                               )
                                                       )
                                                .append($('<div class="wmeFdo-tx-content">')
                                                        .append($('<div id="wmeFdoStatusSummary">')
                                                                .append($('<div><span>Total Requests: </span><span id="wmeFdoRequests">0<span></div>'))
                                                                .append($('<div><span>Requests per second: </span><span id="wmeFdoRequestsPerSecond">0<span></div>'))
                                                               )
                                                        .append($('<div id="wmeFdoStatusUsers">')
                                                                .append($('<ul id="wmeFdoStatusUsersList">')
                                                                       ))
                                                       )
                                               )
                                       )
                               )
                       )
               );

        let $parentDiv = $(parentDivName);
        $parentDiv.append($section);

        //   $('#wmeFdoFilterDetailsUsers').append([
        //       '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Editor Name</div><input type="text" id="wmeFdoFilterEditorName" class="form-control wmeFdoFormInput"><label for="wmeFdoFilterEditorName"></label></div><br>',
        //   ].join(' '));
        let $placeTypes = $('<ul>');
        let $placeTypeLi = $('<li class="tx-changed-attribute">');
        $placeTypes.append($placeTypeLi);
        $('#wmeFdoFilterDetailsPlaces').append($placeTypes);
        $placeTypeLi.append(['<div class="ca-name">Place Types</div>'].join(' '));
        createSettingsCheckbox($placeTypeLi, 'wmeFdoFilterResidential','Residential');
        createSettingsCheckbox($placeTypeLi, 'wmeFdoFilterParkingLot','Parking Lots');
        createSettingsCheckbox($placeTypeLi, 'wmeFdoFilterAnyType','All Other Types');

        let $purTypes = $('<ul>');
        let $purTypeLi = $('<li class="tx-changed-attribute">');
        $purTypes.append($purTypeLi);
        $('#wmeFdoFilterDetailsPlaces').append($purTypes);
        $purTypeLi.append(['<div class="ca-name">Deleted From PUR</div>'].join(' '));
        createSettingsCheckbox($purTypeLi, 'wmeFdoFilterNewRejected','New Place Rejected PUR');
        createSettingsCheckbox($purTypeLi, 'wmeFdoFilterDeleteRequest','Existing Place Delete Request');
        //   $('#wmeFdoFilterDetailsPlaces').append([
        //       '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Place Name</div><input type="text" id="wmeFdoFilterPlaceName" class="form-control wmeFdoFormInput"><label for="wmeFdoFilterPlaceName"></label></div><br>',
        //   ].join(' '));

        document.getElementById('wmeFdoFilterDetailsHeader').onclick = function () {
            toggleFilterDropDown();
        }

        document.getElementById('wmeFdoScanStatusHeader').onclick = function () {
            toggleScanStatusDropDown();
        }
    }

    function onLayerCheckboxChanged(checked) {
        settings.Enabled = checked;
        if (settings.Enabled && _wmeFdoFeatureLayer != undefined) {
            _wmeFdoFeatureLayer.setVisibility(true);
            _wmeFdoMarkerLayer.setVisibility(true);
        } else if (_wmeFdoFeatureLayer != undefined) {
            _wmeFdoFeatureLayer.setVisibility(false);
            _wmeFdoMarkerLayer.setVisibility(false);
        }
        saveSettings();
    }

    function clearResults() {
        if (_wmeFdoFeatureLayer != undefined) {
            _wmeFdoFeatureLayer.removeAllFeatures();
            _wmeFdoMarkerLayer.clearMarkers();
        }
        apiEndpointWazeCom = {};
        userProgress = [];
        userCache = [];
        seenVenueIds = [];
        requestStatus = {};
        document.getElementById('wmeFdoTransactionList').innerHTML = '';
        $('#wmeFdoStatusUsersList').empty();
        $('#wmeFdoScanStatusHeader .wmeFdoProgress .wmeFdoComplete').css('width','0%');
    }

    function cancelScan(tries) {
        //TODO: Move rest of cancel code into here

        tries = tries || 1;
        //console.log('incomplete: ' + incompleteRequestCount());
        if (incompleteRequestCount() > 0 && tries < 10) {
            console.log('Cancel requested, waiting for ' + incompleteRequestCount() + ' requests to complete');
            setTimeout(function () {cancelScan(tries++);}, 500);
        } else {
            // TODO: Change button to Start Scan
            let findButton = document.getElementById('wmeFdoFindButton');
            let cancelButton = document.getElementById('wmeFdoCancelButton');
            findButton.className = 'btn btn-success center-block';
            cancelButton.className = 'btn btn-warning center-block hidden';
            cancelButton.textContent = 'Cancel Scan';
            //$('#wmeFdoStatusDate')[0].innerText = 'Scan Cancelled';
            console.log('Cancelled');
        }
    }

    function incompleteRequestCount() {
        let incompleteCount = 0;
        let failedCount = 0;
        Object.keys(requestStatus).forEach(function (url) {
            if (requestStatus[url].status != 'successful') {
                if (Date.now() - requestStatus[url].lastAttempt < 10000) {
                    incompleteCount++;
                } else {
                    failedCount++;
                }
            }
        });
        return incompleteCount;
    }

    function defaultRequestStatus() {
        return {
            status: 'initial',
            attempts: 0,
            lastAttempt: null
        };
    }

    function requestParams(url) {
        return {
            url: url,
            iteration: 0,
            nextTransactionDate: null,
            initiated: Date.now()
        }
    }

    async function beginFind(lookFor, transactionLookup) {
        // var findCenterPoint = new OL.Geometry.Point(lookFor.locationCenter.coordinates[0], lookFor.locationCenter.coordinates[1]).transform(W.map.displayProjection, W.map.projection);

        if ( $('#wmeFdoTabFind').data("status") == "running"
            && transactionLookup.iteration < transactionLookup.maxIterations
           )
        {
            transactionLookup.iteration++;

            let requestParamsTransactions = requestParams(getApiUrlTransactions(transactionLookup.userId, transactionLookup.nextTransactionDate));
            requestStatus[requestParamsTransactions.url] = defaultRequestStatus();
            apiEndpointWazeCom.requests.push(requestParamsTransactions);
            getEndpointResult(apiEndpointWazeCom, requestParamsTransactions, data => {
                if (apiEndpointWazeCom.isRunning) {

                    if (typeof(data) == 'undefined') {
                        console.log('Request failed.');

                    }

                    transactionLookup.nextTransactionDate = data.nextTransactionDate
                    let nextDate = 'Complete';
                    if (data.nextTransactionDate != null)
                    {
                        nextDate = uuidToDate(data.nextTransactionDate);
                        //$('#wmeFdoStatusDate')[0].innerText = 'Scanning thru ' + nextDate + '...';
                        $('#wmeFdoScanCurrentStatus')[0].innerText = 'Scanning...';
                    }


                    var transactionCount = 0;
                    data.transactions.forEach(function(transaction) {
                        transaction.operations.forEach(async function(operation) {
                            if (operation.objectType == lookFor.objectType || operation.objectType == 'Camera')
                            {
                                transactionCount++;
                                var operationCenterPoint = new OpenLayers.Geometry.Point(operation.objectCentroid.coordinates[0], operation.objectCentroid.coordinates[1]).transform(displayProjection, projection);
                                //var distance = operationCenterPoint.distanceTo(findCenterPoint);
                                //if (distance < 1000)
                                if (!lookFor.limitToScreen || lookFor.searchArea.containsPoint(operationCenterPoint))
                                {
                                    if (operation.objectType == 'Camera') {
                                        console.log('camera: ' + operation.objectID);
                                    } else {
                                        if (!seenVenueIds.includes(operation.objectID))
                                        {
                                            getDeletedItemsVenue(operation, transactionLookup, lookFor, operationCenterPoint);
                                        } //else { console.log('already seen: ' + operation.objectID); }
                                    }
                                }
                            }
                        });
                    });

                    let earliestDate = userCache[transactionLookup.userId].earliestDate - DAY_MS;
                    let statusDate = 0;
                    let pctComplete = 100;

                    if (transactionLookup.nextTransactionDate != null) {
                        statusDate = uuidToUnixTime(transactionLookup.nextTransactionDate);
                        let latestDate = userCache[transactionLookup.userId].latestDate + DAY_MS;
                        //let earliestDate = userCache[transactionLookup.userId].earliestDate - DAY_MS;
                        let next = uuidToDate(transactionLookup.nextTransactionDate);

                        let totalTime = latestDate - earliestDate;
                        let progressTime = latestDate - statusDate;

                        pctComplete = (progressTime / totalTime) * 100;
                        // pctComplete = 100 - pctComplete;

                        let newCompletedMs = userCache[transactionLookup.userId].previousDate - statusDate;
                        apiEndpointWazeCom.progress.completeMs += newCompletedMs;
                        userCache[transactionLookup.userId].completeMs += newCompletedMs;
                    }

                    if (transactionLookup.nextTransactionDate == null || statusDate < earliestDate) {
                        apiEndpointWazeCom.users.complete++;
                        apiEndpointWazeCom.progress.completeMs -= userCache[transactionLookup.userId].completeMs;
                        apiEndpointWazeCom.progress.completeMs += userCache[transactionLookup.userId].totalMs;
                        updateStatusUser(transactionLookup.userId, 'Complete');

                        $('#wmeFdoStatusUser-' + transactionLookup.userId + ' .wmeFdoProgress .wmeFdoComplete').css('width','100%');

                        endScan("Scanning...", false);
                    } else {
                        updateStatusUser(transactionLookup.userId, uuidToDate(transactionLookup.nextTransactionDate));
                        $('#wmeFdoStatusUser-' + transactionLookup.userId + ' .wmeFdoProgress .wmeFdoComplete').css('width',pctComplete + '%');
                        userCache[transactionLookup.userId].previousDate = statusDate;
                        beginFind(lookFor, transactionLookup);
                    }
                }
            });
        } else {
            if (transactionLookup.iteration >= transactionLookup.maxIterations)
            {
                endScan("Transaction Limit Exceeded", false);
            }
        }
    }

    function updateStatusUser(userID, status) {
        $('#wmeFdoStatusUser-' + userID + ' .wmeFdoStatusUser').text(status);
        let pctComplete = (apiEndpointWazeCom.progress.completeMs / apiEndpointWazeCom.progress.totalMs) * 100;

        if (status == 'Complete') {
            $('#wmeFdoStatusUser-' + userID).appendTo('#wmeFdoStatusUsersList');
            userCache[userID].completeMs = userCache[userID].totalMs;
            //pctComplete = 100;
        }
        $('#wmeFdoScanStatusHeader .wmeFdoProgress .wmeFdoComplete').css('width',pctComplete + '%');
    }

    async function getDeletedItemsVenue(operation, transactionLookup, lookFor, operationCenterPoint) {
        //console.log('element history for ' + data.nextTransactionDate);
        let requestParamsElementHistory = requestParams(getApiUrlElementHistory('venue', operation.objectID, null));
        requestStatus[requestParamsElementHistory.url] = defaultRequestStatus();
        apiEndpointWazeCom.requests.push(requestParamsElementHistory);
        getEndpointResult(apiEndpointWazeCom, requestParamsElementHistory, venueHistory => {
            if (apiEndpointWazeCom.isRunning) {
                venueHistory.transactions.objects.forEach(function(venueTransaction) {
                    venueTransaction.objects.forEach(function(venueAction) {
                        if (venueAction.actionType == 'DELETE') {
                            //             console.log(venueAction);
                            if (venueAction.objectType == 'venue' && venueAction.oldValue != null)
                            {
                                let selfDelete = false;
                                if (transactionLookup.userId == venueTransaction.userID) {
                                    selfDelete = true;
                                }

                                if (lookFor.includeSelf || !selfDelete) {
                                    // Skip if searching for editor who deleted it
                                    if (!seenVenueIds.includes(venueAction.objectID))
                                    {
                                        seenVenueIds.push(venueAction.objectID);

                                        let historySummary = getVenueHistory(venueHistory);

                                        var record = document.createElement('div');
                                        record.id=venueTransaction.transactionID;
                                        let username = null;
                                        venueHistory.users.objects.forEach( function(user) {
                                            if (user.id == venueTransaction.userID) {
                                                let userRank = user.rank + 1;
                                                username = user.userName + '(' + userRank + ')';
                                            }
                                        });


                                        record.innerText = username + ": " + venueAction.oldValue.name + " - " + venueAction.objectID;

                                        var details = document.createElement('li');
                                        details.id = 'entry-' + venueAction.objectID;
                                        details.className = 'wmeFdo-element-history-item wmeFdo-tx-has-content wmeFdo-tx-has-related closed';

                                        var txHeader = document.createElement('div');
                                        txHeader.className = 'wmeFdo-tx-header';
                                        let detailsDisplayedOnce = false;
                                        txHeader.onclick = function () {

                                            if (!detailsDisplayedOnce && details.classList.contains('closed')) {
                                                detailsDisplayedOnce = true;
                                                displayExternalProviders(venueAction.objectID, venueItem.externalProviderIDs);
                                            }
                                            toggleDetailsDropDown(details.id, operationCenterPoint);
                                        };

                                        var txTypeDiv = document.createElement('div');
                                        txTypeDiv.className = 'flex-noshrink';
                                        txHeader.append(txTypeDiv);

                                        var txTypeIcon = document.createElement('div');
                                        // class name populated after venueItem is defined
                                        txTypeDiv.append(txTypeIcon);

                                        var txSummary = document.createElement('div');
                                        txSummary.className = 'wmeFdo-tx-summary wmeFdoDropdownHeader';
                                        txHeader.append(txSummary);

                                        var txCollapse = document.createElement('div');
                                        txCollapse.className = 'flex-noshrink wmeFdo-tx-toggle-closed';
                                        txHeader.append(txCollapse);

                                        var txAuthorDate = document.createElement('div');
                                        txAuthorDate.className = 'wmeFdo-tx-author-date';

                                        var txPreview = document.createElement('div');
                                        txPreview.className = 'wmeFdo-tx-preview';


                                        var txName = document.createElement('h4');
                                        //txName.className = 'type';
                                        txAuthorDate.append(txName);

                                        txSummary.append(txAuthorDate);
                                        txSummary.append(txPreview);

                                        var txContent = document.createElement('div');
                                        txContent.className = 'wmeFdo-tx-content wmeFdoResultDetail';

                                        var txContentClass = document.createElement('div');
                                        txContentClass.className = 'main-object-region wmeFdo-tx-changes';
                                        txContent.append(txContentClass);

                                        var txContentInfo = document.createElement('div');
                                        txContentInfo.className = 'related-objects-region wmeFdo-tx-changes';

                                        var txContentData = document.createElement('div');

                                        var controls = document.createElement('div');
                                        controls.className = 'wmeFdoDetailControls';
                                        txContent.append(controls);

                                        txContentInfo.append(txContentData);
                                        txContent.append(txContentInfo);

                                        details.append(txHeader);
                                        details.append(txContent);

                                        var venueItem = venueAction.oldValue;

                                        if (typeof(venueItem.geometry) == 'undefined') {
                                            // History does not contain any details for this place, other than it was deleted.
                                            // TODO: create empty record to at least show that there was something here at one point.
                                            console.log(`No history found for venue id ${venueAction.objectID}`);
                                        } else {
                                            if (typeof(venueItem.categories) != 'undefined' && venueItem.categories[0] == 'RESIDENCE_HOME') {
                                                txTypeIcon.className = 'flex-noshrink wmeFdoResultTypeResidential';
                                                if (historySummary.isRejectedNew) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedNewPlaceAlt');
                                                } else if (historySummary.isAcceptedDelete) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedFromRequestAlt');
                                                }
                                            } else if (typeof(venueItem.categories) != 'undefined' && venueItem.categories[0] == 'PARKING_LOT') {
                                                txTypeIcon.className = 'flex-noshrink wmeFdoResultTypeParking';
                                                if (historySummary.isRejectedNew) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedNewPlaceAlt');
                                                } else if (historySummary.isAcceptedDelete) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedFromRequestAlt');
                                                }
                                            } else {
                                                txTypeIcon.className = 'flex-noshrink wmeFdoResultTypeVenue';
                                                if (historySummary.isRejectedNew) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedNewPlace');
                                                } else if (historySummary.isAcceptedDelete) {
                                                    txTypeIcon.classList.add('wmeFdoResultDeletedFromRequest');
                                                }
                                            }

                                            if (historySummary.isRejectedNew) {
                                                details.setAttribute("data-pur-new-rejected", "true");
                                            }

                                            if (venueItem.geometry.type == 'Polygon') {
                                                var txTypeArea = document.createElement('div');
                                                txTypeArea.className = 'flex-noshrink wmeFdoResultTypeArea';
                                                txTypeDiv.append(txTypeArea);
                                            }


                                            let streetName = formatFullStreetName(venueHistory, venueItem.streetID);
                                            let address = streetName;
                                            if (typeof(venueItem.houseNumber) != 'undefined') {
                                                address = venueItem.houseNumber + ' ' + address;
                                            }

                                            var venueName = venueItem.name;
                                            if (typeof venueName == 'undefined' || venueName == " " || venueName == "")
                                            {
                                                if (typeof(venueItem.categories) != 'undefined' && venueItem.categories[0] == 'RESIDENCE_HOME') {
                                                    let resiStreet = formatStreetName(venueHistory, venueItem.streetID);
                                                    if (typeof(venueItem.houseNumber) != 'undefined') {
                                                        resiStreet = venueItem.houseNumber + ' ' + resiStreet;
                                                    }
                                                    venueName = resiStreet;
                                                } else if (typeof(venueItem.categories) != 'undefined') {
                                                    venueName = 'unnamed ' + I18n.t("venues.categories." + venueItem.categories[0]);
                                                } else {
                                                    venueName = 'unnamed';
                                                    //console.log('No category: ' + venueAction.objectID);
                                                }
                                            }

                                            txName.innerText = venueName;

                                            if (historySummary.purDeletedBy != null)
                                            {
                                                username = historySummary.purDeletedBy;
                                            }

                                            txPreview.innerHTML = streetName + '<br>Deleted by ' + username + " " + timeConverterNoTime(venueTransaction.date);
                                            if (historySummary.isAcceptedDelete) {
                                                //txPreview.innerHTML += " via PUR";
                                                details.setAttribute("data-pur-delete-request", "true");
                                            }

                                            var info = document.createElement('div');
                                            info.id = 'details-' + venueAction.objectID;

                                            let flagControl = document.createElement('div');
                                            flagControl.className = 'focus-buttons';
                                            let flag = document.createElement('a');
                                            flag.className = 'wmeFdoFlag';
                                            if (flags['venue'][venueAction.objectID]) { flag.className = 'wmeFdoFlag flagged'; }
                                            flag.innerHTML = '<span class="fa fa-flag"></span>';
                                            flag.onclick = function () {
                                                toggleFlag(flag, 'venue', venueAction.objectID);
                                            };
                                            flagControl.append(flag);
                                            controls.append(flagControl);

                                            var focusButtons = document.createElement('div');
                                            focusButtons.className = 'focus-buttons';

                                            var focus = document.createElement('a');
                                            focus.className = 'focus';
                                            focus.onclick = function () {
                                                var lonLat = new OpenLayers.LonLat(operationCenterPoint.x, operationCenterPoint.y);
                                                W.map.setCenter(lonLat, 6);
                                                highlightPin(pin);
                                            };
                                            focusButtons.append(focus);
                                            controls.append(focusButtons);

                                            let lockRank = venueItem.lockRank +1;
                                            txContentData.append(info);
                                            let searchText = venueItem.name + " " + streetName;

                                            var searchButton = document.createElement('button');
                                            searchButton.textContent = 'Google';
                                            searchButton.className = 'wmeFdoGoogleBtn btn btn-default center-block';
                                            searchButton.addEventListener('click', function() {
                                                window.open('https://www.google.com/search?q=' + encodeURIComponent(searchText),'_blank');
                                            });

                                            let recreateDiv = document.createElement('div');
                                            recreateDiv.className = 'wmeFdoRecreate';

                                            let recreateButton = document.createElement('a');
                                            recreateButton.textContent = 'Recreate Place';
                                            recreateButton.className = 'focus';
                                            recreateButton.addEventListener('click', function() {
                                                recreatePlace(venueItem, venueHistory, operationCenterPoint);
                                            });
                                            recreateDiv.append(recreateButton);

                                            txContentData.append(searchButton);
                                            txContentData.append(recreateDiv);

                                            let panelHTML = '<ul>';
                                            //panelHTML += '<ul>';

                                            let venueIdReference = 'details-venue-id-' + venueAction.objectID;
                                            panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Venue ID</div>';
                                            panelHTML += '<div class="ca-description ca-preview" id="' + venueIdReference + '">';
                                            panelHTML += '</div></li>';

                                            if (address != null && address != "" && address != " ") {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Address</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += address;
                                                panelHTML += '</div></li>';
                                            }

                                            panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Categories</div>';
                                            panelHTML += '<div class="ca-description ca-preview">';
                                            panelHTML += formatCategories(venueItem.categories);
                                            details.setAttribute("data-categories", formatCategoriesNative(venueItem.categories));
                                            panelHTML += '</div></li>';

                                            if (typeof(venueItem.description) != 'undefined') {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Description</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += venueItem.description;
                                                panelHTML += '</div></li>';
                                            }

                                            if (typeof(venueItem.url) != 'undefined') {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Website</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += venueItem.url;
                                                panelHTML += '</div></li>';
                                            }

                                            if (typeof(venueItem.phone) != 'undefined') {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Phone</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += venueItem.phone;
                                                panelHTML += '</div></li>';
                                            }

                                            if (typeof(venueItem.lockRank) != 'undefined') {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Lock Rank</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += lockRank;
                                                panelHTML += '</div></li>';
                                            }

                                            if (typeof(venueItem.externalProviderIDs) != 'undefined') {
                                                panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">External Provider IDs</div>';
                                                panelHTML += '<div class="ca-description ca-preview">';
                                                panelHTML += '<div id="wmeFdoExternalProviders-' + venueAction.objectID + '"></div>';
                                                panelHTML += '</div></li>';
                                            }

                                            panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">History</div>';
                                            panelHTML += '<div class="ca-description ca-preview">';
                                            panelHTML += historySummary.html;
                                            panelHTML += '</div></li>';


                                            panelHTML += '</ul>';
                                            info.innerHTML = panelHTML;



                                            //info.innerHTML += 'Changed from';
                                            //info.innerHTML += '<span class="ca-value ca-value-old">';
                                            //info.innerHTML += 'Walmart';
                                            //info.innerHTML += '</span>';
                                            //info.innerHTML += '<span>';
                                            //info.innerHTML += 'to';
                                            //info.innerHTML += '</span>';
                                            //info.innerHTML += '<span class="ca-value ca-value-new">';
                                            //info.innerHTML += 'Walmart Supercenter';
                                            //info.innerHTML += '</span>';

                                            $('#wmeFdoTransactionList').append(details);

                                            let venueIdDiv = document.getElementById(venueIdReference);
                                            let venueIdLi = document.getElementById(details.id);
                                            setFilteredState(venueIdLi);

                                            let venueIdDisplay = document.createElement('span');
                                            if (W.loginManager.user.userName == "Joyriding") {
                                                venueIdDisplay.innerHTML = `<a href="https://${window.location.host}${W.Config.api_base}/ElementHistory?objectType=venue&objectID=${venueAction.objectID}" target="_blank">${venueAction.objectID}</a> `;
                                            } else {
                                                venueIdDisplay.innerHTML = venueAction.objectID;
                                            }
                                            venueIdDiv.append(venueIdDisplay);

                                            let pin = displayPin(operation, venueAction.oldValue);
                                            let areaPreview = null;

                                            details.onmouseenter = function () {
                                                highlightPin(pin);
                                                areaPreview = showDeletedArea(operation, venueAction.oldValue);
                                            };
                                            details.onmouseleave = function () {
                                                returnPin(pin);
                                                //removeDeletedArea();
                                                _wmeFdoFeatureLayer.removeFeatures([areaPreview]);
                                            };
                                        }

                                    }
                                }

                            }
                        }
                    });

                });
            }
        });
    }

    function toggleFlag (control, objectType, objectID) {
        if (flags[objectType][objectID]) {
            delete flags[objectType][objectID];
            control.className = 'wmeFdoFlag';
        } else {
            flags[objectType][objectID] = true;
            control.className = 'wmeFdoFlag flagged';
        }
    }

    function displayDetails() {

    }

    async function recreatePlace(origVenue, venueHistory, operationCenterPoint) {
        let lonLat = new OpenLayers.LonLat(operationCenterPoint.x, operationCenterPoint.y);
        if(!W.map.getExtent().containsLonLat(lonLat)) {
            // Move to pin so that the City can be populated correctly
            W.map.setCenter(lonLat, 6);
            await wait(1000); //TODO: Check to see that cities have loaded before continuing rather than guessing a time delay
        }

        let wazeActionUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry");
        let wazefeatureVectorLandmark = require("Waze/Feature/Vector/Landmark");
        let wazeActionAddLandmark = require("Waze/Action/AddLandmark");
        let wazeActionUpdateObject = require('Waze/Action/UpdateObject');
        let wazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress');
        let wazeOpeningHour = require("Waze/Model/Objects/OpeningHour");

        let landmark = new wazefeatureVectorLandmark();
        if (origVenue.geometry.type == 'Polygon') {
            let points = [];
            origVenue.geometry.coordinates[0].forEach( function(coord) {
                let point = new OpenLayers.Geometry.Point(coord[0],coord[1]);
                point.transform(displayProjection,projection);
                points.push(point);
            });
            points.push(points[0]);
            let linearRing = new OpenLayers.Geometry.LinearRing(points);
            let polygon = new OpenLayers.Geometry.Polygon(linearRing);
            landmark.geometry = polygon;
        } else {
            let point = new OpenLayers.Geometry.Point(origVenue.geometry.coordinates[0],origVenue.geometry.coordinates[1]);
            point.transform(displayProjection,projection);
            landmark.geometry = point;
        }

        if (typeof(origVenue.name) != 'undefined') { landmark.attributes.name = origVenue.name; }
        if (typeof(origVenue.aliases) != 'undefined') { landmark.attributes.aliases = origVenue.aliases; }
        if (typeof(origVenue.categories) != 'undefined') { landmark.attributes.categories = origVenue.categories; }
        if (typeof(origVenue.description) != 'undefined') { landmark.attributes.description = origVenue.description; }
        if (typeof(origVenue.lockRank) != 'undefined') { landmark.attributes.lockRank = origVenue.lockRank; }
        if (typeof(origVenue.phone) != 'undefined') { landmark.attributes.phone = origVenue.phone; }
        if (typeof(origVenue.rank) != 'undefined') { landmark.attributes.rank = origVenue.rank; }
        if (typeof(origVenue.residential) != 'undefined') { landmark.attributes.residential = origVenue.residential; }
        if (typeof(origVenue.services) != 'undefined') { landmark.attributes.services = origVenue.services; }
        if (typeof(origVenue.url) != 'undefined') { landmark.attributes.url = origVenue.url; }

        if (origVenue.categories.indexOf('GAS_STATION') > -1 || origVenue.categories.indexOf('PARKING_LOT') > -1) {
            if (typeof(origVenue.brand) != 'undefined') { landmark.attributes.brand = origVenue.brand; }
        }

        if (origVenue.categories.indexOf('PARKING_LOT') > -1)
        {
            if (typeof(origVenue.categoryAttributes) != 'undefined') { landmark.attributes.categoryAttributes = origVenue.categoryAttributes; }
        }

        if (typeof(origVenue.entryExitPoints) != 'undefined') { //TODO: and if # entry points > 0
            origVenue.entryExitPoints.forEach(function(origEntryExitPoint) {
                let point = new OpenLayers.Geometry.Point(origEntryExitPoint.point.coordinates[0],origEntryExitPoint.point.coordinates[1]);
                point.transform(displayProjection, projection);
                let entryExitPoint = new NavigationPointMEP(point);

                entryExitPoint._name = origEntryExitPoint.name;
                entryExitPoint._entry = origEntryExitPoint.entry;
                entryExitPoint._exit = origEntryExitPoint.exit;
                entryExitPoint._isPrimary = origEntryExitPoint.primary;

                landmark.attributes.entryExitPoints.push(entryExitPoint);
            });
        }

        W.model.actionManager.add(new wazeActionAddLandmark(landmark));

        if (typeof(origVenue.openingHours) != 'undefined') {
            let hours = [];
            origVenue.openingHours.forEach(function (hourEntry) {
                hours.push(new wazeOpeningHour(hourEntry));
            });
            W.model.actionManager.add(new wazeActionUpdateObject(landmark, { openingHours: hours }));
        }

        if (typeof(origVenue.streetID) != 'undefined') {

            //TODO: Make this a separate function and use for all address building, cache after built by streetID
            var addressParts = {
                streetName: "",
                emptyStreet: false,
                cityName: "",
                emptyCity: false,
                streetID: origVenue.streetID,
                stateID: null,
                countryID: null,
                addressFormShown: false,
                editable: true,
                fullAddress: "",
                ttsLocales: [W.Config.tts.default_locale],
                altStreets: new Backbone.Collection,
                newAltStreets: new Backbone.Collection
            };

            venueHistory.streets.objects.forEach(function(street) {
                if (street.id == origVenue.streetID) {
                    if (street.name == "")
                        addressParts.emptyStreet = true;
                    addressParts.streetName = street.name;

                    venueHistory.cities.objects.forEach(function(city) {
                        if (street.cityID == city.id) {
                            if (city.name == "")
                                addressParts.emptyCity = true;
                            addressParts.cityName = city.name;
                            addressParts.stateID = city.stateID;
                            venueHistory.states.objects.forEach(function(state) {
                                if (city.stateID == state.id) {
                                    addressParts.countryID = state.countryID;

                                }
                            });
                        }
                    });
                }
            });


            W.model.actionManager.add(new wazeActionUpdateFeatureAddress(landmark, addressParts,{streetIDField: 'streetID'}));
        }

        if (typeof(origVenue.houseNumber) != 'undefined') {
            W.model.actionManager.add(new wazeActionUpdateObject(landmark,{houseNumber: origVenue.houseNumber}));
        }

        //console.log(landmark);

        let foundFeature = null;
        W.selectionManager._layers.forEach(function(layer) {
            if (layer.featureType == 'venue') {
                layer.features.forEach(function(feature) {
                    if (landmark.geometry.id == feature.geometry.id)
                    {
                        foundFeature = feature;
                    }
                });
            }
        });
        if (foundFeature != null) {
            W.selectionManager.selectFeature(foundFeature);
            W.selectionManager._triggerSelectionChanged()
        }


        var epm = Backbone.Model.extend({
            defaults: {
                uuid: null,
                name: null,
                url: null,
                location: null
            },
            initialize: function() {
                if (null === this.get("uuid")) return this.set({
                    uuid: this.id
                })
            },
            getDetails: function(e) {
                this.set({uuid: e});
                return this.set({
                    name: externalProviderDetails[e].name,
                    location: externalProviderDetails[e].geometry.location,
                    url: externalProviderDetails[e].url
                });

            },
            _getDetailsFromUuid: function(e) {
                return this.set({
                    name: this.get("uuid"),
                    location: "",
                    url: ""
                })
            },
            toJSON: function() {
                return this.get("uuid")
            }
        });

        if (typeof(origVenue.externalProviderIDs) != 'undefined') {
            let ids = [];
            origVenue.externalProviderIDs.forEach(function(externalProviderID) {
                let newID = new epm();
                newID.getDetails(externalProviderID);
                newID.collection = new Backbone.Collection();
                ids.push(newID);
            }); //10521
            W.model.actionManager.add(new wazeActionUpdateObject(landmark,{externalProviderIDs: ids}));
        }
    }

    function displayExternalProviders(venueID, externalProviderIDs) {
        if (typeof(externalProviderIDs) != 'undefined') {
            externalProviderIDs.forEach(function(externalProviderID) {
                let extProv = document.getElementById('wmeFdoExternalProviders-' + venueID);
                let div = document.createElement('div');
                div.innerHTML = externalProviderID;
                extProv.append(div);

                getExternalProviderDetail(externalProviderID, function(data) {
                    let closed = data.permanently_closed;
                    div.innerHTML = '<a href="' + data.url + '" target="_blank">' + externalProviderID + '</a>';
                    if (closed) {
                        div.innerHTML = div.innerHTML + ' <span style="color:red;">closed</span>';
                    }
                });
            });
        }
    }

    function getExternalProviderDetail(externalProviderID, callback) {
        if (googleMapsApi.service == null) {
            googleMapsApi.service = new google.maps.places.PlacesService(googleMapsApi.map);
        }

        if (typeof(externalProviderDetails[externalProviderID]) == 'undefined') {
            let request = {
                placeId: externalProviderID,
                fields: ['name','url','permanently_closed','geometry']
            }
            googleMapsApi.service.getDetails(request, function(place, status) {
                if (status == google.maps.places.PlacesServiceStatus.OK) {
                    externalProviderDetails[externalProviderID] = place;
                    callback(externalProviderDetails[externalProviderID]);
                } else {
                    //TODO: callback with error response
                    callback(null);
                }
            });
        } else {
            callback(externalProviderDetails[externalProviderID]);
        }

    }

    function toggleFilterDropDown() {
        let details = document.getElementById('wmeFdoFilterDetails');
        let openDropDown = false;
        if (details.classList.contains('closed')) {
            details.classList.remove('closed');
        } else {
            details.classList.add('closed');
        }
    }

    function toggleScanStatusDropDown() {
        let details = document.getElementById('wmeFdoScanStatus');
        let openDropDown = false;
        if (details.classList.contains('closed')) {
            details.classList.remove('closed');
        } else {
            details.classList.add('closed');
        }
    }

    function toggleDetailsDropDown(elementId, operationCenterPoint) {
        let details = document.getElementById(elementId);
        let openDropDown = false;
        if (details.classList.contains('closed')) {
            openDropDown = true;
        }
        closeAllDetailsDropDown();
        if (openDropDown) {
            details.classList.remove('closed');
            settings.Enabled = true;
            onLayerCheckboxChanged(settings.Enabled); // TODO: Fix this so the Layer menu checkbox gets updated
            let lonLat = new OpenLayers.LonLat(operationCenterPoint.x, operationCenterPoint.y);
            if(!W.map.getExtent().containsLonLat(lonLat)) {
                W.map.setCenter(lonLat, 6);
            }
        }
    }

    function openDetailsDropDown(elementId, operationCenterPoint) {
        let details = document.getElementById(elementId);
        //details.className = 'element-history-item tx-has-content';
        details.classList.remove('closed');
        details.classList.remove('hidden');
        //    var lonLat = new OpenLayers.LonLat(operationCenterPoint.x, operationCenterPoint.y);
        //W.map.setCenter(operationCenterPoint.toLonLat(), 6);
    }

    function closeDetailsDropDown(elementId) {
        let details = document.getElementById(elementId);
        details.classList.add('closed');
    }

    function closeAllDetailsDropDown() {
        let ul = document.getElementById('wmeFdoTransactionList');
        let items = ul.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let elementId = items[i].id;
            if (elementId.startsWith('entry-')) {
                closeDetailsDropDown(elementId);
            }
        }
    }

    function displayPin(transactionOperation, deletedVenue) {
        let deletedPlacePt=new OpenLayers.Geometry.Point(transactionOperation.objectCentroid.coordinates[0], transactionOperation.objectCentroid.coordinates[1]);
        deletedPlacePt.transform(displayProjection, projection);
        let point = new OpenLayers.Geometry.Point(deletedPlacePt.x, deletedPlacePt.y);
        let style = {strokeColor: 'black',
                     strokeWidth: '5',
                     strokeDashstyle: 'solid',
                     pointRadius: '5',
                     fillOpacity: '1'};
        let feature = new OpenLayers.Feature.Vector(point, {id:transactionOperation.objectID}, style);

        var di = require("Waze/DivIcon");
        var iconA = new di("place-update add_venue low map-marker wmeFdo-place-delete");
        var lonlatA = new OpenLayers.LonLat(deletedPlacePt.x, deletedPlacePt.y);
        // let markerLayer = W.map.getLayerByUniqueName('__wmeFdoMarkerLayer');
        let marker = new OpenLayers.Marker(lonlatA, iconA);
        marker.id = 1;
        marker.events.register('click', marker, function(evt) {
            W.selectionManager.unselectAll();
            selectTab();
            let elementId = 'entry-' + transactionOperation.objectID;
            closeAllDetailsDropDown();
            openDetailsDropDown(elementId, deletedPlacePt);
            document.getElementById(elementId).scrollIntoView();
        });

        addLayers();
        _wmeFdoFeatureLayer.addFeatures([feature]);
        _wmeFdoMarkerLayer.addMarker(marker);
        return feature;
    }

    function addLayers() {
        if (_wmeFdoFeatureLayer == undefined) {
            _wmeFdoFeatureLayer = new OpenLayers.Layer.Vector("_wmeFdoFeatureLayer",{uniqueName: "__wmeFdoFeatureLayer"});
            _wmeFdoMarkerLayer = new OpenLayers.Layer.Markers("_wmeFdoMarkerLayer",{uniqueName: "__wmeFdoMarkerLayer"});
            W.map.addLayer(_wmeFdoFeatureLayer);
            W.map.addLayer(_wmeFdoMarkerLayer);
            if (settings.Enabled) {
                _wmeFdoFeatureLayer.setVisibility(true);
                _wmeFdoMarkerLayer.setVisibility(true);
            } else {
                _wmeFdoFeatureLayer.setVisibility(false);
                _wmeFdoMarkerLayer.setVisibility(false);
            }

        }
    }

    function showDeletedArea(transactionOperation, deletedVenue) {
        let polygon = null;
        if (deletedVenue.geometry.type == 'Polygon') {
            let points = [];
            deletedVenue.geometry.coordinates[0].forEach( function(coord) {
                let point = new OpenLayers.Geometry.Point(coord[0],coord[1]);
                point.transform(displayProjection,projection);
                points.push(point);
            });
            points.push(points[0]);
            let linearRing = new OpenLayers.Geometry.LinearRing(points);
            polygon = new OpenLayers.Geometry.Polygon(linearRing);
        }
        let style = {strokeColor: 'orange',
                     strokeWidth: '4',
                     strokeDashstyle: 'solid',
                     fillOpacity: '0.2'};
        let feature = new OpenLayers.Feature.Vector(polygon, {id:transactionOperation.objectID}, style);
        _wmeFdoFeatureLayer.addFeatures([feature]);
        return feature;
    }

    function getParentCategory(category) {
        // Build subcategory > parent category mapping once
        if (subcategoryToParentCategories.length == 0) {
            let index = 0;
            Object.keys(W.Config.venues.subcategories).forEach(function(parent) {
                let parentName = Object.getOwnPropertyNames(W.Config.venues.subcategories)[index];
                subcategoryToParentCategories[parentName] = parentName;
                W.Config.venues.subcategories[parentName].forEach(function(subcategory) {
                    subcategoryToParentCategories[subcategory] = parentName;
                });
                index++;
            });
        }

        return subcategoryToParentCategories[category];
    }

    function getCategoryIconClass(category) {
        let parentCategory = getParentCategory(category);

        // TODO: Lowercase and replace '_' with '-' instead?
        switch(parentCategory) {
            case 'TRANSPORTATION':
                return 'transportation';
                break;
            case 'PROFESSIONAL_AND_PUBLIC':
                return 'professional-and-public';
                break;
            case 'SHOPPING_AND_SERVICES':
                return 'shopping-and-services';
                break;
            case 'FOOD_AND_DRINK':
                return 'food-and-drink';
                break;
            case 'CULTURE_AND_ENTERTAINEMENT':
                return 'culture-and-entertainement';
                break;
            case 'LODGING':
                return 'lodging';
                break;
            case 'OUTDOORS':
                return 'outdoors';
                break;
            case 'NATURAL_FEATURES':
                return 'natural-features';
                break;
            case 'PARKING_LOT':
                return 'parking-lot';
            default:
                return 'OTHER';
        }

        // WME CSS Example:
        //     #edit-buttons .transportation .item-icon::after {
        //     background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        //         background-position: -13px -70px;
        //        width: 12px;
        //         height: 12px;
        //        }
    }

    function formatStreetName(venueHistory, streetId) {
        // TODO: replace with address cache lookup/build function
        let streetName = null;
        venueHistory.streets.objects.forEach(function(street) {
            if (street.id == streetId) {
                streetName = street.name;
            }
        });

        if (streetName == null) {
            streetName = "";
        }
        return streetName;
    }

    function formatFullStreetName(venueHistory, streetId) {
        // TODO: replace with address cache lookup/build function
        let streetName = null;
        venueHistory.streets.objects.forEach(function(street) {
            if (street.id == streetId) {
                streetName = street.name;
                venueHistory.cities.objects.forEach(function(city) {
                    if (street.cityID == city.id) {
                        if (streetName != '') {
                            streetName = streetName + ", ";
                        }
                        streetName = streetName + city.name;
                        venueHistory.states.objects.forEach(function(state) {
                            if (city.stateID == state.id) {
                                if (city.name != '') {
                                    streetName = streetName + ", ";
                                }
                                streetName = streetName + state.name;
                            }
                        });
                    }
                });
            }
        });

        if (streetName == null) {
            streetName = "";
        }
        return streetName;
    }

    function formatCategories(categories) {
        let output = ''
        categories.forEach(function(category) {
            output = output + ", " + I18n.t("venues.categories." + category);
        });
        output = output.substring(2);

        return output;
    }

    function formatCategoriesNative(categories) {
        let output = ''
        categories.forEach(function(category) {
            output = output + "," + category;
        });
        output = output.substring(1);

        return output;
    }

    function formatExternalProviders(externalProviderIDs) {
        let output = "<ul>";
        if (typeof externalProviderIDs != 'undefined') {
            externalProviderIDs.forEach(function(externalProviderID) {
                output = output + "<li>" + externalProviderID + "</ul>";
            });
        }
        output = output + "</ul>";
        return output;
    }

    function getVenueHistory(venueHistory) {
        let historySummary = {
            html: null,
            isRejectedNew: false,
            isAcceptedDelete: false,
            purDeletedBy: null
        }

        let output = "<ul class='wmeFdoHistorySummaryList'>";
        let transactionCount = 0;
        venueHistory.transactions.objects.forEach(function(venueTransaction) {
            transactionCount++;
            let username = null;
            let usernameHtml = null;
            venueHistory.users.objects.forEach( function(user) {
                if (user.id == venueTransaction.userID) {
                    let userRank = user.rank + 1;
                    username = user.userName + '(' + userRank + ')';
                    usernameHtml = `<a target="_blank" href="https://${window.location.host}/user/editor/${user.userName}">${username}</a>`;
                }
            });

            let detailCount = 0;
            let requestType = venueTransaction.actionType;

            let purText = null;
            let addedPurNotation = false;
            let previousObjectType = null;
            venueTransaction.objects.forEach(function(venueSubObject) {
                purText = null;
                let addDate = false;
                if (venueSubObject.objectType == 'venueUpdateRequest') {

                    if (transactionCount == 2 && previousObjectType == 'venue') {
                        historySummary.purDeletedBy = username;
                    }
                    previousObjectType = venueSubObject.objectType;

                    if (!addedPurNotation) {
                        addedPurNotation = true;
                        requestType = requestType + " (PUR)";
                    }
                    if (typeof(venueSubObject.oldValue) != 'undefined') {
                        let rejected = false;
                        if (typeof(venueSubObject.oldValue.approve) == 'undefined') {
                            purText = "(Unknown Action) ";
                        } else if (venueSubObject.oldValue.approve) {
                            purText = "Approved ";
                        } else {
                            purText = "Rejected ";
                            rejected = true;
                        }

                        if (typeof(venueSubObject.oldValue.type) == 'undefined'
                            && (venueSubObject.oldValue.id.includes('-') && !venueSubObject.oldValue.id.includes('.'))
                           ) {
                            // Older entries did not contain the type, test to see if it's a rejected image
                            venueSubObject.oldValue.type = 'IMAGE';
                        }

                        if (venueSubObject.oldValue.type == 'VENUE') {
                            //rejectedNew = true;
                            purText = purText + "New Place";
                            addDate = true;
                            if (rejected) {
                                historySummary.isRejectedNew = true;
                            }

                        } else if (venueSubObject.oldValue.type == 'REQUEST') {
                            if (venueSubObject.oldValue.subType == 'DELETE') {
                                purText = purText + "Delete Request";
                                if (!rejected) {
                                    historySummary.isAcceptedDelete = true;
                                }
                            } else if (venueSubObject.oldValue.subType == 'UPDATE') {
                                purText = purText + "Update Request";
                            }

                            addDate = true;

                        } else if (venueSubObject.oldValue.type == 'IMAGE') {

                            purText += "Photo";
                            purText = `<a target="_blank" href="https://venue-image.waze.com/${venueSubObject.oldValue.id}">${purText}</a>`;
                        }

                        if (addDate) {
                            let origPurUnixTime = parseInt(venueSubObject.oldValue.id.split('.')[0]);
                            let newPurDate = timeConverter(origPurUnixTime);
                            purText = purText + " (" + newPurDate + ")";
                        }

                    } else if (typeof(venueSubObject.newValue) != 'undefined') {
                        purText = "Submitted ";
                        if (typeof(venueSubObject.newValue.type) != 'undefined') {
                            if (venueSubObject.newValue.type == 'VENUE') {
                                //rejectedNew = true;
                                purText = purText + "New Place";
                                addDate = true;

                            } else if (venueSubObject.newValue.type == 'REQUEST') {
                                if (venueSubObject.newValue.subType == 'DELETE') {
                                    purText = purText + "Delete Request";
                                } else if (venueSubObject.newValue.subType == 'UPDATE') {
                                    purText = purText + "Update Request";
                                }

                                addDate = true;
                            }
                        } else if (venueSubObject.newValue.id.includes('-') && !venueSubObject.newValue.id.includes('.')) {
                            // Older entries did not contain the type, test to see if it's a rejected image
                            //venueSubObject.oldValue.type = 'IMAGE';
                            purText += "Photo";
                            purText = `<li><a target="_blank" href="https://venue-image.waze.com/${venueSubObject.newValue.id}">Rejected Photo</a></li>`;
                        }
                        if (addDate) {
                            let origPurUnixTime = parseInt(venueSubObject.newValue.id.split('.')[0]);
                            let newPurDate = timeConverter(origPurUnixTime);
                            purText = purText + " (" + newPurDate + ")";
                        }
                    }


                }
            });

            output = output + "<li><div>" + timeConverter(venueTransaction.date) + " " + requestType + " " + usernameHtml + "</div>";
            output = output + '<ul class="historySummaryItem">';
            if (purText != null) {
                output = output + '<li>' + purText + '</li>';
            }
            if (typeof(venueTransaction.objects[0].newValue) != 'undefined' && typeof(venueTransaction.objects[0].newValue.name) != 'undefined')
            {
                detailCount++;
                output = output + '<li>Name: ' + venueTransaction.objects[0].newValue.name + '</li>';
                if (venueTransaction.objects[0].actionType == 'UPDATE') {
                    output = output + '<li>Was: ' + venueTransaction.objects[0].oldValue.name + '</li>';
                }

            }
            //    if (typeof(venueTransaction.objects[0].oldValue) != 'undefined' && venueTransaction.objects[0].oldValue.type == 'IMAGE') {
            //        output = output + `<li><a target="_blank" href="https://venue-image.waze.com/${venueTransaction.objects[0].oldValue.id}">Rejected Photo</a></li>`;
            //    }
            output = output + '</ul>';
            if (detailCount > 0) {
                output = output + '</li style="margin-bottom:4px">';
            } else {
                output = output + "</li>";
            }
            // console.log(venueTransaction);
        });
        output = output + "</ul>";
        historySummary.html = output;
        return historySummary;
    }

    function applyFiltersToResults() {
        let ul = document.getElementById('wmeFdoTransactionList');
        let items = ul.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let elementId = items[i].id;
            let element = document.getElementById(elementId);
            if (elementId.startsWith('entry-')) {
                setFilteredState(element);
            }
        }

    }

    function setFilteredState(element) {
        let filtered = false;

        let filterTypeCount = 0;
        if (!settings.FilterResidential) {
            filterTypeCount++;
            if (element.getAttribute('data-categories') == 'RESIDENCE_HOME') {
                filtered = true;
            }
        }
        if (!settings.FilterParkingLot) {
            filterTypeCount++;
            if (element.getAttribute('data-categories') == 'PARKING_LOT') {
                filtered = true;
            }
        }
        if (!settings.FilterAnyType) {
            filterTypeCount++;
            if (element.getAttribute('data-categories') != 'RESIDENCE_HOME' && element.getAttribute('data-categories') != 'PARKING_LOT') {
                filtered = true;
            }
        }

        let allTypesFiltered = false;
        if (filterTypeCount == 3) {
            allTypesFiltered = true;
        }

        if (element.getAttribute('data-pur-new-rejected') == "true") {
            if (!settings.FilterNewRejected) {
                filtered = true;
            } else if (allTypesFiltered && filtered) {
                filtered = false;
            }
        }

        //
        if (element.getAttribute('data-pur-delete-request') == "true") {
            if (!settings.FilterDeleteRequest) {
                filtered = true;
            } else if (allTypesFiltered && filtered) {
                filtered = false;
            }
        }

        if (filtered) {
            element.classList.add('hidden');
        } else {
            element.classList.remove('hidden');
        }
    }

    function endScan(reasonText, setEndedFlag) {
        if (setEndedFlag) {
            var findButton = document.getElementById('wmeFdoFindButton');
            findButton.className = 'btn btn-success center-block';

            var cancelButton = document.getElementById('wmeFdoCancelButton');
            cancelButton.className = 'btn btn-warning center-block hidden';

            $('#wmeFdoTabFind').data("status","ended");

            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            cancelScanNew();
        }
        $('#wmeFdoScanCurrentStatus')[0].innerText = reasonText;
    }

    async function findObjects() {

        let editorList = [];

        let searchAreaGeometry = getSearchArea();
        var lookFor = {
            objectType:'Venue',
            actionType:'DELETE',
            searchArea: searchAreaGeometry,
            limitToScreen: settings.LimitToScreen,
            includeSelf: settings.IncludeSelf

        }

        //var centerCoords = W.map.getCenter().clone().transform(W.map.projection, W.map.displayProjection);
        if (settings.NearbyEditors) {

            lookFor.limitToScreen = true;
            lookFor.includeSelf = true;

            //TODO: use W.model if zoom <= 3
            let editorListScreen = getNearbyEditors();

            let featuresList = await getScreenFeatures();

            let editorListFeatures = getNearbyEditorsFromFeatures(featuresList);

            let editorListTemp = [];

            editorListFeatures.forEach(function(userID) {
                editorListTemp[userID] = true;
                apiEndpointWazeCom.users.total++;
            });

            Object.keys(editorListTemp).forEach(function(userID) {
                editorList.push(userID);
            });

            processUsers(lookFor, editorList);

        } else {

            lookFor.limitToScreen = settings.LimitToScreen;
            lookFor.includeSelf = settings.IncludeSelf;

            Date.prototype.addDays = function(days) {
                var date = new Date(this.valueOf());
                date.setDate(date.getDate() + days);
                return date;
            }

            let startDate = null;
            let startDateInput = document.getElementById('wmeFdoFindDate');
            let dateInput = null;
            if (Date.parse(startDateInput.value) != null) {
                dateInput = new Date(startDateInput.value).addDays(1);
                startDate = dateToUuid(dateInput);
                dateInput = new Date(dateInput).getTime();
            } else {
                dateInput = new Date.now();
            }

            var transactionLookup = {
                username:$('#wmeFdoEditorName')[0].value,
                userId:null,
                nextTransactionDate:startDate,
                iteration:0,
                maxIterations:100000
            };

            if (transactionLookup.username == null || transactionLookup.username == "") {
                endScan('Please enter an editor name!', true);
            } else {
                let requestParamsUserProfile = requestParams(getApiUrlUserProfile(transactionLookup.username));
                requestStatus[requestParamsUserProfile.url] = defaultRequestStatus();

                apiEndpointWazeCom.requests.push(requestParamsUserProfile);
                getEndpointResult(apiEndpointWazeCom, requestParamsUserProfile, userProfile => {
                    if (typeof(userProfile.userID) == 'undefined') {
                        endScan('Editor name not found!', true);
                    } else {
                        apiEndpointWazeCom.users.total++;
                        addUserCache(userProfile.userID, userProfile.username, userProfile.rank)
                        if (userProfile.firstEditDate > earliestScanDate) {
                            userCache[userProfile.userID].earliestDate = userProfile.firstEditDate;
                        } else {
                            userCache[userProfile.userID].earliestDate = earliestScanDate;
                        }
                        userCache[userProfile.userID].latestDate = dateInput;
                        editorList.push(userProfile.userID);
                    }
                    processUsers(lookFor, editorList);
                });
            }
        }
    }

    function processUsers(lookFor, editorList) {
        $('#wmeFdoScanStatus .wmeFdoProgress').removeClass('hidden');
        apiEndpointWazeCom.gotFeatures = true;
        editorList.forEach(function (userID) {

            Date.prototype.addDays = function(days) {
                var date = new Date(this.valueOf());
                date.setDate(date.getDate() + days);
                return date;
            }

            let startDate = null;
            let startDateInput = document.getElementById('wmeFdoFindDate');
            if (Date.parse(startDateInput.value) != null) {
                let dateInput = new Date(startDateInput.value).addDays(1);
                startDate = dateToUuid(dateInput);
            }

            var transactionLookup = {
                username:null,
                userId:userID,
                nextTransactionDate:startDate,
                iteration:0,
                maxIterations:100000
            };

            let latestDate = timeConverter(userCache[userID].latestDate + DAY_MS);
            transactionLookup.nextTransactionDate = dateToUuid(latestDate);

            let username = `<a target="_blank" href="https://${window.location.host}/user/editor/${userCache[userID].username}">${userCache[userID].username}(${(userCache[userID].rank + 1)})</a>`;
            $('#wmeFdoStatusUsersList').append('<li id="wmeFdoStatusUser-' + userID + '">'
                                               + username + ': <span class="wmeFdoStatusUser">Not Started</span>'
                                               + '<div class="wmeFdoProgress"><div class="wmeFdoComplete"></div></div>'
                                               + '<div class="wmeFdoStatusUserDates">'
                                               + '<div class="wmeFdoStatusUserLatest">' + timeConverterNoTime(userCache[userID].latestDate) + '</div>'
                                               + '<div class="wmeFdoStatusUserEarliest">' + timeConverterNoTime(userCache[userID].earliestDate) + '</div>'
                                               + '<div style="clear:both;"></div>'
                                               + '</div>'
                                               + '</li>');
            userCache[userID].previousDate = userCache[userID].latestDate + DAY_MS;
            let totalMs = (userCache[userID].latestDate + DAY_MS) - (userCache[userID].earliestDate - DAY_MS);
            userCache[userID].totalMs = totalMs;
            apiEndpointWazeCom.progress.totalMs += userCache[userID].totalMs;

            beginFind(lookFor, transactionLookup);
        });
    }

    function getSearchArea() {
        var theVenue = null;
        var count = 0;
        for (var v in W.model.venues.objects) {
            if (W.model.venues.objects.hasOwnProperty(v) === false) {
                continue;
            }
            var venue = W.model.venues.objects[v];
            if (venue.isPoint() === true) {
                continue;
            }
            if ($.isNumeric(venue.attributes.id) && parseInt(venue.attributes.id) <= 0) {
                theVenue = venue;
                count++;
            }
        }

        if (count === 0) {
            console.log("using screen");
            return W.map.getExtent().toGeometry();
        }
        if (theVenue.geometry.components.length !== 1) {
            alert("Can't parse the geometry");
            return;
        } else {
            console.log("using unsaved place area");
            return theVenue.geometry.clone();

        }
    }

    function getNearbyEditors() {
        $('#wmeFdoScanCurrentStatus')[0].innerText = 'Finding Nearby Editors';

        //let earliestDate = parseInt(new Date('2016-12-31').getTime());
        let editorList = [];
        let editorListReturn = [];

        Object.keys(W.model.venues.objects).forEach(function (venueId) {
            let venue = W.model.venues.objects[venueId].attributes;
            if (typeof(venue.createdOn) != 'undefined' && venue.createdOn > earliestScanDate) {
                editorList[venue.createdBy] = venue.createdOn;
            }
            if (typeof(venue.updatedOn) != 'undefined' && venue.updatedOn > earliestScanDate) {
                editorList[venue.updatedBy] = venue.updatedBy;
            }
            //console.log(W.model.venues.objects[venueId].attributes.createdBy)
        });

        Object.keys(W.model.segments.objects).forEach(function (segmentId) {
            let segment = W.model.segments.objects[segmentId].attributes;
            if (typeof(segment.createdOn) != 'undefined' && segment.createdOn > earliestScanDate) {
                editorList[segment.createdBy] = segment.createdOn;
            }
            if (typeof(segment.updatedOn) != 'undefined' && segment.updatedOn > earliestScanDate) {
                editorList[segment.updatedBy] = segment.updatedBy;
            }
            //console.log(W.model.venues.objects[venueId].attributes.createdBy)
        });

        Object.keys(editorList).forEach(function (editorId) {
            if (editorId != 'undefined') {
                editorListReturn.push(editorId);
            }
        });

        return editorListReturn;
    }

    function getNearbyEditorsFromFeatures(featuresList) {
        $('#wmeFdoScanCurrentStatus')[0].innerText = 'Finding Nearby Editors';


        let editorList = [];
        let editorListReturn = [];

        featuresList.forEach(function (features) {
            if (typeof(features.error) == 'undefined') {

                if (typeof(features.venues) != 'undefined') {
                    features.users.objects.forEach(function (user) {
                        addUserCache(user.id, user.userName, user.rank);

                    });
                }

                if (typeof(features.venues) != 'undefined') {
                    features.venues.objects.forEach(function (venue) {
                        if (typeof(venue.createdOn) != 'undefined' && venue.createdOn > earliestScanDate) {
                            editorList[venue.createdBy] = venue.createdOn;
                            updateUserCache(venue.createdBy, venue.createdOn);

                        }
                        if (typeof(venue.updatedOn) != 'undefined' && venue.updatedOn > earliestScanDate) {
                            editorList[venue.updatedBy] = venue.updatedBy;
                            updateUserCache(venue.updatedBy, venue.updatedOn);
                        }
                        seenVenueIds.push(venue.id); // Venue exists, no need to check its history
                    });
                }

                if (typeof(features.segments) != 'undefined') {
                    features.segments.objects.forEach(function (segment) {
                        if (typeof(segment.createdOn) != 'undefined' && segment.createdOn > earliestScanDate) {
                            editorList[segment.createdBy] = segment.createdOn;
                            updateUserCache(segment.createdBy, segment.createdOn);
                        }
                        if (typeof(segment.updatedOn) != 'undefined' && segment.updatedOn > earliestScanDate) {
                            editorList[segment.updatedBy] = segment.updatedBy;
                            updateUserCache(segment.updatedBy, segment.updatedOn);
                        }
                    });
                }
            }
        });

        Object.keys(editorList).forEach(function (editorId) {
            if (editorId != 'undefined') {
                editorListReturn.push(editorId);
            }
        });

        return editorListReturn;
    }

    function addUserCache(userID, userName, rank) {
        if (typeof(userCache[userID]) == 'undefined') {
            userCache[userID] = defaultUserCache(userID, userName, rank);
        }
    }

    function updateUserCache(userID, actionDate) {
        if (typeof(userCache[userID]) == 'undefined') {
        } else {
            if (actionDate > userCache[userID].latestDate) {
                userCache[userID].latestDate = actionDate;
            }

            if (actionDate < userCache[userID].earliestDate) {
                userCache[userID].earliestDate = actionDate;
            }
        }
    }

    async function getScreenFeatures() {

        return new Promise( async resolve => {
            let urlList = [];
            let paramsList = [];
            let featuresList = [];
            let maxDegrees = 0.125;
            let minDegrees = 0.0625;
            let extent = W.map.getExtent().transform(projection,displayProjection);
            //NW: LT - left top
            //NE: RT - right top
            //SW: LB - left bottom
            //SE: RB - right bottom

            let geoLT = new OpenLayers.Geometry.Point(extent.left,extent.top);
            let geoRB = new OpenLayers.Geometry.Point(extent.right,extent.bottom);
            let geoLTSplit = new OpenLayers.Geometry.Point(geoLT.x,geoLT.y);
            let geoRBSplit = new OpenLayers.Geometry.Point(geoRB.x,geoRB.y);
            let geoTempY = new OpenLayers.Geometry.Point(geoLT.x,geoLT.y);

            let xSplit = geoLT.x;
            let ySplit = geoLT.y;

            //console.log(extent);
            //console.log(geoLT);
            //console.log(geoRB);

            while (ySplit >= geoRB.y) {
                geoTempY.y = geoTempY.y - maxDegrees;

                ySplit = geoTempY.y;
                let degreesFromGeoRBY = geoRB.y - ySplit;

                if (ySplit > geoRB.y)
                    geoRBSplit.y = ySplit
                else if (degreesFromGeoRBY <= minDegrees)
                    geoRBSplit.y = ySplit - (minDegrees - degreesFromGeoRBY);
                else
                    geoRBSplit.y = geoRB.y;

                while (xSplit <= geoRB.x) {
                    geoTempY.x = geoTempY.x + maxDegrees;

                    xSplit = geoTempY.x;
                    let degreesFromGeoRBX = geoRB.x - xSplit;
                    if (xSplit > geoRB.x)
                        geoRBSplit.x = xSplit
                    else if (degreesFromGeoRBX <= minDegrees)
                        geoRBSplit.x = xSplit - (minDegrees - degreesFromGeoRBX);
                    else
                        geoRBSplit.x = geoRB.x;

                    //  let bboxGeoLT = geoLTSplit.clone();
                    //  let bboxGeoEB = geoEBSplit.clone();
                    let bbox = `${geoLTSplit.x},${geoLTSplit.y},${geoRBSplit.x},${geoRBSplit.y}`;
                    let url = getApiUrlFeatures(bbox);
                    urlList.push(url);
                    //console.log(url);
                    let params = requestParams(url);
                    requestStatus[url] = defaultRequestStatus();
                    // let features = null;

                    //   features = await getApiRequest(params);
                    //   featuresList.push(features);

                    paramsList.push(params);
                    apiEndpointWazeCom.requests.push(params);

                    geoLTSplit.x = xSplit;
                }

                geoLTSplit.y = ySplit;
                geoLTSplit.x = geoLT.x;
                xSplit = geoLT.x;
                geoTempY.x = xSplit;
            }
            getEndpointResultFromArray(apiEndpointWazeCom, paramsList, featuresList => {
                resolve(featuresList);
            });

        });
    }

    function getApiUrlUserProfile(username) {
        return `https://${window.location.host}${W.Config.api_base}/UserProfile/Profile?username=${username}`;
    }

    function getApiUrlTransactions(userId, nextTransactionDate) {
        let url = `https://${window.location.host}${W.Config.api_base}/UserProfile/Transactions?userID=${userId}`;
        if (nextTransactionDate != null) {
            url = url + '&till=' + nextTransactionDate;
        }
        return url;
    }

    function getApiUrlElementHistory(objectType, objectId, nextTransactionDate) {
        let url = `https://${window.location.host}${W.Config.api_base}/ElementHistory?objectType=${objectType}&objectID=${objectId}`;
        if (nextTransactionDate != null) {
            url = url + '&till=' + nextTransactionDate;
        }
        return url;
    }

    function getApiUrlFeatures(bbox) {
        let url = `https://${window.location.host}${W.Config.api_base}/Features?language=en-US&bbox=${bbox}&roadTypes=2%2C3%2C4%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22&venueLevel=4&venueFilter=3%2C3%2C3`;
        return url;
    }

    async function highlightPin(pin) {
        if (pin.onScreen()) {
            let layer = W.map.getLayerByUniqueName('__wmeFdoFeatureLayer');

            let enableAnimation = false;
            if (enableAnimation) {
                pin.data.mouseout = false;
                pin.style.strokeColor = 'red';
                for (var i=100;i>=1;i--) {
                    await wait(10);
                    pin.style.strokeWidth = i;
                    layer.redraw();
                    if (pin.data.mouseout == true) {
                        i = 1;
                        returnPin(pin);
                    }
                }
                pin.style.strokeWidth = 4;
                pin.style.pointRadius = 5;
                pin.style.strokeColor = 'orange';
                layer.redraw();
                if (pin.data.mouseout == true) {
                    returnPin(pin);
                }
            } else {
                pin.style.strokeWidth = 4;
                pin.style.pointRadius = 5;
                pin.style.strokeColor = 'orange';
                layer.redraw();
            }
        }
    }

    function returnPin(pin) {
        let style = {strokeColor: 'black',
                     strokeWidth: '4',
                     strokeDashstyle: 'solid',
                     pointRadius: '4',
                     fillOpacity: '1'};

        if (pin.onScreen()) {
            let layer = W.map.getLayerByUniqueName('__wmeFdoFeatureLayer');
            pin.data.mouseout = true;
            //pin.style.strokeColor = 'black';
            //pin.style.strokeWidth = 5;
            pin.style = style;
            layer.redraw();
        }
    }

    function createSettingsCheckbox($div, settingID, textDescription) {
        let $checkbox = $('<input>', {type:'checkbox', id:settingID, class:'wmeFdoSettingsCheckbox'});
        $div.append(
            $('<div>', {class:'controls-container'}).css({paddingTop:'2px'}).append(
                $checkbox,
                $('<label>', {for:settingID}).text(textDescription).css({whiteSpace:'pre-line'})
            )
        );
        return $checkbox;
    }

    function setChecked(checkboxId, checked) {
        $('#' + checkboxId).prop('checked', checked);
    }

    function saveSettings() {
        if (localStorage) {
            var localsettings = {
                Enabled: settings.Enabled,
                IncludeSelf: settings.IncludeSelf,
                LimitToScreen: settings.LimitToScreen,
                NearbyEditors: settings.NearbyEditors,
                FilterResidential: settings.FilterResidential,
                FilterParkingLot: settings.FilterParkingLot,
                FilterAnyType: settings.FilterAnyType,
                FilterNewRejected: settings.FilterNewRejected,
                FilterDeleteRequest: settings.FilterDeleteRequest
            };

            localStorage.setItem("wmeFdo_Settings", JSON.stringify(localsettings));
        }
    }

    function loadSettings() {
        var loadedSettings = $.parseJSON(localStorage.getItem("wmeFdo_Settings"));
        var defaultSettings = {
            Enabled: true,
            IncludeSelf: true,
            LimitToScreen: true,
            NearbyEditors: true,
            FilterResidential: true,
            FilterParkingLot: true,
            FilterAnyType: true,
            FilterNewRejected: true,
            FilterDeleteRequest: true
        };
        settings = loadedSettings ? loadedSettings : defaultSettings;
        for (var prop in defaultSettings) {
            if (!settings.hasOwnProperty(prop))
                settings[prop] = defaultSettings[prop];
        }

    }

    function prepareTab() {
        let tabs = document.getElementById('user-tabs');
        let items = tabs.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let a = items[i].getElementsByTagName('a')[0]
            if (a.href.includes('sidepanel-fido')) {
                a.id = "wmeFdoTab";
                a.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            }
        }
    }

    function selectTab() {
        let tabs = document.getElementById('user-tabs');
        let items = tabs.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let a = items[i].getElementsByTagName('a')[0]
            if (a.href.includes('sidepanel-fido')) {
                items[i].getElementsByTagName('a')[0].click();
            }
        }
    }

    function dateToUuid(inputDate) {
        let timestamp = parseInt(new Date(inputDate).getTime());
        // uuid = "a02102cd-19df-11e9-bd87-1201fde9baf6"
        // timestamp = 1547678386643;
        let a = timestamp * 10000;
        let b = a + 122192928000000000;
        let c = b.toString(16);
        //if (c.length % 2) {
        //   c = '0' + c;
        //}
        let d = c.substring(7) + '-' + c.substring(3,7) + '-1' + c.substring(0,3) + '-bd87-1201fde9baf6';

        return d;
    }

    function get_time_int(uuid_str) {
        var uuid_arr = uuid_str.split( '-' ),
            time_str = [
                uuid_arr[ 2 ].substring( 1 ),
                uuid_arr[ 1 ],
                uuid_arr[ 0 ]
            ].join( '' );
        return parseInt( time_str, 16 );
    };

    function uuidToUnixTime(uuid_str) {
        var int_time = get_time_int( uuid_str ) - 122192928000000000,
            int_millisec = Math.floor( int_time / 10000 );
        return int_millisec;
    };

    function uuidToDate(uuid_str) {
        var int_time = get_time_int( uuid_str ) - 122192928000000000,
            int_millisec = Math.floor( int_time / 10000 );
        //return new Date( int_millisec );
        return timeConverter(int_millisec);
    };

    function formatCurrentDate() {
        var today = new Date();
        var dd = today.getDate();
        var mm = today.getMonth() + 1; //January is 0!
        var yyyy = today.getFullYear();

        if (dd < 10) {
            dd = '0' + dd;
        }

        if (mm < 10) {
            mm = '0' + mm;
        }

        return yyyy + '-' + mm + '-' + dd;
    }

    function timeConverter(UNIX_timestamp){
        var a = new Date(UNIX_timestamp);
        var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        var year = a.getFullYear();
        var month = months[a.getMonth()];
        var date = a.getDate();
        if (date.toString().length == 1) {
            date = "0" + date;
        }
        var hour = a.getHours();
        if (hour.toString().length == 1) {
            hour = "0" + hour;
        }
        var min = a.getMinutes();
        if (min.toString().length == 1) {
            min = "0" + min;
        }
        var sec = a.getSeconds();
        if (sec.toString().length == 1) {
            sec = "0" + sec;
        }
        //Dec 08, 2018
        var time = month + ' ' + date + ', ' + year + ' ' + hour + ':' + min;
        return time;
    }

    function timeConverterNoTime(UNIX_timestamp){
        var a = new Date(UNIX_timestamp);
        var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        var year = a.getFullYear();
        var month = months[a.getMonth()];
        var date = a.getDate();
        if (date.toString().length == 1) {
            date = "0" + date;
        }
        var hour = a.getHours();
        if (hour.toString().length == 1) {
            hour = "0" + hour;
        }
        var min = a.getMinutes();
        if (min.toString().length == 1) {
            min = "0" + min;
        }
        var sec = a.getSeconds();
        if (sec.toString().length == 1) {
            sec = "0" + sec;
        }
        //Dec 08, 2018
        var time = month + ' ' + date + ', ' + year;
        return time;
    }

    async function wait(ms) {
        return new Promise(resolve => {
            setTimeout(resolve, ms);
        });
    }

    // ------------- Begin Web Request/Rate Limiting -----------

    var apiEndpointWazeCom = null;

    function defaultEndpointSettings() {
        return {
            state: 'idle',
            isRunning: false,
            requests: [],
            results: [],
            requestInterval: null,
            statusInterval: null,
            count: 0,
            beginTime: Date.now(),
            timeout: 100000,
            gotFeatures: false,
            rateLimit: {
                baseDelay: 50,
                addedDelay: 0,
                stepUp: 20,
                stepDown: 10,
                lastCheck: Date.now(),
                lastAdjusted: Date.now(),
                intervalDelay: 5000,
                checkInterval: null,
                errorCount: 0,
                errorCountMax: 50
            },
            users: {
                total: 0,
                complete: 0
            },
            progress: {
                totalMs: 0,
                completeMs: 0
            }
        };
    }

    function defaultUserCache(userID, username, rank) {
        return {
            userID: userID,
            username: username,
            rank: rank,
            isRunning: false,
            currentScanDate: null,
            earliestDate: Date.now(),
            latestDate: 0,
            previousDate: 0,
            totalMs: 0,
            completeMs: 0
        }
    }

    function defaultUserProgress(userID) {
        return {
            userID: userID,
            username: null,
            rank: null,
            isRunning: false,
            currentScanDate: null,
            earliestDate: Date.now(),
            previousDate: 0,
            latestDate: 0,
            totalMs: 0
        }
    }

    function beginScanNew() {
        // start api request handler
        apiEndpointWazeCom = defaultEndpointSettings();
        apiEndpointWazeCom.state = 'running';
        apiEndpointWazeCom.isRunning = true;
        apiEndpointWazeCom.requestInterval = setInterval(function() { apiHandlerWazeCom(apiEndpointWazeCom) }, getRateLimit(apiEndpointWazeCom));
        apiEndpointWazeCom.rateLimit.checkInterval = setInterval(function() { setRateLimitAdjustDown(apiEndpointWazeCom) }, apiEndpointWazeCom.rateLimit.intervalDelay);
        apiEndpointWazeCom.statusInterval = setInterval(function() { populateStatus(); }, 1000);
        //$('#wmeFdoScanStatus .wmeFdoProgress').removeClass('hidden');
    }

    function cancelScanNew() {
        // end api request handler
        apiEndpointWazeCom.state = 'cancelled';
        apiEndpointWazeCom.isRunning = false;
        clearInterval(apiEndpointWazeCom.requestInterval);
        clearInterval(apiEndpointWazeCom.rateLimit.checkInterval);
        clearInterval(apiEndpointWazeCom.statusInverval);
        $('#wmeFdoScanCurrentStatus')[0].innerText = 'Cancelled';
    }

    function apiHandlerWazeCom(apiEndpoint) {
        if (apiEndpoint.requests.length > 0) {
            //console.log("Handler " + apiEndpoint.count);
            let requestParams = apiEndpoint.requests.shift();
            getApiWebRequest(apiEndpoint, requestParams);
            //apiEndpointWazeCom.count++;
        }
    }

    function getApiWebRequest(apiEndpoint, requestParams) {
        apiEndpoint.count++;
        $.ajax({
            type: 'GET',
            url: requestParams.url,
            success: function(data) {
                //requestStatus[requestParams.url].status = 'successful';
                if (typeof(data) == 'undefined') {
                    debugger;
                }
                apiEndpoint.results[requestParams.url] = data;
            },
            statusCode: {
                406: function() { // Not Acceptable - bbox invalid?
                    apiEndpoint.results[requestParams.url] = {"error":true,reason:"error"};
                },
                429: function() { // Rate limit
                    setRateLimitAdjustUp(apiEndpoint);
                    apiEndpoint.requests.push(requestParams);
                },
                500: function() {
                    //debugger;
                    apiEndpoint.results[requestParams.url] = {"error":true,reason:"error"};
                }
            }
        });
    }

    function getRateLimit(apiEndpoint) {
        return apiEndpoint.rateLimit.baseDelay + apiEndpoint.rateLimit.addedDelay;
    }

    function setRateLimitAdjustUp(apiEndpoint) {
        let currentTime = Date.now();
        if (currentTime - apiEndpoint.rateLimit.lastAdjusted > 1000) {
            apiEndpoint.rateLimit.lastAdjusted = currentTime;
            apiEndpoint.rateLimit.errorCount++;
            apiEndpoint.rateLimit.addedDelay += apiEndpoint.rateLimit.stepUp;
            clearInterval(apiEndpoint.requestInterval);
            apiEndpoint.requestInterval = setInterval(function() { apiHandlerWazeCom(apiEndpoint) }, getRateLimit(apiEndpoint));
            console.log('rate limit adjusted up: ' + getRateLimit(apiEndpoint));
        }
    }

    function setRateLimitAdjustDown(apiEndpoint) {
        if (apiEndpoint.rateLimit.addedDelay > 0 && Date.now() - apiEndpoint.rateLimit.lastAdjusted > apiEndpoint.rateLimit.intervalDelay) { //&& apiEndpoint.rateLimit.errorCount <= apiEndpoint.rateLimit.errorCountMax) {
            apiEndpoint.rateLimit.addedDelay -= apiEndpoint.rateLimit.stepDown;
            clearInterval(apiEndpoint.requestInterval);
            apiEndpoint.requestInterval = setInterval(function() { apiHandlerWazeCom(apiEndpoint) }, getRateLimit(apiEndpoint));
            console.log('rate limit adjusted down: ' + getRateLimit(apiEndpoint));
        }
    }

    function getEndpointResultFromArray(apiEndpoint, paramsList, callback) {
        waitForAllUrlResults(apiEndpoint, paramsList, complete => {
            let resultList = [];
            paramsList.forEach(function (params) {
                getEndpointResult(apiEndpoint, params, result => {
                    resultList.push(result);
                });
            });
            callback(resultList);
        });
    }

    function populateStatus() {
        let runningSeconds = (Date.now() - apiEndpointWazeCom.beginTime) / 1000;
        let requestsPerSecond = apiEndpointWazeCom.count / runningSeconds;

        //console.log('queued requests: ' + Object.keys(apiEndpoint.requests).length + ', queued results: ' + Object.keys(apiEndpoint.results).length);
        //console.log('users: ' + apiEndpoint.users.complete + '/' + apiEndpoint.users.total + ' complete, ' + (apiEndpoint.users.total - apiEndpoint.users.complete) + ' remaining');
        //console.log('requests per second: ' + requestsPerSecond);
        let remainingUsers = apiEndpointWazeCom.users.total - apiEndpointWazeCom.users.complete;
        $('#wmeFdoRequests')[0].innerText = apiEndpointWazeCom.count;
        $('#wmeFdoRequestsPerSecond')[0].innerText = Math.round(requestsPerSecond);

        if (apiEndpointWazeCom.gotFeatures && remainingUsers == 0) {
            endScan('Complete', true);
        }
    }

    async function getEndpointResult(apiEndpoint, params, callback) {
        let timeSinceRequest = Date.now() - params.initiated;
        //console.log(typeof(apiEndpoint.results[params.url]));

        while ( typeof(apiEndpoint.results[params.url]) == 'undefined' && apiEndpoint.isRunning ) {
            // removed  && timeSinceRequest < apiEndpoint.timeout
            //console.log(typeof(apiEndpoint.results[params.url]));
            await wait(50);
            timeSinceRequest = Date.now() - params.initiated;
            //console.log('waiting for: ' + params.url);
        }
        //if (timeSinceRequest >= apiEndpoint.timeout) {
        //    console.log('request timeout'); //TODO: handle this condition better
        // }

        let result = apiEndpoint.results[params.url];
        delete apiEndpoint.results[params.url];
        callback(result);
    }

    async function waitForAllUrlResults(apiEndpoint, paramsList, callback) {
        let complete = false;
        let listCount = paramsList.length;

        while (!complete && apiEndpoint.isRunning) {
            let foundCount = 0;
            await wait(50);
            paramsList.forEach(function (params) {
                let foundUrl = false;
                if (typeof(apiEndpoint.requests[params.url]) == 'undefined' && typeof(apiEndpoint.results[params.url]) != 'undefined') {
                    foundUrl = true;
                    foundCount++;
                }
            });
            if (listCount == foundCount) {
                complete = true;
            }
        }
        callback(complete);
    }

    // ------------- End Web Request/Rate Limiting -----------

    bootstrap();
})();

class NavigationPointMEP
{
    constructor(point){
        this._point = point.clone();
        this._entry = true;
        this._exit = true;
        this._isPrimary = true;
        this._name = "";
    }

    with(){
        var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
        if(e.point == null)
            e.point = this.toJSON().point;
        let val = new this.constructor((this.toJSON().point, e.point));
        val._entry = this._entry;
        val._exit = this._exit;
        val._isPrimary = this._isPrimary;
        val._name = this._name;
        return val;
    }

    getPoint(){
        return this._point.clone();
    }

    getEntry(){
        return this._entry;
    }

    getExit(){
        return this._exit;
    }

    getName(){
        return this._name;
    }

    isPrimary(){
        return this._isPrimary;
    }

    toJSON(){
        return  {
            point: this._point,
            entry: this._entry,
            exit: this._exit,
            primary: this._isPrimary,
            name: this._name
        };
    }

    clone(){
        return this.with();
    }
}