您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display state transportation department reports in WME.
// ==UserScript== // @name WME State DOT Reports // @namespace https://gf.qytechs.cn/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://gf.qytechs.cn/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();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址