- // ==UserScript==
- // @name WME State DOT Reports
- // @namespace https://greasyfork.org/users/45389
- // @version 2020.11.02.002
- // @description Display state transportation department reports in WME.
- // @author MapOMatic
- // @license GNU GPLv3
- // @contributionURL https://github.com/WazeDev/Thank-The-Authors
- // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
- // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
- // @grant GM_xmlhttpRequest
- // @connect indot.carsprogram.org
- // @connect hb.511ia.org
- // @connect ohgo.com
- // @connect hb.511.nebraska.gov
- // @connect hb.511.idaho.gov
- // @connect hb.511mn.org
- // ==/UserScript==
-
- /* global $ */
- /* global OpenLayers */
- /* global GM_info */
- /* global W */
- /* global unsafeWindow */
- /* global WazeWrap */
- /* global GM_xmlhttpRequest */
-
- const SETTINGS_STORE_NAME = 'dot_report_settings';
- const ALERT_UPDATE = false;
- const SCRIPT_VERSION = GM_info.script.version;
- const SCRIPT_VERSION_CHANGES = [
- `${GM_info.script.name}\nv${SCRIPT_VERSION}\n\nWhat's New\n------------------------------\n`,
- '\n- Added Copy To Clipboard button on report popups.'
- ].join('');
- const IMAGES_PATH = 'https://raw.githubusercontent.com/WazeDev/WME-State-DOT-Reports/master/images';
- const DOT_INFO = {
- ID: {
- stateName: 'Idaho',
- mapType: 'cars',
- baseUrl: 'https://hb.511.idaho.gov',
- reportUrl: '/#roadReports/eventAlbum/',
- reportsFeedUrl: '/tgevents/api/eventReports'
- },
- IN: {
- stateName: 'Indiana',
- mapType: 'cars',
- baseUrl: 'https://indot.carsprogram.org',
- reportUrl: '/#roadReports/eventAlbum/',
- reportsFeedUrl: '/tgevents/api/eventReports'
- },
- IA: {
- stateName: 'Iowa',
- mapType: 'cars',
- baseUrl: 'https://hb.511ia.org',
- reportUrl: '/#allReports/eventAlbum/',
- reportsFeedUrl: '/tgevents/api/eventReports'
- },
- MN: {
- stateName: 'Minnesota',
- mapType: 'cars',
- baseUrl: 'https://hb.511mn.org',
- reportUrl: '/#roadReports/eventAlbum/',
- reportsFeedUrl: '/tgevents/api/eventReports'
- },
- NE: {
- stateName: 'Nebraska',
- mapType: 'cars',
- baseUrl: 'https://hb.511.nebraska.gov',
- reportUrl: '/#roadReports/eventAlbum/',
- reportsFeedUrl: '/tgevents/api/eventReports'
- }
- };
- const _columnSortOrder = ['priority', 'beginTime.time', 'eventDescription.descriptionHeader', 'icon.image', 'archived'];
- let _reports = [];
- let _previousZoom;
- let _mapLayer = null;
- let _settings = {};
-
- function log(message) {
- console.log('DOT Reports: ', message);
- }
-
- function logDebug(message) {
- console.debug('DOT Reports:', message);
- }
- function logError(message) {
- console.error('DOT Reports:', message);
- }
-
- function copyToClipboard(report) {
- // create hidden text element, if it doesn't already exist
- const targetId = '_hiddenCopyText_';
-
- // must use a temporary form element for the selection and copy
- let target = document.getElementById(targetId);
- if (!target) {
- target = document.createElement('textarea');
- target.style.position = 'absolute';
- target.style.left = '-9999px';
- target.style.top = '0';
- target.id = targetId;
- document.body.appendChild(target);
- }
- const startTime = new Date(report.beginTime.time);
- const lastUpdateTime = new Date(report.updateTime.time);
-
- const $content = $('<div>').html(
- `${report.eventDescription.descriptionHeader}<br/><br/>
- ${report.eventDescription.descriptionFull}<br/><br/>
- Start Time: ${startTime.toString('MMM d, y @ h:mm tt')}<br/>
- Updated: ${lastUpdateTime.toString('MMM d, y @ h:mm tt')}`
- );
-
- $(target).val($content[0].innerText || $content[0].textContent);
-
- // select the content
- const currentFocus = document.activeElement;
- target.focus();
- target.setSelectionRange(0, target.value.length);
-
- // copy the selection
- let succeed = false;
- try {
- succeed = document.execCommand('copy');
- } catch (e) {
- // do nothing
- }
- // restore original focus
- if (currentFocus && typeof currentFocus.focus === 'function') {
- currentFocus.focus();
- }
-
- target.textContent = '';
- return succeed;
- }
-
- // I believe this should return the bounds that Waze uses to load its data model.
- // It's wider than the visible bounds of the map, to reduce data loading frequency.
- function getExpandedDataBounds() {
- return W.controller.descartesClient.getExpandedDataBounds(W.map.calculateBounds());
- }
-
- function createSavableReport(reportIn) {
- const attributesToCopy = ['agencyAttribution', 'archived', 'beginTime', 'editorIdentifier', 'eventDescription', 'headlinePhrase',
- 'icon', 'id', 'location', 'priority', 'situationUpdateKey', 'starred', 'updateTime'];
-
- const reportOut = {};
- attributesToCopy.forEach(attr => (reportOut[attr] = reportIn[attr]));
-
- return reportOut;
- }
- function copyToSavableReports(reportsIn) {
- const reportsOut = {};
- Object.keys(reportsIn).forEach(id => (reportsOut[id] = createSavableReport(reportsIn[id])));
- return reportsOut;
- }
-
- function saveSettingsToStorage() {
- if (localStorage) {
- const settings = {
- lastVersion: SCRIPT_VERSION,
- layerVisible: _mapLayer.visibility,
- state: _settings.state,
- hideArchivedReports: $('#hideDotArchivedReports').is(':checked'),
- hideWazeReports: $('#hideDotWazeReports').is(':checked'),
- hideNormalReports: $('#hideDotNormalReports').is(':checked'),
- hideWeatherReports: $('#hideDotWeatherReports').is(':checked'),
- hideCrashReports: $('#hideDotCrashReports').is(':checked'),
- hideWarningReports: $('#hideDotWarningReports').is(':checked'),
- hideClosureReports: $('#hideDotClosureReports').is(':checked'),
- hideRestrictionReports: $('#hideDotRestrictionReports').is(':checked'),
- hideFutureReports: $('#hideDotFutureReports').is(':checked'),
- hideCurrentReports: $('#hideDotCurrentReports').is(':checked'),
- archivedReports: _settings.archivedReports,
- starredReports: copyToSavableReports(_settings.starredReports)
- };
- localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(settings));
- logDebug('Settings saved');
- }
- }
-
- function dynamicSort(property) {
- let sortOrder = 1;
- if (property[0] === '-') {
- sortOrder = -1;
- property = property.substr(1);
- }
- return (a, b) => {
- const props = property.split('.');
- props.forEach(prop => {
- a = a[prop];
- b = b[prop];
- });
- let result = 0;
- if (a < b) {
- result = -1;
- } else if (a > b) {
- result = 1;
- }
- return result * sortOrder;
- };
- }
-
- function dynamicSortMultiple(...args) {
- /*
- * save the arguments object as it will be overwritten
- * note that arguments object is an array-like object
- * consisting of the names of the properties to sort by
- */
- let props = args;
- if (args[0] && Array.isArray(args[0])) {
- [props] = args;
- }
- return (obj1, obj2) => {
- let i = 0;
- let result = 0;
- const numberOfProperties = props.length;
- /* try getting a different result from 0 (equal)
- * as long as we have extra properties to compare
- */
- while (result === 0 && i < numberOfProperties) {
- result = dynamicSort(props[i])(obj1, obj2);
- i++;
- }
- return result;
- };
- }
-
- function getReport(reportId) {
- return _reports.find(report => report.id === reportId);
- }
-
- function isHideOptionChecked(reportType) {
- return $(`#hideDot${reportType}Reports`).is(':checked');
- }
-
- function updateReportsVisibility() {
- hideAllReportPopovers();
- const hideArchived = isHideOptionChecked('Archived');
- const hideWaze = isHideOptionChecked('Waze');
- const hideNormal = isHideOptionChecked('Normal');
- const hideWeather = isHideOptionChecked('Weather');
- const hideCrash = isHideOptionChecked('Crash');
- const hideWarning = isHideOptionChecked('Warning');
- const hideRestriction = isHideOptionChecked('Restriction');
- const hideClosure = isHideOptionChecked('Closure');
- const hideFuture = isHideOptionChecked('Future');
- const hideCurrent = isHideOptionChecked('Current');
- let visibleCount = 0;
- _reports.forEach(report => {
- const img = report.icon.image;
- const now = Date.now();
- const start = new Date(report.beginTime.time);
- const hide = (hideArchived && report.archived)
- || (hideWaze && img.indexOf('waze') > -1)
- || (hideNormal && img.includes('driving'))
- || (hideWeather && (img.indexOf('weather') > -1 || img.indexOf('flooding') > -1))
- || (hideCrash && img.indexOf('crash') > -1)
- || (hideWarning && (img.indexOf('warning') > -1 || img.indexOf('lane_closure') > -1))
- || (hideRestriction && img.indexOf('restriction') > -1)
- || (hideClosure && img.indexOf('closure') > -1)
- || (hideFuture && start > now)
- || (hideCurrent && start <= now);
- if (hide) {
- report.dataRow.hide();
- if (report.imageDiv) { report.imageDiv.hide(); }
- } else {
- visibleCount += 1;
- report.dataRow.show();
- if (report.imageDiv) { report.imageDiv.show(); }
- }
- });
- $('.dot-report-count').text(`${visibleCount} of ${_reports.length} reports`);
- }
-
- function hideAllPopovers($excludeDiv) {
- _reports.forEach(rpt => {
- const $div = rpt.imageDiv;
- if ((!$excludeDiv || $div[0] !== $excludeDiv[0]) && $div.data('state') === 'pinned') {
- $div.data('state', '');
- $div.popover('hide');
- }
- });
- }
-
- function deselectAllDataRows() {
- _reports.forEach(rpt => rpt.dataRow.css('background-color', 'white'));
- }
-
- function toggleMarkerPopover($div, forcePin = false) {
- hideAllPopovers($div);
- if ($div.data('state') !== 'pinned' || forcePin) {
- const id = $div.data('reportId');
- const report = getReport(id);
- $div.data('state', 'pinned');
- $div.popover('show');
- _mapLayer.setZIndex(100000); // this is to help make sure the report shows on top of the turn restriction arrow layer
- if (report.archived) {
- $('.btn-archive-dot-report').text('Un-Archive');
- }
- $('.btn-archive-dot-report').click(() => { setArchiveReport(report, !report.archived, true); buildTable(); });
- $('.btn-open-dot-report').click(evt => {
- evt.stopPropagation();
- window.open($(evt.currentTarget).data('dot-report-url'), '_blank');
- });
- $('.btn-zoom-dot-report').click(evt => {
- evt.stopPropagation();
- W.map.setCenter(getReport($(evt.currentTarget).data('dot-report-id')).marker.lonlat);
- W.map.olMap.zoomTo(4);
- });
- $('.btn-copy-dot-report').click(evt => {
- evt.stopPropagation();
- copyToClipboard(getReport($(evt.currentTarget).data('dot-report-id')));
- });
- $('.reportPopover,.close-popover').click(evt => {
- evt.stopPropagation();
- hideAllReportPopovers();
- });
- // $(".close-popover").click(function() {hideAllReportPopovers();});
- $div.data('report').dataRow.css('background-color', 'beige');
- } else {
- $div.data('state', '');
- $div.popover('hide');
- }
- }
-
- function toggleReportPopover($div) {
- deselectAllDataRows();
- toggleMarkerPopover($div);
- }
-
- function hideAllReportPopovers() {
- deselectAllDataRows();
- hideAllPopovers();
- }
-
- function setArchiveReport(report, archive, updateUi) {
- report.archived = archive;
- if (archive) {
- _settings.archivedReports[report.id] = { updateNumber: report.situationUpdateKey.updateNumber };
- report.imageDiv.addClass('dot-archived-marker');
- } else {
- delete _settings.archivedReports[report.id];
- report.imageDiv.removeClass('dot-archived-marker');
- }
- if (updateUi) {
- saveSettingsToStorage();
- updateReportsVisibility();
- hideAllReportPopovers();
- }
- }
-
- function setStarReport(report, star, updateUi) {
- report.starred = star;
- if (star) {
- if (!_settings.starredReports) { _settings.starredReports = {}; }
- _settings.starredReports[report.id] = report;
- report.imageDiv.addClass('dot-starred-marker');
- } else {
- delete _settings.starredReports[report.id];
- report.imageDiv.removeClass('dot-starred-marker');
- }
- if (updateUi) {
- saveSettingsToStorage();
- updateReportsVisibility();
- hideAllReportPopovers();
- }
- }
-
- function archiveAllReports(unarchive) {
- _reports.forEach(report => setArchiveReport(report, !unarchive, false));
- saveSettingsToStorage();
- buildTable();
- hideAllReportPopovers();
- }
-
- function addRow($table, report) {
- const $img = $('<img>', { src: report.imgUrl, class: 'table-img' });
- const $row = $('<tr> class="clickable"', { id: `dot-row-${report.id}` }).append(
- $('<td class="centered">').append(
- $('<span>', {
- class: `star ${(report.starred ? 'star-filled' : 'star-empty')}`,
- title: 'Star if you want notification when this report is removed by the DOT.\nFor instance, if a map change needs to be undone after a closure report is removed.'
- }).click(evt => {
- evt.stopPropagation();
- setStarReport(report, !report.starred, true);
- const $target = $(evt.currentTarget);
- $target.removeClass(report.starred ? 'star-empty' : 'star-filled');
- $target.addClass(report.starred ? 'star-filled' : 'star-empty');
- })
- ),
- $('<td>', { class: 'centered' }).append(
- $('<input>', {
- type: 'checkbox',
- title: 'Archive (will automatically un-archive if report is updated by DOT)',
- id: `archive-${report.id}`,
- 'data-report-id': report.id
- }).prop('checked', report.archived).click(evt => {
- evt.stopPropagation();
- const $target = $(evt.currentTarget);
- const id = $target.data('reportId');
- const thisReport = getReport(id);
- setArchiveReport(thisReport, $target.is(':checked'), true);
- })
- ),
- $('<td>', { class: 'clickable' }).append($img),
- $('<td>', { class: 'centered' }).text(report.priority),
- $('<td>', { class: (report.wasRemoved ? 'removed-report' : '') }).text(report.eventDescription.descriptionHeader),
- $('<td>', { class: 'centered' }).text(new Date(report.beginTime.time).toString('M/d/y h:mm tt'))
- ).click(evt => {
- const $thisRow = $(evt.currentTarget);
- const id = $thisRow.data('reportId');
- const { marker } = getReport(id);
- const $imageDiv = report.imageDiv;
-
- if ($imageDiv.data('state') !== 'pinned') {
- W.map.setCenter(marker.lonlat);
- }
-
- toggleReportPopover($imageDiv);
- }).data('reportId', report.id);
- report.dataRow = $row;
- $table.append($row);
- $row.report = report;
- }
-
- function onClickColumnHeader(evt) {
- const obj = evt.currentTarget;
- let prop;
- switch (/dot-table-(.*)-header/.exec(obj.id)[1]) {
- case 'category':
- prop = 'icon.image';
- break;
- case 'begins':
- prop = 'beginTime.time';
- break;
- case 'desc':
- prop = 'eventDescription.descriptionHeader';
- break;
- case 'priority':
- prop = 'priority';
- break;
- case 'archive':
- prop = 'archived';
- break;
- default:
- return;
- }
- const idx = _columnSortOrder.indexOf(prop);
- if (idx > -1) {
- _columnSortOrder.splice(idx, 1);
- _columnSortOrder.reverse();
- _columnSortOrder.push(prop);
- _columnSortOrder.reverse();
- buildTable();
- }
- }
-
- function buildTable() {
- logDebug('Building table');
- const $table = $('<table>', { class: 'dot-table' });
- $table.append(
- $('<thead>').append(
- $('<tr>').append(
- $('<th>', { id: 'dot-table-star-header', title: 'Favorites' }),
- $('<th>', { id: 'dot-table-archive-header', class: 'centered' }).append(
- $('<span>', { class: 'fa fa-archive', style: 'font-size:120%', title: 'Sort by archived' })
- ),
- $('<th>', { id: 'dot-table-category-header', title: 'Sort by report type' }),
- $('<th>', { id: 'dot-table-priority-header', title: 'Sort by priority' }).append(
- $('<span>', { class: 'fa fa-exclamation-circle', style: 'font-size:120%' })
- ),
- $('<th>', { id: 'dot-table-desc-header', title: 'Sort by description' }).text('Description'),
- $('<th>', { id: 'dot-table-begins-header', title: 'Sort by starting date' }).text('Starts')
- )
- )
- );
- _reports.sort(dynamicSortMultiple(_columnSortOrder));
- _reports.forEach(report => addRow($table, report));
- $('.dot-table').remove();
- $('#dot-report-table').append($table);
- $('.dot-table th').click(onClickColumnHeader);
-
- updateReportsVisibility();
- }
-
- function getUrgencyString(imagePath) {
- const i1 = imagePath.lastIndexOf('_');
- const i2 = imagePath.lastIndexOf('.');
- return imagePath.substring(i1 + 1, i2);
- }
-
- function updateReportImageUrl(report) {
- const startTime = new Date(report.beginTime.time);
- let imgName = report.icon.image;
-
- if (imgName.indexOf('flooding') !== -1) {
- imgName = imgName.replace('flooding', 'weather').replace('.png', '.gif');
- } else if (report.headlinePhrase.category === 5 && report.headlinePhrase.code === 21) {
- imgName = '/tg_flooding_urgent.png';
- }
-
- const now = new Date(Date.now());
- if (startTime > now) {
- let futureValue;
- if (startTime > now.clone().addMonths(2)) {
- futureValue = 'pp';
- } else if (startTime > now.clone().addMonths(1)) {
- futureValue = 'p';
- } else {
- futureValue = startTime.getDate();
- }
- imgName = `/tg_future_${futureValue}_${getUrgencyString(imgName)}.gif`;
- }
- report.imgUrl = IMAGES_PATH + imgName;
- }
-
- function updateReportGeometry(report) {
- const coord = report.location.primaryPoint;
- report.location.openLayers = {
- primaryPointLonLat: new OpenLayers.LonLat(coord.lon, coord.lat).transform('EPSG:4326', 'EPSG:900913')
- };
- }
-
- function processReport(report) {
- if (report.location && report.location.primaryPoint && report.icon) {
- const size = new OpenLayers.Size(report.icon.width, report.icon.height);
- const icon = new OpenLayers.Icon(report.imgUrl, size, null);
- const marker = new OpenLayers.Marker(report.location.openLayers.primaryPointLonLat, icon);
- marker.report = report;
- // marker.events.register('click', marker, onMarkerClick);
- // _mapLayer.addMarker(marker);
-
- const dot = DOT_INFO[_settings.state];
- const lastUpdateTime = new Date(report.updateTime.time);
- const startTime = new Date(report.beginTime.time);
- const content = $('<div>').append(
- report.eventDescription.descriptionFull,
- $('<div>', { style: 'margin-top: 10px;' }).append(
- $('<span>', { style: 'font-weight: bold; margin-right: 8px;' }).text('Start Time:'),
- startTime.toString('MMM d, y @ h:mm tt'),
- ),
- $('<div>').append(
- $('<span>', { style: 'font-weight: bold; margin-right: 8px;' }).text('Updated:'),
- `${lastUpdateTime.toString('MMM d, y @ h:mm tt')} (update #${report.situationUpdateKey.updateNumber})`
- ),
- $('<div>').append(
- $('<hr>', { style: 'margin-bottom: 5px; margin-top: 5px; border-color: gainsboro' }),
- $('<div>', { style: 'display: table; width: 100%' }).append(
- $('<button>', {
- class: 'btn btn-primary, btn-open-dot-report',
- style: 'float: left;',
- 'data-dot-report-url': dot.baseUrl + dot.reportUrl + report.id
- }).text('Open in DOT website'),
- $('<button>', {
- class: 'btn btn-primary, btn-zoom-dot-report',
- style: 'float: left; margin-left: 6px;',
- 'data-dot-report-id': report.id
- }).text('Zoom'),
- $('<button>', {
- class: 'btn btn-primary, btn-copy-dot-report',
- style: 'float: left; margin-left: 6px;',
- 'data-dot-report-id': report.id
- }).append('<span class="fa fa-copy">'),
- $('<button>', {
- class: 'btn btn-primary, btn-archive-dot-report',
- style: 'float: right;',
- 'data-dot-report-id': report.id
- }).text('Archive'),
- )
- )
- ).html();
-
- const title = $('<div>', { style: 'width: 100%;' }).append(
- $('<div>', { style: 'float: left; max-width: 330px; color: #5989af; font-size: 120%;' }).text(report.eventDescription.descriptionHeader),
- $('<div>', { style: 'float: right;' }).append(
- // eslint-disable-next-line no-script-url
- $('<span>', { class: 'close-popover fa fa-window-close' })
- ),
- $('<div>', { style: 'clear: both;' })
- ).html();
-
- const popoverTemplate = $('<div>', { class: 'reportPopover popover', style: 'max-width: 500px; width: 500px;' }).append(
- $('<div>', { class: 'arrow' }),
- $('<div>', { class: 'popover-title' }),
- $('<div>', { class: 'popover-content' })
- );
-
- const $imageDiv = $(marker.icon.imageDiv)
- .css('cursor', 'pointer')
- .addClass('dotReport')
- .attr({
- 'data-toggle': 'popover',
- title: '',
- 'data-content': content,
- 'data-original-title': title
- }).popover({
- trigger: 'manual',
- html: true,
- placement: 'auto top',
- template: popoverTemplate
- }).on('click', () => toggleReportPopover($imageDiv))
- .data('reportId', report.id)
- .data('state', '')
- .data('report', report);
-
- if (report.agencyAttribution && report.agencyAttribution.agencyName.toLowerCase().includes('waze')) {
- $imageDiv.addClass('wazeReport');
- }
- if (report.archived) {
- $imageDiv.addClass('dot-archived-marker');
- }
- report.imageDiv = $imageDiv;
- report.marker = marker;
- }
- }
-
- function processReports(reports) {
- let settingsUpdated = false;
- _reports = [];
- _mapLayer.clearMarkers();
- logDebug('Adding reports to map...');
- reports.forEach(report => {
- // Exclude pandemic reports (e.g. required social distancing, masks, etc)
- const isPandemicReport = report.icon.image.includes('pandemic');
- if (!isPandemicReport && report.location && report.location.primaryPoint) {
- report.archived = false;
- if (_settings.archivedReports.hasOwnProperty(report.id)) {
- if (_settings.archivedReports[report.id].updateNumber < report.situationUpdateKey.updateNumber) {
- delete _settings.archivedReports[report.id];
- } else {
- report.archived = true;
- }
- }
- _reports.push(report);
- }
- });
-
- // Check saved starred reports.
- Object.keys(_settings.starredReports).forEach(reportId => {
- const starredReport = _settings.starredReports[reportId];
- const report = getReport(reportId);
- if (report) {
- report.starred = true;
- if (report.situationUpdateKey.updateNumber !== starredReport.situationUpdateKey.updateNumber) {
- _settings.starredReports[report.id] = report;
- settingsUpdated = true;
- }
- } else {
- // Report has been removed by DOT.
- if (!starredReport.wasRemoved) {
- starredReport.archived = false;
- starredReport.wasRemoved = true;
- settingsUpdated = true;
- }
- _reports.push(starredReport);
- }
- });
- _reports.forEach(report => {
- updateReportImageUrl(report);
- updateReportGeometry(report);
- processReport(report);
- });
- if (settingsUpdated) {
- saveSettingsToStorage();
- }
- buildTable();
- }
-
- // This function returns a Promise so that it can be used with async/await.
- function makeRequest(url) {
- // GM_xmlhttpRequest is necessary to avoid CORS issues on some sites.
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url,
- onload: res => {
- if (res.status >= 200 && res.status < 300) {
- resolve(res.responseText);
- } else {
- reject(new Error(`(${this.status}) ${this.statusText}`));
- }
- },
- onerror: res => {
- let msg;
- if (res.status === 0) {
- msg = 'An unknown error occurred while attempting to download DOT data.';
- } else {
- msg = `Status code ${this.status} - ${this.statusText}`;
- }
- reject(new Error(msg));
- }
- });
- });
- }
-
- async function fetchReports() {
- const dot = DOT_INFO[_settings.state];
- let json;
- try {
- const url = dot.baseUrl + dot.reportsFeedUrl;
- const text = await makeRequest(url);
- json = $.parseJSON(text);
- } catch (ex) {
- logError(new Error(ex.message));
- json = [];
- }
- processReports(json);
- }
-
- function onLayerVisibilityChanged() {
- saveSettingsToStorage();
- }
-
- /* eslint-disable */
- function installIcon() {
- OpenLayers.Icon = OpenLayers.Class({
- url: null,
- size: null,
- offset: null,
- calculateOffset: null,
- imageDiv: null,
- px: null,
- initialize: function(a, b, c, d){
- this.url=a;
- this.size=b||{w: 20, h: 20};
- this.offset=c||{x: -(this.size.w/2), y: -(this.size.h/2)};
- this.calculateOffset=d;
- a=OpenLayers.Util.createUniqueID("OL_Icon_");
- var div = this.imageDiv=OpenLayers.Util.createAlphaImageDiv(a);
-
- // LEAVE THE FOLLOWING LINE TO PREVENT WME-HARDHATS SCRIPT FROM TURNING ALL ICONS INTO HARDHAT WAZERS --MAPOMATIC
- $(div.firstChild).removeClass('olAlphaImg');
- },
- destroy: function(){ this.erase();OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);this.imageDiv.innerHTML="";this.imageDiv=null; },
- clone: function(){ return new OpenLayers.Icon(this.url, this.size, this.offset, this.calculateOffset); },
- setSize: function(a){ null!==a&&(this.size=a); this.draw(); },
- setUrl: function(a){ null!==a&&(this.url=a); this.draw(); },
- draw: function(a){
- OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, this.size, this.url, "absolute");
- this.moveTo(a);
- return this.imageDiv;
- },
- erase: function(){ null!==this.imageDiv&&null!==this.imageDiv.parentNode&&OpenLayers.Element.remove(this.imageDiv); },
- setOpacity: function(a){ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, null, null, null, null, a); },
- moveTo: function(a){
- null!==a&&(this.px=a);
- null!==this.imageDiv&&(null===this.px?this.display(!1): (
- this.calculateOffset&&(this.offset=this.calculateOffset(this.size)),
- OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {x: this.px.x+this.offset.x, y: this.px.y+this.offset.y})
- ));
- },
- display: function(a){ this.imageDiv.style.display=a?"": "none"; },
- isDrawn: function(){ return this.imageDiv&&this.imageDiv.parentNode&&11!=this.imageDiv.parentNode.nodeType; },
- CLASS_NAME: "OpenLayers.Icon"
- });
- }
- /* eslint-enable */
-
- function onStateSelectChange(evt) {
- hideAllReportPopovers();
- _settings.state = evt.currentTarget.value;
- saveSettingsToStorage();
- fetchReports();
- }
-
- function onHideReportTypeCheckChange() {
- saveSettingsToStorage();
- updateReportsVisibility();
- }
-
- function isLoading() {
- return $('.dot-refresh-reports').hasClass('fa-spin');
- }
- function beforeLoading() {
- const spinner = $('.dot-refresh-reports');
- spinner.addClass('fa-spin').css({ cursor: 'auto' });
- hideAllReportPopovers();
- }
- function afterLoading() {
- const spinner = $('.dot-refresh-reports');
- spinner.removeClass('fa-spin').css({ cursor: 'pointer' });
- WazeWrap.Alerts.success(null, 'DOT reports refreshed');
- }
-
- async function onRefreshReportsClick(evt) {
- evt.stopPropagation();
- if (!isLoading()) {
- beforeLoading();
- await fetchReports();
- afterLoading();
- }
- }
-
- function init511ReportsOverlay() {
- installIcon();
- _mapLayer = new OpenLayers.Layer.Markers('State DOT Reports', {
- displayInLayerSwitcher: true,
- uniqueName: '__stateDotReports'
- });
-
- W.map.addLayer(_mapLayer);
- _mapLayer.setVisibility(_settings.layerVisible);
- _mapLayer.setZIndex(100000);
- _mapLayer.events.register('visibilitychanged', null, onLayerVisibilityChanged);
- }
-
- function initSideTab() {
- $('#stateDotStateSelect').change(onStateSelectChange);
- $('[id^=hideDot]').change(onHideReportTypeCheckChange);
- $('#stateDotStateSelect').val(_settings.state);
-
- ['ArchivedReports', 'WazeReports', 'NormalReports', 'WeatherReports',
- 'TrafficReports', 'CrashReports', 'WarningReports', 'RestrictionReports',
- 'ClosureReports', 'FutureReports', 'CurrentReports'].forEach(name => {
- const settingsPropName = `hide${name}`;
- const checkboxId = `hideDot${name}`;
- if (_settings[settingsPropName]) {
- $(`#${checkboxId}`).prop('checked', true);
- }
- });
-
- $('<span>', {
- title: 'Click to refresh DOT reports',
- class: 'fa fa-refresh refreshIcon dot-tab-icon dot-refresh-reports',
- style: 'cursor:pointer;'
- }).appendTo($('a[href="#sidepanel-dot"]'));
-
- $('.dot-refresh-reports').click(onRefreshReportsClick);
- }
-
- function buildSideTab() {
- // Helper template functions to create elements
- const createCheckbox = (id, text) => $('<div>', { class: 'controls-container' }).append(
- $('<input>', { type: 'checkbox', id }),
- $('<label>', { for: id }).text(text)
- );
- const createOption = (value, text) => $('<option>', { value }).text(text);
-
- const panel = $('<div>').append(
- $('<div>', { class: 'side-panel-section>' }).append(
- $('<div>', { class: 'form-group' }).append(
- $('<label>', { class: 'control-label' }).text('Select your state'),
- $('<div>', { class: 'controls', id: 'state-select' }).append(
- $('<div>').append(
- $('<select>', { id: 'stateDotStateSelect', class: 'form-control' }).append(
- Object.keys(DOT_INFO).map(abbr => createOption(abbr, DOT_INFO[abbr].stateName))
- )
- )
- ),
- $('<label style="width:100%; cursor:pointer; border-bottom: 1px solid #e0e0e0; margin-top:9px;" data-toggle="collapse" data-target="#dotSettingsCollapse"><span class="fa fa-caret-down" style="margin-right:5px;font-size:120%;"></span>Hide reports...</label>'),
- $('<div>', { id: 'dotSettingsCollapse', class: 'collapse' }).append(
- createCheckbox('hideDotArchivedReports', 'Archived'),
- createCheckbox('hideDotWazeReports', 'Waze (if supported by DOT)'),
- createCheckbox('hideDotNormalReports', 'Driving conditions'),
- createCheckbox('hideDotWeatherReports', 'Weather'),
- createCheckbox('hideDotCrashReports', 'Crash'),
- createCheckbox('hideDotWarningReports', 'Warning'),
- createCheckbox('hideDotRestrictionReports', 'Restriction'),
- createCheckbox('hideDotClosureReports', 'Closure'),
- createCheckbox('hideDotFutureReports', 'Future'),
- createCheckbox('hideDotCurrentReports', 'Current/Past')
- )
- )
- ),
- $('<div>', { class: 'side-panel-section>', id: 'dot-report-table' }).append(
- $('<div>').append(
- $('<span>', {
- title: 'Click to refresh DOT reports',
- class: 'fa fa-refresh refreshIcon dot-refresh-reports dot-table-label',
- style: 'cursor:pointer;'
- }),
- $('<span>', { class: 'dot-table-label dot-report-count count' }),
- $('<span>', { class: 'dot-table-label dot-table-action right' }).text('Archive all').click(() => {
- if (confirm(`Archive all reports for ${_settings.state}?`)) {
- archiveAllReports(false);
- }
- }),
- $('<span>', { class: 'dot-table-label right' }).text('|'),
- $('<span>', { class: 'dot-table-label dot-table-action right' }).text('Un-Archive all').click(() => {
- if (confirm(`Un-archive all reports for ${_settings.state}?`)) {
- archiveAllReports(true);
- }
- })
- )
- )
- );
-
- new WazeWrap.Interface.Tab('DOT', panel.html(), initSideTab, null);
- }
-
- function showScriptInfoAlert() {
- /* Check version and alert on update */
- if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) {
- alert(SCRIPT_VERSION_CHANGES);
- }
- }
-
- function initGui() {
- init511ReportsOverlay();
- buildSideTab();
- showScriptInfoAlert();
-
- $(`<style type="text/css">
- .dot-table th,td,tr {cursor: default;}
- .dot-table .centered {text-align:center;}
- .dot-table th:hover,tr:hover {background-color: aliceblue;outline: -webkit-focus-ring-color auto 5px;}
- .dot-table th:hover {color: blue;border-color: whitesmoke; }
- .dot-table {border: 1px solid gray;border-collapse: collapse;width: 100%;font-size: 83%;margin: 0px 0px 0px 0px}
- .dot-table th,td {border: 1px solid gainsboro;}
- .dot-table td,th {color: black;padding: 1px 4px;}
- .dot-table th {background-color: gainsboro;}
- .dot-table .table-img {max-width: 24px;max-height: 24px;}
- .tooltip.top > .tooltip-arrow {border-top-color: white;}
- .tooltip.bottom > .tooltip-arrow {border-bottom-color: white;}
- .close-popover { cursor: pointer;font-size: 20px; }
- .close-popover:hover { color: #f35252; }
- .refreshIcon:hover {color:blue;text-shadow: 2px 2px #aaa;}
- .refreshIcon:active { text-shadow: 0px 0px; }
- .dot-tab-icon { margin-left: 10px; }
- .dot-archived-marker {opacity: 0.5;}
- .dot-table-label {font-size: 85%;}
- .dot-table-action:hover {color: blue;cursor: pointer}
- .dot-table-label.right {float: right}
- .dot-table-label.count {margin-left: 4px;}
- .dot-table .star {cursor: pointer;width: 18px;height: 18px;margin-top: 3px;}
- .dot-table .star-empty {content: url(${IMAGES_PATH}/star-empty.png);}
- .dot-table .star-filled {content: url(${IMAGES_PATH}/star-filled.png);}
- .dot-table .removed-report {text-decoration: line-through;color: #bbb}
- </style>`).appendTo('head');
-
- _previousZoom = W.map.zoom;
- W.map.events.register('zoomend', null, () => {
- if (_previousZoom !== W.map.zoom) {
- hideAllReportPopovers();
- }
- _previousZoom = W.map.zoom;
- });
- }
-
- function loadSettingsFromStorage() {
- let settings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
- if (!settings) {
- settings = {
- lastVersion: null,
- layerVisible: true,
- state: 'ID',
- hideArchivedReports: true,
- archivedReports: {}
- };
- } else {
- settings.layerVisible = (settings.layerVisible === true);
- settings.state = settings.state ? settings.state : Object.keys(DOT_INFO)[0];
- if (typeof settings.hideArchivedReports === 'undefined') {
- settings.hideArchivedReports = true;
- }
- settings.archivedReports = settings.archivedReports ? settings.archivedReports : {};
- settings.starredReports = settings.starredReports ? settings.starredReports : {};
- }
- _settings = settings;
- }
-
- function addMarkers() {
- _mapLayer.clearMarkers();
- const dataBounds = getExpandedDataBounds();
- _reports.forEach(report => {
- if (dataBounds.containsLonLat(report.location.openLayers.primaryPointLonLat)) {
- _mapLayer.addMarker(report.marker);
- }
- });
- }
-
- function onMoveEnd() {
- addMarkers();
- }
-
- async function init() {
- loadSettingsFromStorage();
- W.map.events.register('moveend', null, onMoveEnd);
- unsafeWindow.addEventListener('beforeunload', saveSettingsToStorage, false);
- initGui();
- await fetchReports();
- addMarkers();
- log('Initialized');
- }
-
- function bootstrap() {
- if (W && W.loginManager
- && W.loginManager.events.register
- && W.map && W.loginManager.user
- && WazeWrap.Ready) {
- log('Initializing...');
- init();
- } else {
- log('Bootstrap failed. Trying again...');
- setTimeout(bootstrap, 1000);
- }
- }
-
- bootstrap();