您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Check for possible BDP routes between two selected segments.
当前为
/* eslint-disable no-nested-ternary */ // ==UserScript== // @name WME BDP Check // @namespace https://gf.qytechs.cn/users/166843 // @version 2019.10.16.01 // @description Check for possible BDP routes between two selected segments. // @author dBsooner // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/ // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js // @grant none // @license GPLv3 // ==/UserScript== /* global localStorage, window, $, performance, GM_info, W, WazeWrap */ const ALERT_UPDATE = true, DEBUG = true, LOAD_BEGIN_TIME = performance.now(), // SCRIPT_AUTHOR = GM_info.script.author, SCRIPT_FORUM_URL = '', SCRIPT_GF_URL = '', SCRIPT_NAME = GM_info.script.name.replace('(beta)', 'β'), SCRIPT_VERSION = GM_info.script.version, SCRIPT_VERSION_CHANGES = ['<b>CHANGE:</b> Initial release.'], SETTINGS_STORE_NAME = 'WMEBDPC', _timeouts = { bootstrap: undefined, saveSettingsToStorage: undefined }; let _settings = {}; function log(message) { console.log('WME-BDPC:', message); } function logError(message) { console.error('WME-BDPC:', message); } function logWarning(message) { console.warn('WME-BDPC:', message); } function logDebug(message) { if (DEBUG) console.log('WME-BDPC:', message); } function loadSettingsFromStorage() { return new Promise(async resolve => { const defaultSettings = { lastSaved: 0, lastVersion: undefined }, loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME)); _settings = $.extend({}, defaultSettings, loadedSettings); const serverSettings = await WazeWrap.Remote.RetrieveSettings(SETTINGS_STORE_NAME); if (serverSettings && (serverSettings.lastSaved > _settings.lastSaved)) $.extend(_settings, serverSettings); _timeouts.saveSettingsToStorage = window.setTimeout(saveSettingsToStorage, 5000); resolve(); }); } function saveSettingsToStorage() { checkTimeout({ timeout: 'saveSettingsToStorage' }); if (localStorage) { _settings.lastVersion = SCRIPT_VERSION; _settings.lastSaved = Date.now(); localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings)); WazeWrap.Remote.SaveSettings(SETTINGS_STORE_NAME, _settings); logDebug('Settings saved.'); } } function showScriptInfoAlert() { if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) { let releaseNotes = ''; releaseNotes += '<p>What\'s new:</p>'; if (SCRIPT_VERSION_CHANGES.length > 0) { releaseNotes += '<ul>'; for (let idx = 0; idx < SCRIPT_VERSION_CHANGES.length; idx++) releaseNotes += `<li>${SCRIPT_VERSION_CHANGES[idx]}`; releaseNotes += '</ul>'; } else { releaseNotes += '<ul><li>Nothing major.</ul>'; } WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, releaseNotes, SCRIPT_GF_URL, SCRIPT_FORUM_URL); } } function checkTimeout(obj) { if (obj.toIndex) { if (_timeouts[obj.timeout] && (_timeouts[obj.timeout][obj.toIndex] !== undefined)) { window.clearTimeout(_timeouts[obj.timeout][obj.toIndex]); _timeouts[obj.timeout][obj.toIndex] = undefined; } } else { if (_timeouts[obj.timeout] !== undefined) window.clearTimeout(_timeouts[obj.timeout]); _timeouts[obj.timeout] = undefined; } } function findObjIndex(array, fldName, value) { return array.map(a => a[fldName]).indexOf(value); } function rtgContinuityCheck(segs = []) { if (segs.length < 2) return false; const rtg = { 7: 'mH', 6: 'MHFW', 3: 'MHFW' }, seg1rtg = rtg[segs[0].attributes.roadType]; segs.splice(0, 1); return segs.every(el => seg1rtg === rtg[el.attributes.roadType]); } function nameContinuityCheck(segs = []) { if (segs.length < 2) return false; const streetNames = []; let street; if (segs[0].attributes.primaryStreetID) { street = W.model.streets.getObjectById(segs[0].attributes.primaryStreetID); if (street && street.name && (street.name.length > 0)) streetNames.push(street.name); } if (segs[0].attributes.streetIDs.length > 0) { for (let i = 0; i < segs[0].attributes.streetIDs.length; i++) { street = W.model.streets.getObjectById(segs[0].attributes.streetIDs[i]); if (street && street.name && (street.name.length > 0)) streetNames.push(street.name); } } if (streetNames.length === 0) return false; segs.splice(0, 1); return segs.every(el => { if (el.attributes.primaryStreetID) { street = W.model.streets.getObjectById(el.attributes.primaryStreetID); if (street && street.name && (street.name.length > 0) && streetNames.includes(street.name)) return true; } if (el.attributes.streetIDs.length > 0) { for (let i = 0; i < el.attributes.streetIDs.length; i++) { street = W.model.streets.getObjectById(el.attributes.streetIDs[i]); if (street && street.name && (street.name.length > 0) && streetNames.includes(street.name)) return true; } } return false; }); } function findDirectRoute(obj = {}) { const { maxLength, segIds, startSeg, startNode, endSeg, endNodeIds } = obj, processedSegs = [startSeg.attributes.id], segIdsFilter = (nextSegIds, alreadyProcessed) => nextSegIds.filter(value => alreadyProcessed.indexOf(value) === -1), getNextSegs = (nextSegIds, curSeg, nextNode, idx) => { const rObj = { addPossibleRouteSegments: [], removePossibleRouteSegments: [] }; for (let i = 0; i < nextSegIds.length; i++) { const nextSeg = W.model.segments.getObjectById(nextSegIds[i]); if (curSeg.isTurnAllowed(nextSeg, nextNode) && nameContinuityCheck([startSeg, nextSeg])) { if (processedSegs.indexOf(nextSegIds[i]) === -1) { rObj.addPossibleRouteSegments.push({ startNode: nextNode, seg: nextSeg }); processedSegs.push(nextSegIds[i]); } } } if (rObj.addPossibleRouteSegments.length === 0) rObj.removePossibleRouteSegments.push(idx); return rObj; }; let directRouteSegs = []; segIds.some(segId => { const seg = W.model.segments.getObjectById(segId); if (startSeg.isTurnAllowed(seg, startNode) && nameContinuityCheck([startSeg, seg])) { let possibleRouteSegments = [{ startNode, seg }], curLength = 0; while (possibleRouteSegments.length > 0) { const idx = possibleRouteSegments.length - 1, curSeg = possibleRouteSegments[idx].seg, nextNode = curSeg.getOtherNode(possibleRouteSegments[idx].startNode), nextSegIds = segIdsFilter(nextNode.attributes.segIDs, possibleRouteSegments.map(routeSeg => routeSeg.seg.attributes.id)); if (endNodeIds.indexOf(nextNode.attributes.id) > -1) { directRouteSegs = [startSeg.attributes.id].concat(possibleRouteSegments.map(routeSeg => routeSeg.seg.attributes.id), [endSeg.attributes.id]); possibleRouteSegments = []; return true; } if ((curLength + curSeg.attributes.length) > maxLength) { possibleRouteSegments.splice(idx, 1); curLength -= curSeg.attributes.length; } else { const nextSegsObj = getNextSegs(nextSegIds, curSeg, nextNode, idx); if (nextSegsObj.removePossibleRouteSegments.length > 0) { curLength -= curSeg.attributes.length; possibleRouteSegments.splice(nextSegsObj.removePossibleRouteSegments[0], 1); } else { curLength += curSeg.attributes.length; for (let i = 0; i < nextSegsObj.addPossibleRouteSegments.length; i++) possibleRouteSegments.push(nextSegsObj.addPossibleRouteSegments[i]); } } } } return false; }); return directRouteSegs; } async function doCheckBDP() { const selectedFeatures = W.selectionManager.getSelectedFeatures(), segmentSelection = W.selectionManager.getSegmentSelection(), numSelectedFeatureSegments = selectedFeatures.filter(feature => feature.model.type === 'segment').length; let jsonData, startSeg, endSeg, maxLength, directRoute = []; if ((segmentSelection.segments.length < 2) || (numSelectedFeatureSegments < 2)) { WazeWrap.Alerts.error(SCRIPT_NAME, 'You must select either the two <i>bracketing segments</i> or an entire detour route with <i>bracketing segments</i>.'); return; } if (segmentSelection.multipleConnectedComponents && ((segmentSelection.segments.length > 2) || (numSelectedFeatureSegments < 2))) { WazeWrap.Alerts.error(SCRIPT_NAME, 'If you select more than 2 segments, the selection of segments must be continuous.<br><br>' + 'Either select just the two bracketing segments or an entire detour route with bracketing segments.'); return; } if (segmentSelection.segments.length === 2) { [startSeg, endSeg] = segmentSelection.segments; } else { const tempNodeIds = []; segmentSelection.segments.forEach(segment => { let idx = findObjIndex(tempNodeIds, 'nodeId', segment.attributes.fromNodeID); if (idx > -1) tempNodeIds.splice(idx, 1); else tempNodeIds.push({ nodeId: segment.attributes.fromNodeID, segId: segment.attributes.id }); idx = findObjIndex(tempNodeIds, 'nodeId', segment.attributes.toNodeID); if (idx > -1) tempNodeIds.splice(idx, 1); else tempNodeIds.push({ nodeId: segment.attributes.toNodeID, segId: segment.attributes.id }); }); if (tempNodeIds.length !== 2) { logError('Error finding which two segments were the bracketing segments.'); return; } startSeg = W.model.segments.getObjectById(tempNodeIds[0].segId); endSeg = W.model.segments.getObjectById(tempNodeIds[1].segId); endSeg.attributes.bdpcheck = { routeFarEndNodeId: tempNodeIds[1].nodeId }; maxLength = (startSeg.attributes.roadType === 7) ? 5000 : 50000; } if ((startSeg.attributes.roadType < 3) || (startSeg.attributes.roadType === 4) || (startSeg.attributes.roadType === 5) || (startSeg.attributes.roadType > 7) || (endSeg.attributes.roadType < 3) || (endSeg.attributes.roadType === 4) || (endSeg.attributes.roadType === 5) || (endSeg.attributes.roadType > 7) ) { WazeWrap.Alerts.info(SCRIPT_NAME, 'At least one of the bracketing selected segments is not in the correct road type group for BDP.'); return; } if (!rtgContinuityCheck([startSeg, endSeg])) { WazeWrap.Alerts.info(SCRIPT_NAME, 'One bracketing segment is a minor highway while the other is not. BDP only applies when bracketing segments are in the same road type group.'); return; } if (segmentSelection.segments.length > 2) { // Detour route selected. Lets check BDP checkpoints. const routeSegIds = W.selectionManager.getSegmentSelection().getSelectedSegments() .map(segment => segment.attributes.id) .filter(segId => (segId !== endSeg.attributes.id) && (segId !== startSeg.attributes.id)), endNodeObj = endSeg.getOtherNode(W.model.nodes.getObjectById(endSeg.attributes.bdpcheck.routeFarEndNodeId)), startSegDirection = startSeg.getDirection(), startNodeObjs = (startSegDirection === 1) ? [startSeg.getToNode()] : (startSegDirection === 2) ? [startSeg.getFromNode()] : [startSeg.getToNode(), startSeg.getFromNode()], lastDetourSegId = routeSegIds.filter(el => endNodeObj.attributes.segIDs.includes(el)), lastDetourSeg = W.model.segments.getObjectById(lastDetourSegId), detourSegs = segmentSelection.segments.slice(1, -1); if (nameContinuityCheck([lastDetourSeg, endSeg])) { WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because the last detour segment and the second bracketing segment share a common street name.'); return; } if (rtgContinuityCheck([lastDetourSeg, endSeg])) { WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because the last detour segment and the second bracketing segment are in the same road type group.'); return; } if (detourSegs.length < 2) { WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because it is less than 2 segments long.'); return; } if (detourSegs.map(seg => seg.attributes.length).reduce((a, b) => a + b) > ((startSeg.attributes.roadType === 7) ? 500 : 5000)) { WazeWrap.Alerts.info(SCRIPT_NAME, `BDP will not be applied to this detour route because it is longer than ${((startSeg.attributes.roadType === 7) ? '500m' : '5km')}.`); return; } // We have a preventable detour. Let's check for a direct route. for (let i = 0; i < startNodeObjs.length; i++) { const startNode = startNodeObjs[i], segIds = startNode.attributes.segIDs.filter(segId => segId !== startSeg.attributes.id); directRoute = findDirectRoute({ maxLength, segIds, startSeg, startNode, endSeg, endNodeIds: [endNodeObj.attributes.id] }); if (directRoute.length > 0) break; } } // Check bracketing segment name continuity if (!nameContinuityCheck([startSeg, endSeg])) { WazeWrap.Alerts.info(SCRIPT_NAME, 'The bracketing segments do not share a street name. BDP will not be applied to any route.'); return; } // Let's check for a "direct route" // First check what is returned by the Live Map routing engine. const start900913center = startSeg.getCenter(), end900913center = endSeg.getCenter(), start4326Center = WazeWrap.Geometry.ConvertTo4326(start900913center.x, start900913center.y), end4326Center = WazeWrap.Geometry.ConvertTo4326(end900913center.x, end900913center.y), url = (W.model.countries.getObjectById(235) || W.model.countries.getObjectById(40) || W.model.countries.getObjectById(182)) ? '/RoutingManager/routingRequest' : W.model.countries.getObjectById(106) ? '/il-RoutingManager/routingRequest' : '/row-RoutingManager/routingRequest', data = { from: `x:${start4326Center.lon} y:${start4326Center.lat}`, to: `x:${end4326Center.lon} y:${end4326Center.lat}`, returnJSON: true, returnGeometries: true, returnInstructions: false, timeout: 60000, type: 'HISTORIC_TIME', nPaths: 6, clientVersion: '4.0.0', vehType: 'PRIVATE', options: 'AVOID_TOLL_ROADS:f,AVOID_PRIMARIES:f,AVOID_DANGEROUS_TURNS:f,AVOID_FERRIES:f,ALLOW_UTURNS:t' }; try { jsonData = await $.ajax({ dataType: 'JSON', cache: false, url, data, traditional: true, dataFilter: retData => retData.replace(/NaN/g, '0') }).fail((response, textStatus, errorThrown) => { logWarning(`Route request failed ${(textStatus !== null ? `with ${textStatus}` : '')}\r\n${errorThrown}!`); }); } catch (error) { logWarning(JSON.stringify(error)); } if (!jsonData) { logWarning('No data returned.'); } else if (jsonData.error !== undefined) { logWarning(jsonData.error.replace('|', '\r\n')); } else { let routes = (jsonData.coords !== undefined) ? [jsonData] : []; if (jsonData.alternatives !== undefined) routes = routes.concat(jsonData.alternatives); const directRouteObj = routes.find(el => { const fullRouteSegIds = el.response.results.map(result => result.path.segmentId), fullRouteSegs = W.model.segments.getByIds(fullRouteSegIds); if (nameContinuityCheck(fullRouteSegs) && rtgContinuityCheck(fullRouteSegs)) { // name and rtg continuity exists, let's check distance // using 5km for mH and 50km for MH-FW on purpose. This is because a true "detour path" shouldn't be longer than 500m or 5km respectively. const routeDistance = el.response.results.map(result => result.length).slice(1, -1).reduce((a, b) => a + b); if (routeDistance < maxLength) return true; } return false; }); if (directRouteObj !== undefined) directRoute = directRouteObj.response.results.map(result => result.path.segmentId); } // No direct route found from live-map routing. Let's try to do it manually. if (directRoute.length === 0) { const startSegDirection = startSeg.getDirection(), endSegDirection = endSeg.getDirection(), startNodeObjs = (startSegDirection === 1) ? [startSeg.getToNode()] : (startSegDirection === 2) ? [startSeg.getFromNode()] : [startSeg.getToNode(), startSeg.getFromNode()], endNodeObjs = (endSegDirection === 1) ? [endSeg.getFromNode()] : (endSegDirection === 2) ? [endSeg.getToNode()] : [endSeg.getFromNode(), endSeg.getToNode()], endNodeIds = endNodeObjs.map(nodeObj => nodeObj && nodeObj.attributes.id); for (let i = 0; i < startNodeObjs.length; i++) { const startNode = startNodeObjs[i], segIds = startNode.attributes.segIDs.filter(segId => segId !== startSeg.attributes.id); directRoute = findDirectRoute({ maxLength, segIds, startSeg, startNode, endSeg, endNodeIds }); if (directRoute.length > 0) break; } } if (directRoute.length > 0) { WazeWrap.Alerts.confirm(SCRIPT_NAME, 'A <b>direct route</b> was found! Would you like to select the direct route?', () => { const segments = []; for (let i = 0; i < directRoute.length; i++) { const seg = W.model.segments.getObjectById(directRoute[i]); if (seg !== 'undefined') segments.push(seg); } W.selectionManager.setSelectedModels(segments); }, () => { }, 'Yes', 'No'); } else if (segmentSelection.segments.length === 2) { WazeWrap.Alerts.info(SCRIPT_NAME, 'No direct routes found between the two selected segments. A BDP penalty <b>will not</b> be applied to any routes.' + '<br><b>Note:</b> This could also be caused by the distance between the two selected segments is longer than than the allowed distance for detours.'); } else { WazeWrap.Alerts.info(SCRIPT_NAME, 'No direct routes found between the possible detour bracketing segments. A BDP penalty <b>will not</b> be applied to the selected route.' + '<br><b>Note:</b> This could also be because any possible direct routes are very long, which would take longer to travel than taking the selected route (even with penalty).'); } } function insertCheckBDPButton(evt) { if (!evt || !evt.object || !evt.object._selectedFeatures || (evt.object._selectedFeatures.length < 2)) { if ($('#WME-BDPC').length > 0) $('#WME-BDPC').remove(); return; } if (evt.object._selectedFeatures.filter(feature => feature.model.type === 'segment').length > 1) { $('.edit-restrictions').after( '<button id="WME-BDPC" class="waze-btn waze-btn-small waze-btn-white" title="Check if there are possible BDP routes between two selected segments.">BDP Check</button>' ); } else if ($('#WME-BDPC').length > 0) { $('#WME-BDPC').remove(); } } async function init() { log('Initializing.'); await loadSettingsFromStorage(); W.selectionManager.events.register('selectionchanged', null, insertCheckBDPButton); if (W.selectionManager.getSegmentSelection().segments.length > 1) { $('.edit-restrictions').after( '<button id="WME-BDPC" class="waze-btn waze-btn-small waze-btn-white" title="Check if there are possible BDP routes between two selected segments.">BDP Check</button>' ); } $('#sidebar').on('click', '#WME-BDPC', e => { e.preventDefault(); doCheckBDP(); }); showScriptInfoAlert(); log(`Fully initialized in ${Math.round(performance.now() - LOAD_BEGIN_TIME)} ms.`); } function bootstrap(tries) { if (W && W.map && W.model && $ && WazeWrap.Ready) { checkTimeout({ timeout: 'bootstrap' }); log('Bootstrapping.'); init(); } else if (tries < 1000) { logDebug(`Bootstrap failed. Retrying ${tries} of 1000`); _timeouts.bootstrap = window.setTimeout(bootstrap, 200, ++tries); } else { logError('Bootstrap timed out waiting for WME to become ready.'); } } bootstrap(1);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址