您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlighter to help out with cities on WME road segments
// ==UserScript== // @name WME Segment City Highlighter // @namespace WazeDev // @version 2024.09.07.002 // @description Highlighter to help out with cities on WME road segments // @author MapOMatic // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/ // @grant none // ==/UserScript== /* global W */ /* global _ */ /* global OpenLayers */ (function main() { 'use strict'; const SCRIPT_STORE = 'wme-sc-highlighter'; const SCRIPT_NAME = GM_info.script.name; const NO_CITY_NAME = '<No city>'; const CSS = ` input.wmesch-city-input { height: 22px; } .wmesch-clear-text { height: 22px; vertical-align: bottom; } .wmesch-btn { height: 22px; float: right; } #wmesch-container {border: #bbb 1px solid; border-radius: 4px; margin: 2px 10px; padding: 4px;} #wmesch-container table { width: 100%; } #wmesch-container td { vertical-align:top; } #wmesch-container .header-label { float: right; } #wmesch-container .header-cell { width: 90px; } #wmesch-container .wmesch-city-input { width: 160px; } .wmesch-preview { float: left; } `; const _lastValues = {}; let LAYER_Z_INDEX; let _mapLayer; let _$previewCheckbox; let _$primaryCityText; let _$altCityText; function log(msg) { console.log('WME SCH:', msg); } function _id(name) { return `wmesch-${name}`; } function saveSettings() { localStorage.setItem(SCRIPT_STORE, JSON.stringify({ primaryCity: _$primaryCityText.val(), altCity: _$altCityText.val(), preview: _$previewCheckbox.prop('checked') })); } function loadSettings() { const settings = $.parseJSON(localStorage.getItem(SCRIPT_STORE) || '{}'); _$primaryCityText.val(settings.primaryCity || ''); _$altCityText.val(settings.altCity || ''); _$previewCheckbox.prop('checked', settings.preview || false); } function updateCityLists() { const cities = W.model.cities.getObjectArray() .map(city => city.attributes.name) .filter(name => name.length) .sort() .map(name => `<option value="${name}">`); $(`#${_id('alt-city-datalist')}`).empty().append(cities); cities.push(`<option value="${NO_CITY_NAME}">`); $(`#${_id('primary-city-datalist')}`).empty().append(cities); } function getStreetInfo(streetID, isPrimary = false) { const street = W.model.streets.getObjectById(streetID); if (!street) { return { ignore: true }; } const city = W.model.cities.getObjectById(street.attributes.cityID); if (!city) return { ignore: true }; const state = W.model.states.getObjectById(city.attributes.stateID); const country = W.model.countries.getObjectById(city.getCountryID()); // If country is not found, it will be assumed the city is not a valid city and will be treated the // same as a no - city segment. i.e. it wll be removed if primary or any alts have a city with the same street name. return { id: streetID, streetName: street.attributes.name, cityName: country ? W.model.cities.getObjectById(street.attributes.cityID).attributes.name : '', stateID: state.attributes.id, countryID: country ? country.attributes.id : -1, isPrimary }; } function processSegments(segments) { const roadTypesToIgnore = [18]; segments = segments.filter(s => roadTypesToIgnore.indexOf(s.attributes.roadType) === -1); const newPrimaryCityName = $(`#${_id('primary-city')}`).val().trim(); const newAltCityName = $(`#${_id('alt-city')}`).val().trim(); const removeOtherAltCities = $(`#${_id('remove-other-alts')}`).prop('checked'); const result = { actions: [], affectedSegments: [], altIdsToRemove: [] }; segments.forEach(segment => { const segmentAttr = segment.attributes; let isSegmentEdited = false; let isAllNoCity = !newPrimaryCityName && !newAltCityName; if (segmentAttr.primaryStreetID) { const primaryStreetInfo = getStreetInfo(segmentAttr.primaryStreetID, true); const noPrimaryCity = newPrimaryCityName === NO_CITY_NAME; if (newPrimaryCityName && ((!noPrimaryCity && primaryStreetInfo.cityName !== newPrimaryCityName) || (noPrimaryCity && !!primaryStreetInfo.cityName))) { isSegmentEdited = true; } if (primaryStreetInfo.cityName || !primaryStreetInfo.streetName || primaryStreetInfo.ignore) { isAllNoCity = false; } let streetInfos = [primaryStreetInfo]; if (noPrimaryCity) { primaryStreetInfo.cityName = ''; } else if (newPrimaryCityName) { primaryStreetInfo.cityName = newPrimaryCityName; } const altStreetInfos = segmentAttr.streetIDs.map(streetID => getStreetInfo(streetID)); streetInfos = streetInfos.concat(altStreetInfos); if (!streetInfos.some(streetInfo => streetInfo.ignore)) { let cityNames = _.uniq(streetInfos.map(streetInfo => streetInfo.cityName).filter(cityName => !!cityName)); if (cityNames.length) isAllNoCity = false; if (newAltCityName && cityNames.indexOf(newAltCityName) === -1) cityNames.push(newAltCityName); const streetNames = _.uniq(streetInfos.map(streetInfo => streetInfo.streetName).filter(streetName => !!streetName)); if (removeOtherAltCities) { cityNames = cityNames.filter(cityName => cityName === newPrimaryCityName || cityName === newAltCityName); } cityNames.forEach(cityName => { streetNames.forEach(streetName => { if (!streetInfos.some(streetInfo => streetInfo.streetName === streetName && streetInfo.cityName === cityName)) { isSegmentEdited = true; streetInfos.push({ streetID: -999, streetName, cityName }); } }); }); if (cityNames.length) { const altIdsToRemove = altStreetInfos.filter(altStreetInfo => { if (newPrimaryCityName && newPrimaryCityName === altStreetInfo.cityName && primaryStreetInfo.streetName === altStreetInfo.streetName) { return true; } if (!altStreetInfo.cityName) { return true; } return false; }).map(altStreetInfo => altStreetInfo.id); if (altIdsToRemove.length) { result.altIdsToRemove.push({ segment, altIds: altIdsToRemove }); isSegmentEdited = true; } } } } if (isAllNoCity && !segment.isNew()) isSegmentEdited = true; if (isSegmentEdited) result.affectedSegments.push(segment); }); return result; } function highlightSegments() { if (!_$previewCheckbox.prop('checked')) return; _mapLayer.removeAllFeatures(); const result = processSegments(W.model.segments.getObjectArray(), true); const features = W.map.segmentLayer.features.filter(f => result.affectedSegments.indexOf(f.attributes.wazeFeature._wmeObject) > -1).map(f => { const geometry = f.geometry.clone(); const style = { strokeColor: '#ff0', strokeDashstyle: 'solid', strokeWidth: 30 }; return new OpenLayers.Feature.Vector(geometry, null, style); }); _mapLayer.addFeatures(features); } function onSegmentsAdded() { highlightSegments(); } function onCitiesAddedToModel() { updateCityLists(); } function onCityTextChange() { const id = $(this).attr('id'); if (id) { _lastValues[id] = $(this).val(); } saveSettings(); highlightSegments(); } function onClearTextClick() { $(`#${_id($(this).attr('for'))}`).val(null).change(); } function onPreviewChanged() { saveSettings(); if (_$previewCheckbox.prop('checked')) { highlightSegments(); W.model.segments.on('objectsadded', onSegmentsAdded); W.model.segments.on('objectschanged', onSegmentsAdded); } else { _mapLayer.removeAllFeatures(); W.model.segments.off('objectsadded', onSegmentsAdded); W.model.segments.off('objectschanged', onSegmentsAdded); } onSelectionChanged(); } function onSelectionChanged() { try { const selected = W.selectionManager.getSelectedDataModelObjects()[0]; const isSegment = selected?.type === 'segment'; $(`#${_id('container')}`).css({ display: isSegment ? '' : 'none' }); } catch (ex) { console.error(SCRIPT_NAME, ex); } } function initGui() { _$previewCheckbox = $('<input>', { id: _id('preview'), type: 'checkbox', class: _id('preview') }); _$primaryCityText = $('<input>', { id: _id('primary-city'), type: 'text', class: _id('city-input'), list: _id('primary-city-datalist'), autocomplete: 'off' // helps prevent password manager from displaying a popup list }); _$altCityText = $('<input>', { id: _id('alt-city'), type: 'text', class: _id('city-input'), list: _id('alt-city-datalist'), autocomplete: 'off' // helps prevent password manager from displaying a popup list }); // TODO: 2022-11-22 - This is temporary to determine which parent element to add the div to, depending on beta or production WME. // Remove once new side panel is pushed to production. const $parent = $('#edit-panel .contents'); $parent.prepend( $('<div>', { id: _id('container') }).append( $('<table>').append( $('<tr>').append( $('<td>', { class: 'header-cell' }).append($('<label>', { class: 'header-label' }).text('Primary city')), $('<td>').append( _$primaryCityText, $('<button>', { class: _id('clear-text'), for: 'primary-city' }).text('x') ) ), $('<tr>').append( $('<td>', { class: 'header-cell' }).append($('<label>', { class: 'header-label' }).text('Alt city')), $('<td>').append( _$altCityText, $('<button>', { class: _id('clear-text'), for: 'alt-city' }).text('x') ) ), $('<tr>').append($('<td>', { colspan: '2', class: _id('run-button-container') }).append( $('<div>').append( $('<div>', { class: `controls-container ${_id('preview')}` }).append( _$previewCheckbox.change(onPreviewChanged), $('<label>', { for: _id('preview') }).text('Preview') ) ) )) ), $('<datalist>', { id: _id('primary-city-datalist') }), $('<datalist>', { id: _id('alt-city-datalist') }) ) ); $(`.${_id('clear-text')}`).click(onClearTextClick); $(`.${_id('city-input')}`).each2((idx, obj) => { const [{ id }] = obj; const lastVal = _lastValues[id]; if (lastVal) obj.val(lastVal); }).change(onCityTextChange); updateCityLists(); loadSettings(); onPreviewChanged(); } function initLayer() { _mapLayer = new OpenLayers.Layer.Vector('WME Segment City Highlighter', { uniqueName: '__wmeSegmentCityHighlighter' }); W.map.addLayer(_mapLayer); // W.map.setLayerIndex(_mapLayer, W.map.getLayerIndex(W.map.roadLayers[0])-2); // HACK to get around conflict with URO+. If URO+ is fixed, this can be replaced with the setLayerIndex line above. LAYER_Z_INDEX = W.map.roadLayer.getZIndex() - 2; _mapLayer.setZIndex(LAYER_Z_INDEX); const checkLayerZIndex = () => { if (_mapLayer.getZIndex() !== LAYER_Z_INDEX) _mapLayer.setZIndex(LAYER_Z_INDEX); }; setInterval(() => { checkLayerZIndex(); }, 100); // END HACK _mapLayer.setOpacity(0.6); _mapLayer.setVisibility(true); } function init() { $(`<style type="text/css">${CSS}</style>`).appendTo('head'); W.model.cities.on('objectsadded', onCitiesAddedToModel); W.selectionManager.events.register('selectionchanged', null, onSelectionChanged); initLayer(); initGui(); } function bootstrap(tries = 1) { if (W && W.loginManager && W.loginManager.user && $('#sidebar').length) { init(); } else if (tries > 200) { log('Bootstrap has failed too many times. Exiting script.'); } else { if (tries % 20 === 0) log('Bootstrap failed. Trying again...'); setTimeout(() => bootstrap(++tries), 250); } } bootstrap(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址