您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Retrieve and show road events
// ==UserScript== // @name WME Road Events Data // @namespace http://www.tomputtemans.com/ // @description Retrieve and show road events // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/ // @version 1.8.0 // @connect geo.api.vlaanderen.be // @connect * // @grant GM_xmlhttpRequest // ==/UserScript== /* global I18n, W, $, OpenLayers */ let styleElement; (function() { var UI = {}, RoadEvents; function roadEventsInit() { var userInfo = document.getElementById('user-info'); // Check initialisation if (typeof W == 'undefined' || typeof I18n == 'undefined') { setTimeout(roadEventsInit, 660); log('Waze object unavailable, map still loading'); return; } if (userInfo === null) { setTimeout(roadEventsInit, 660); log('User info unavailable, map still loading'); return; } var navTabs = userInfo.querySelector('.nav-tabs'); if (navTabs === null) { setTimeout(roadEventsInit, 660); log('Nav tabs unavailable, map still loading'); return; } log('Road Events initated'); // Set translation strings addTranslations(); if (!localStorage.WME_RoadEventsData) { var data = {}; data.disabledSources = []; data.relevantOnly = true; localStorage.WME_RoadEventsData = JSON.stringify(data); } UI.Tab = function() { log("Configuring UI"); var tabContent = userInfo.querySelector('.tab-content'), roadEventsTab = document.createElement('li'), roadEventsContent = document.createElement('div'), roadEventsFooter = document.createElement('p'); roadEventsTab.innerHTML = '<a href="#sidepanel-roadEvents" data-toggle="tab">' + I18n.t('road_events.tab_name') + '</a>'; roadEventsContent.id = 'sidepanel-roadEvents'; roadEventsContent.className = 'tab-pane'; navTabs.appendChild(roadEventsTab); tabContent.appendChild(roadEventsContent); roadEventsFooter.appendChild(document.createTextNode(GM_info.script.name + ': v' + GM_info.script.version)); roadEventsFooter.style.fontSize = '11px'; roadEventsFooter.style.marginTop = '1em'; roadEventsContent.appendChild(roadEventsFooter); return { add: function(element) { roadEventsContent.insertBefore(element, roadEventsFooter); } }; }(); UI.ResultList = function() { var filterPane = document.createElement('div'); // List to contain results encapsulated in this object var ul = document.createElement('ul'); filterPane.style.display = 'none'; filterPane.style.marginTop = '5px'; function parseDate(date) { return date.getFullYear() + '/' + zeroPad(date.getMonth()+1) + '/' + zeroPad(date.getDate()) + ' ' + zeroPad(date.getHours()) + ':' + zeroPad(date.getMinutes()); } function createButton(text, handler, title) { var button = document.createElement('button'); button.type = 'button'; button.className = 'btn btn-default'; button.innerHTML = text; button.addEventListener('click', handler, true); if (title) { button.title = title; $(button).tooltip(); } return button; } function clearList() { while (ul.firstChild) { ul.removeChild(ul.firstChild); } } // Add search button var searchButton = createButton(I18n.t('road_events.search.button_label'), function() { RoadEvents.update(); return false; }); UI.Tab.add(searchButton); // Add filter button var filterButton = createButton('<span class="fa" style="font-size:14px"></span>', function() { $(filterPane).toggle(); return false; }, I18n.t('road_events.search.filter_title')); filterButton.style.marginLeft = '0.4em'; UI.Tab.add(filterButton); // Add clear button var clearButton = createButton('<span class="fa" style="font-size:14px"></span>', function() { clearList(); UI.Layer.clear(); UI.ItemDetail.hide(); UI.ResultList.show(); return false; }, I18n.t('road_events.results.clear_title')); clearButton.className = 'btn btn-danger'; clearButton.style.marginLeft = '0.2em'; UI.Tab.add(clearButton); // Fill in filter pane var filterForm = document.createElement('form'); filterForm.className = 'form-horizontal'; var sourceGroup = document.createElement('div'); sourceGroup.className = 'form-group'; sourceGroup.style.backgroundColor = '#ccc'; sourceGroup.style.paddingBottom = '5px'; var generalLabel = document.createElement('label'); generalLabel.className = 'col-sm-3 control-label'; generalLabel.appendChild(document.createTextNode('Sources:')); sourceGroup.appendChild(generalLabel); var sourceList = document.createElement('div'); sourceList.className = 'col-sm-9'; sourceGroup.appendChild(sourceList); // Allow showing only the relevant data var relevantOnlyGroup = document.createElement('div'); relevantOnlyGroup.className = 'checkbox col-sm-12'; var relevantOnlyLabel = document.createElement('label'); var relevantOnlyCheckbox = document.createElement('input'); relevantOnlyCheckbox.type = 'checkbox'; relevantOnlyCheckbox.checked = JSON.parse(localStorage.WME_RoadEventsData).relevantOnly !== false; relevantOnlyLabel.appendChild(relevantOnlyCheckbox); relevantOnlyLabel.appendChild(document.createTextNode(I18n.t('road_events.search.relevant_results_only'))); relevantOnlyGroup.appendChild(relevantOnlyLabel); relevantOnlyCheckbox.addEventListener('click', () => { let data = JSON.parse(localStorage.WME_RoadEventsData); data.relevantOnly = relevantOnlyCheckbox.checked; localStorage.WME_RoadEventsData = JSON.stringify(data); }); sourceGroup.appendChild(relevantOnlyGroup); filterForm.appendChild(sourceGroup); filterPane.appendChild(filterForm); UI.Tab.add(filterPane); ul.className = 'result-list'; UI.Tab.add(ul); return { // Populate the result list with an array of events fill: function(events) { this.clear(); ul.style.listStyleType = 'decimal'; events.forEach(function(event, index) { var li = document.createElement('li'); var head = document.createElement('p'); head.className = 'title'; if (event.color) { head.style.paddingLeft = '5px'; head.style.borderLeft = '3px solid ' + event.color; } head.innerHTML = event.description; li.appendChild(head); var subhead = document.createElement('p'); subhead.className = 'additional-info'; subhead.style.fontWeight = 'normal'; if (event.color) { subhead.style.paddingLeft = '8px'; } subhead.appendChild(document.createTextNode(parseDateTime(event.start))); if (event.end) { var separator = document.createElement('i'); separator.className = 'fa fa-fw fa-chevron-right'; subhead.appendChild(separator); subhead.appendChild(document.createTextNode(parseDateTime(event.end))); } li.appendChild(subhead); li.addEventListener('click', function() { RoadEvents.show(event, index + 1); }); ul.appendChild(li); }); UI.ItemDetail.hide(); UI.ResultList.show(); }, clear: clearList, setStatus: function(status, count) { var title, info; this.clear(); ul.style.listStyleType = 'none'; if (status == 'loading') { title = I18n.t('road_events.search.loading_header'); info = I18n.t('road_events.search.loading_subheader', {count: count}); } else if (status == 'noResult') { title = I18n.t('road_events.results.empty_header'); info = I18n.t('road_events.results.empty_subheader'); } else if (status == 'noSources') { title = I18n.t('road_events.results.no_sources_header'); info = I18n.t('road_events.results.no_sources_subheader'); } else { log('Invalid status received: ' + status); throw new Error('Invalid status received: ' + status); } var li = document.createElement('li'); li.className = 'result'; li.style.fontWeight = 'bold'; li.style.paddingLeft = '0'; var head = document.createElement('p'); head.className = 'title'; head.appendChild(document.createTextNode(title)); li.appendChild(head); var subhead = document.createElement('p'); subhead.className = 'additional-info'; subhead.style.fontWeight = 'normal'; subhead.appendChild(document.createTextNode(info)); li.appendChild(subhead); ul.appendChild(li); UI.ItemDetail.hide(); UI.ResultList.show(); }, show: function() { ul.style.display = 'block'; }, hide: function() { ul.style.display = 'none'; }, addFilter: function(sourceName, enabled, action) { var newSource = document.createElement('div'); newSource.className = 'checkbox'; var sourceLabel = document.createElement('label'); var sourceCheckbox = document.createElement('input'); sourceCheckbox.type = 'checkbox'; sourceCheckbox.checked = enabled; sourceLabel.appendChild(sourceCheckbox); sourceLabel.appendChild(document.createTextNode(sourceName)); newSource.appendChild(sourceLabel); sourceCheckbox.addEventListener('click', function(e) { action(this.checked); }); sourceList.appendChild(newSource); } }; }(); UI.ItemDetail = function() { var pane = document.createElement('div'); pane.style.display = 'none'; pane.style.marginTop = '8px'; var details = document.createElement('div'); var backToList = document.createElement('button'); var backToListIcon = document.createElement('span'); var listeners = []; function returnToList() { UI.Layer.removeType("area"); UI.ItemDetail.hide(); UI.ResultList.show(); } backToListIcon.className = 'fa'; backToListIcon.appendChild(document.createTextNode('')); backToListIcon.style.marginRight = '1em'; backToList.appendChild(backToListIcon); backToList.appendChild(document.createTextNode(I18n.t('road_events.detail.back_to_list'))); backToList.className = 'btn btn-link'; var backToListBottom = backToList.cloneNode(true); backToList.addEventListener('click', returnToList); backToListBottom.addEventListener('click', returnToList); pane.appendChild(backToList); pane.appendChild(details); pane.appendChild(backToListBottom); UI.Tab.add(pane); return { set: function(event, index) { this.clear(); var eventTitle = document.createElement('h3'); eventTitle.style.borderBottom = '1px solid #cecece'; eventTitle.textContent = (index ? index + '. ' : '') + event.detail.description; details.appendChild(eventTitle); var moreInfoHeader = document.createElement('h3'); moreInfoHeader.style.marginTop = '1em'; moreInfoHeader.textContent = I18n.t('road_events.detail.more_info'); var moreInfoDataContainer = document.createElement('fieldset'); var moreInfoData = document.createElement('p'); moreInfoDataContainer.appendChild(moreInfoData); if (event.start) { moreInfoData.innerHTML = '<strong>' + I18n.t('road_events.detail.start_date') + ':</strong> ' + parseDateTime(event.start) + '<br />'; } if (event.end) { moreInfoData.innerHTML += '<strong>' + I18n.t('road_events.detail.end_date') + ':</strong> ' + parseDateTime(event.end) + '<br />'; } if (event.id) { moreInfoData.innerHTML += '<strong>' + I18n.t('road_events.detail.id') + ':</strong> ' + formatDataField(escapeString(event.id)) + '<br />'; } if (moreInfoData.childNodes.length > 0) { details.appendChild(moreInfoHeader); details.appendChild(moreInfoData); } for (var group in event.detail) { if (typeof event.detail[group] === 'string') { continue; } var sectionHeader = document.createElement('h3'); sectionHeader.style.marginTop = '1em'; sectionHeader.textContent = I18n.t('road_events.detail.' + group); details.appendChild(sectionHeader); var p = document.createElement('p'); for (var name in event.detail[group]) { var item = event.detail[group][name]; if (item !== null && item.length !== 0) { if (Array.isArray(item)) { p.innerHTML += '<strong>' + I18n.t('road_events.detail.' + name, {count: item.length}) + ":</strong> "; p.innerHTML += item.join(', '); } else { p.innerHTML += '<strong>' + I18n.t('road_events.detail.' + name) + ":</strong> "; if (typeof item === 'boolean') { p.innerHTML += I18n.t('road_events.detail.' + (item ? 'yes' : 'no')); } else if (typeof item === 'string' && item.startsWith('http')) { p.innerHTML += '<a href="' + item + '" target="_blank">' + item + '</a>'; } else { p.innerHTML += item; } } p.innerHTML += '<br/>'; } } details.appendChild(p); } UI.ResultList.hide(); UI.ItemDetail.show(); }, clear: function() { while (details.firstChild) { details.removeChild(details.firstChild); } }, show: function() { pane.style.display = 'block'; }, hide: function() { pane.style.display = 'none'; } }; }(); UI.Layer = function() { var layer = new OpenLayers.Layer.Vector(I18n.t('road_events.layer_label'), { styleMap: new OpenLayers.StyleMap({ 'default': new OpenLayers.Style({ pointRadius: 10, strokeColor: '#eee', fillColor: '${color}', fontColor: '#fff', fontWeight: 'bold', label: '${index}' }), 'select': new OpenLayers.Style({ pointRadius: 10, strokeColor: '#aaa', fillColor: '${color}', fontColor: '#fff', fontWeight: 'bold', label: '${index}' }) }) }); W.map.addLayer(layer); function makeVector(event) { return new OpenLayers.Feature.Vector( event.coordinate, { type: 'marker', description: event.description, id: event.id, color: event.color, index: event.index.toString() } ); } return { // Add a vector add: function(vector) { layer.addFeatures([ vector ]); }, // Add a set of road events to the map at once fill: function(events) { this.clear(); if (events) { var vectors = events.map(makeVector); layer.addFeatures(vectors); } }, // Remove all features for which a certain attribute is set removeType: function(type) { layer.removeFeatures(layer.getFeaturesByAttribute('type', type)); }, // Clear the layer by removing all features clear: function() { layer.removeAllFeatures(); } }; }(); RoadEvents = function() { var activeEvent = null, sources = {}; // Hook into dialog-container for closure addition prefilling var observer = new MutationObserver(function(mutations) { if (activeEvent !== null) { mutations.forEach(function(mutation) { for (var i = 0; i < mutation.addedNodes.length; i++) { var node = mutation.addedNodes[i]; if (node !== Node.ELEMENT_NODE) { continue; } if (node.querySelector('.edit-closure.new')) { node.querySelector("input.form-control[name='closure_reason']").value = activeEvent.detail.description; if (activeEvent.start) { node.querySelector("input[name='closure_hasStartDate']").checked = true; node.querySelector("input.form-control[name='closure_startDate']").value = activeEvent.start.getFullYear() + '-' + zeroPad(activeEvent.start.getMonth()+1) + '-' + zeroPad(activeEvent.start.getDate()); node.querySelector("input.form-control[name='closure_startTime']").value = zeroPad(activeEvent.start.getHours()) + ':' + zeroPad(activeEvent.start.getMinutes()); } if (activeEvent.end) { node.querySelector("input.form-control[name='closure_endDate']").value = activeEvent.end.getFullYear() + '-' + zeroPad(activeEvent.end.getMonth()+1) + '-' + zeroPad(activeEvent.end.getDate()); node.querySelector("input.form-control[name='closure_endTime']").value = zeroPad(activeEvent.end.getHours()) + ':' + zeroPad(activeEvent.end.getMinutes()); } } } }); } }); observer.observe(document.getElementById('edit-panel'), { childList: true, subtree: true }); return { // Retrieve one specific event from a source show: function(event, index) { sources[event.source].get(event.id, function(detail) { UI.ItemDetail.set(detail, index); activeEvent = detail; if (detail.vector) { UI.Layer.add(detail.vector); } W.map.moveTo(OpenLayers.LonLat.fromArray([ event.coordinate.x, event.coordinate.y ])); }); }, // Update all sources for the current location update: function() { var promises = []; var viewBounds = W.map.getExtent(); activeEvent = null; for (var source in sources) { if (!sources[source].disabled && sources[source].intersects(viewBounds)) { promises.push(sources[source].update()); } } if (promises.length === 0) { UI.ResultList.setStatus('noSources'); return; } UI.ResultList.setStatus('loading', promises.length); Promise.all(promises).then(function(results) { var roadEvents = results.reduce(function(prev, curr) { return prev.concat(curr); }).sort(function(a, b) { if (a.hindrance == b.hindrance) { return new Date(a.start).getTime() - new Date(b.start).getTime(); } else { return (a.hindrance ? -1 : 1); } }).map(function(roadEvent, index) { roadEvent.index = index + 1; return roadEvent; }); if (roadEvents.length > 0) { UI.ResultList.fill(roadEvents); UI.Layer.fill(roadEvents); } else { UI.ResultList.setStatus('noResult'); } }); }, // Add a new source of road events addSource: function(source) { sources[source.id] = source; source.disabled = JSON.parse(localStorage.WME_RoadEventsData).disabledSources.indexOf(source.id) >= 0; UI.ResultList.addFilter(source.name, !source.disabled, function(enabled) { source.disabled = !enabled; var data = JSON.parse(localStorage.WME_RoadEventsData); if (enabled) { if (data.disabledSources.indexOf(source.id) >= 0) { data.disabledSources.splice(data.disabledSources.indexOf(source.id), 1); } } else { data.disabledSources.push(source.id); } localStorage.WME_RoadEventsData = JSON.stringify(data); }); } }; }(); window.RoadEvents = RoadEvents; // Data source: GIPOD Mobility Hindrances (Flanders, Belgium) RoadEvents.addSource(function() { var url = 'https://geo.api.vlaanderen.be/GIPOD/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=GIPOD:HINDER&SRSNAME=urn:x-ogc:def:crs:EPSG:4326&outputFormat=application/json', projection = new OpenLayers.Projection("EPSG:4326"), cache = [], // cached event details, bounds = new OpenLayers.Bounds(280525, 6557859, 661237, 6712007) relevantConsequences = ['Geen doorgang voor gemotoriseerd verkeer', 'Geen doorgang voor gemotoriseerd verkeer uitgezonderd hulpdiensten', 'Geen doorgang voor gemotoriseerd verkeer uitgezonderd openbaar vervoer', 'Geen doorgang voor gemotoriseerd verkeer uitgezonderd plaatselijk verkeer', 'Afgesloten in 1 rijrichting', 'Afgesloten in 1 rijrichting uitgezonderd hulpdiensten', 'Afgesloten in 1 rijrichting uitgezonderd openbaar vervoer', 'Rijrichting omgekeerd']; return { id: 'gipod_mobility', name: 'GIPOD Mobility Hindrances', intersects: function(view) { return bounds.intersectsBounds(view); }, update: function() { return new Promise(function(resolve, reject) { // Obtain the bounds of the current view var bounds = new OpenLayers.Bounds(W.map.calculateBounds()); var bbox = bounds.left + "," + bounds.bottom + "," + bounds.right + "," + bounds.top + ",EPSG:4326"; GM_xmlhttpRequest({ method: 'GET', url: url + '&BBOX=' + bbox, onload: function(response) { var rawData = JSON.parse(response.responseText); if (!rawData || !rawData.features) { resolve([]); return; } let isRelevant = (event) => { // Don't filter if not requested if (JSON.parse(localStorage.WME_RoadEventsData).relevantOnly === false) { return true; } let consequences = event.properties.Consequences.split(';'); return relevantConsequences.some(consequence => consequences.includes(consequence)); }; var roadEvents = rawData.features.filter(isRelevant).map(function(data) { let event = { id: data.id, source: 'gipod_mobility', description: escapeString(data.properties.HindranceDescription), start: data.properties.HindranceStart, end: data.properties.HindranceEnd, hindrance: data.properties.SevereHindrance, color: (data.properties.SevereHindrance ? '#ff3333' : '#ff8c00'), coordinate: (new OpenLayers.Bounds(data.bbox)).toGeometry().transform(projection, W.map.getProjectionObject()).getCentroid(), rawData: data }; cache[data.id] = event; return event; }); resolve(roadEvents); }, onerror: function(xhr, text) { resolve([]); } }); }); }, get: function(gipodId, callback) { if (cache[gipodId]) { let data = cache[gipodId]; let vector = null; if (data.rawData.geometry !== null) { let poly = null; if (data.rawData.geometry.type == 'Polygon') { let ring = new OpenLayers.Geometry.LinearRing(data.rawData.geometry.coordinates[0].map(function(coord) { return new OpenLayers.Geometry.Point(coord[0], coord[1]).transform(projection, W.map.getProjectionObject()); })); poly = new OpenLayers.Geometry.Polygon([ ring ]); } else if (data.rawData.geometry.type == 'MultiPolygon') { let rings = data.rawData.geometry.coordinates[0].map(function(coords) { return new OpenLayers.Geometry.LinearRing(coords.map(function(coord) { return new OpenLayers.Geometry.Point(coord[0], coord[1]).transform(projection, W.map.getProjectionObject()); })); }); poly = new OpenLayers.Geometry.Polygon(rings); } if (poly !== null) { vector = new OpenLayers.Feature.Vector(poly, { type: 'area' }, { fillOpacity: 0.6, fillColor: '#ff8c00', strokeColor: '#eeeeee'}); } } let sourceId = data.rawData.properties.HindranceConsequenceOf.split(';')[0].split('/').pop(); let roadEvent = { detail: { description: data.description, identification: { last_update: escapeString(parseDateTime(data.rawData.properties.HindranceLastModifiedOn)), created_on: escapeString(parseDateTime(data.rawData.properties.HindranceCreatedOn)), state: escapeString(data.rawData.properties.HindranceStatus), id: escapeString(data.id), source: 'GIPOD Mobility Hindrances' }, hindrance: { important_hindrance: data.hindrance === true, effects: data.rawData.properties.Consequences ? data.rawData.properties.Consequences.split(';').map(escapeString) : '' }, contact: { owner: escapeString(data.rawData.properties.HindranceOwner) } }, start: new Date(data.start), end: new Date(data.end), id: 'http://www.geopunt.be/kaart?app=Hinder_in_kaart_app&lang=nl&GIPODID=' + escapeString(sourceId) + '|' + escapeString(sourceId), vector: vector, rawData: data }; callback(roadEvent); } } }; }()); // Data source: Waze alerts by Wazers RoadEvents.addSource(function() { var projection = new OpenLayers.Projection("EPSG:4326"); var url = 'https://www.waze.com'; switch (W.app.getAppRegionCode()) { case 'row': url += '/row-rtserver/web/TGeoRSS'; break; case 'il': url += '/il-rtserver/web/TGeoRSS'; break; default: url += '/rtserver/web/TGeoRSS'; } var cache = {}; return { id: 'waze_alerts', name: 'Waze livemap alerts', intersects: function(view) { return true; // Available worldwide }, update: function() { return new Promise(function(resolve, reject) { var extent = W.map.getExtent().transform(W.map.getProjectionObject(), projection); var data = { types: "alerts", left: extent.left, right: extent.right, bottom: extent.bottom, top: extent.top }; $.ajax({ url: url, data: data, dataType: 'json' }).done(function(response) { var roadEvents = []; if (response.alerts) { response.alerts.forEach(function(alert) { roadEvents.push({ id: alert.id, source: 'waze_alerts', description: escapeString(alert.type), start: alert.pubMillis, hindrance: false, color: '#614051', coordinate: new OpenLayers.Geometry.Point(alert.location.x, alert.location.y).transform(projection, W.map.getProjectionObject()) }); cache[alert.id] = alert; }); } resolve(roadEvents); }).fail(function(xhr, text) { resolve([]); }); }); }, get: function(id, callback) { var alert = cache[id], description = alert.type; if (alert.reportDescription) { description += ' ' + alert.reportDescription; } if (alert.subtype) { description += ' ' + alert.subtype; } callback({ detail: { description: escapeString(description), identification: { periods: [ new Date(alert.pubMillis).toISOString().substring(0, 10) ], id: alert.id }, contact: { owner: 'Waze', initiator: escapeString(alert.reportBy) } } }); } }; }()); // Data source: Brussels Mobility (Brussels, Belgium) /*RoadEvents.addSource(function() { // Proxy necessary as this API is not available via a secure connection var url = 'https://tomputtemans.com/waze-scripts/road-events.php?source=mobiris', projection = new OpenLayers.Projection("EPSG:31370"), cache = [], // cached events bounds = new OpenLayers.Bounds(472208, 6579412, 499191, 6606318); // http://www.bruxellesmobilite.irisnet.be/static/mobiris_files/nl/alerts.json*/ } function addTranslations() { var strings = { en: { layer_label: "Road Events", tab_name: "RED", tab_title: "Road Events Data", search: { button_label: "Search current area", filter_title: "Select data sources", relevant_results_only: "Only show relevant results", loading_header: "Loading...", loading_subheader: { one: "Retrieving information from 1 service", other: "Retrieving information from %{count} services" } }, results: { empty_header: "No results found", empty_subheader: "Please zoom out or pan to another area", limit_reached_header: "Some results may have been omitted", limit_reached_header: "A service limits the amount of results and this limit has been reached. Zoom in to see all results", no_sources_header: "No data sources for this area", no_sources_subheader: "There are no data sources configured for this area", clear_title: "Clear results" }, detail: { back_to_list: "Back to results", more_info: "More information", cities: { one: "City", other: "Cities" }, comment: "Comment", contact: "Contact", contractor: "Contractor", description: "Description", direction: "Direction", effects: { one: "Effect", other: "Effects" }, email: "E-mail", event_type: "Event type", hindrance: "Hindrance", id: "ID", start_date: "Start date", end_date: "End date", identification: "Identification", important_hindrance: "Important hindrance", initiator: "Initiator", last_update: "Last update", created_on: "Created on", locations: { one: "Location", other: "Locations" }, main_contractor: "Main Contractor", no: "no", no_hindrance: "no hindrance specified", no_organisation: "no organisation specified", organisation: "Organisation", owner: "Owner", periods: { one: "Period", other: "Periods" }, phone: "Phone number", recurrence: "Recurrences", reference: "Reference", source: "Source", state: "State", type: "Type", url: "URL", yes: "yes" } }, nl: { layer_label: "Weggebeurtenissen", tab_name: "RED", tab_title: "Weggebeurtenissen (Road Events Data)", search: { button_label: "Gebied doorzoeken", filter_title: "Selecteer databronnen", relevant_results_only: "Toon alleen relevante resultaten", loading_header: "Aan het laden...", loading_subheader: { one: "Informatie aan het opvragen bij 1 dienst", other: "Informatie aan het opvragen bij %{count} diensten" } }, results: { empty_header: "Geen resultaten gevonden", empty_subheader: "Zoom uit of ga naar een andere locatie", limit_reached_header: "Mogelijk ontbreken sommige resultaten", limit_reached_header: "Een dienst beperkt het aantal resultaten en deze limiet werd bereikt. Zoom in om het alle resultaten te zien", no_sources_header: "Geen databronnen voor dit gebied", no_sources_subheader: "Er zijn geen databronnen ingesteld voor dit gebied", clear_title: "Resultaten verwijderen" }, detail: { back_to_list: "Terug naar resultaten", more_info: "Meer informatie", cities: { one: "Grondgebied", other: "Grondgebied" }, comment: "Commentaar", contact: "Contact", contractor: "Aannemer", description: "Omschrijving", direction: "Richting", effects: { one: "Impact", other: "Impact" }, email: "E-mail", event_type: "Evenementtype", hindrance: "Hinder", id: "ID", start_date: "Begindatum", end_date: "Einddatum", identification: "Identificatie", important_hindrance: "Ernstige hinder", initiator: "Initiatiefnemer", last_update: "Laatst bijgewerkt", created_on: "Aangemaakt op", locations: { one: "Plaats", other: "Plaatsen" }, main_contractor: "Hoofdaannemer", no: "nee", no_hindrance: "geen hinder vermeld", no_organisation: "geen organisatie vermeld", organisation: "Organisatie", owner: "Beheerder", periods: { one: "Periode", other: "Periodes" }, phone: "Telefoonnummer", recurrence: "Herhalingspatroon", reference: "Referentie", source: "Bron", state: "Status", type: "Type", url: "URL", yes: "ja" } }, fr: { layer_label: "Evénements routiers", tab_name: "RED", tab_title: "Evénements routiers (Road Events Data)", search: { button_label: "Cherchez ici", filter_title: "Sélectionne sources des données", relevant_results_only: "Affiche uniquement les résultats pertinents", loading_header: "Chargement en cours...", loading_subheader: { one: "En train de obtenir de l'information chez 1 service", other: "En train de obtenir de l'information chez %{count} services" } }, results: { empty_header: "Aucun résultat trouvé", empty_subheader: "Dézoome ou déplace la carte", limit_reached_header: "Quelques résultats peuvent manquer", limit_reached_header: "Une service limite la quantité de résultats et on a attaint cette limite. Agrandis la carte pour voir tout les résultats", no_sources_header: "Aucun source pour cette région", no_sources_subheader: "Aucun source de données spécifié pour cette région", clear_title: "Supprime les résultats" }, detail: { back_to_list: "Retour aux résultats", more_info: "Détails", cities: { one: "Commune", other: "Communes" }, comment: "Commentaire", contact: "Contact", contractor: "Contractant", description: "Description", direction: "Direction", effects: { one: "Impact", other: "Impact" }, email: "Email", event_type: "Type d'événement", hindrance: "Obstacles", id: "ID", start_date: "Date de début", end_date: "Date de fin", identification: "Identification", important_hindrance: "Obstacle majeur", initiator: "Initiateur", last_update: "Dernier mise à jour", created_on: "Date de création", locations: { one: "Endroit", other: "Endroits" }, main_contractor: "Contractant principal", no: "non", no_hindrance: "aucun obstacle spécifié", no_organisation: "aucun organisation spécifié", organisation: "Organisation", owner: "Administrateur", periods: { one: "Période", other: "Périodes" }, phone: "Numéro telephone", recurrence: "Récurrences", reference: "Réference", source: "Source", state: "Etat", type: "Type", url: "URL", yes: "oui" } } }; strings['en-GB'] = strings['en-US'] = strings.en; I18n.locales.get().forEach(function(locale) { if (I18n.translations[locale]) { I18n.translations[locale].road_events = strings[locale]; } }); } // Input: Unix timestamp or ISO string - Output: 2014-12-22 00:00 in local timezone function parseDateTime(datetime) { if (isNaN(datetime)) { return datetime.replace(/(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2})(:\d{2})(\.\d+)?.*/, "$1-$2-$3 $4"); } return new Date(datetime).toISOString().replace(/(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2})(:\d{2})(\.\d+)?.*/, "$1-$2-$3 $4"); } function escapeString(text) { if (Array.isArray(text)) { if (text.length > 0) { return text.map(escapeString); } else { return null; } } if (typeof text === 'undefined' || text === null || text === '' || text === ' ') { return null; } if (typeof text !== 'string') { text = JSON.stringify(text); } return text.replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function zeroPad(number) { return ("0" + number).slice(-2); } function formatDataField(field) { if (typeof field === 'string') { if (/^https?:\/\//.test(field)) { // Website if (field.indexOf('|') == -1) { return '<a href="' + encodeURI(field) + '" target="_blank">' + field + '</a>'; } else { var dataParts = field.split('|'); return '<a href="' + encodeURI(dataParts[0]) + '" target="_blank">' + dataParts[1] + '</a>'; } } else if (/^[^ ]+@[^ ]+\.[a-zA-Z0-9]+$/.test(field)) { // E-mail return '<a href="mailto:' + encodeURI(field) + '" target="_blank">' + field + '</a>'; } else { return field; } } return null; } // TODO: handle these calls visually for the user function showError(error) { log(error); } function log(message) { if (typeof message === 'string') { console.log('Road Events: ' + message); } else { console.log('Road Events', message); } } // Add style if (!styleElement) { styleElement = document.createElement('style'); styleElement.textContent = ` #sidepanel-roadEvents .result-list { margin-top: 5px; padding-left: 10px; } #sidepanel-roadEvents .result-list li { cursor: pointer; font-weight: bold; padding-left: 0; } #sidepanel-roadEvents .result-list li:hover { background-color: #ddd; } `; } if (!styleElement.parentNode) { document.head.appendChild(styleElement); } // attempt to bootstrap after about a second log('Road Events bootstrap set'); setTimeout(roadEventsInit, 1010); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址