您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Quickly add house numbers based on open data sources of house numbers
// ==UserScript== // @name WME Quick HN Importer // @namespace http://www.wazebelgium.be/ // @version 1.2.11 // @description Quickly add house numbers based on open data sources of house numbers // @author Tom 'Glodenox' Puttemans // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/ // @connect www.wazebelgium.be // @grant GM_xmlhttpRequest // ==/UserScript== /* global W, OpenLayers, I18n, require */ (function() { 'use strict'; function getUIHookElement() { return document.getElementById('search-autocomplete'); } function init(e) { if (e && e.user == null) { return; } if (document.getElementById('user-info') == null) { setTimeout(init, 500); log('user-info element not yet available, page still loading'); return; } if (typeof W === 'undefined' || typeof W.loginManager === 'undefined' || typeof W.prefs === 'undefined' || typeof W.map === 'undefined' || typeof OpenLayers === 'undefined' || getUIHookElement() == null) { setTimeout(init, 300); return; } if (!W.loginManager.user) { W.loginManager.events.register('login', null, init); W.loginManager.events.register('loginStatus', null, init); // Double check as event might have triggered already if (!W.loginManager.user) { return; } } var currentStreetId = null; var streetNames = {}; var layer = new OpenLayers.Layer.Vector('Quick HN importer', { uniqueName: 'quick-hn-importer', styleMap: new OpenLayers.StyleMap({ "default": new OpenLayers.Style({ fillColor: '${fillColor}', fillOpacity: '${opacity}', fontColor: '#111111', fontWeight: 'bold', strokeColor: '#ffffff', strokeOpacity: '${opacity}', strokeWidth: 2, pointRadius: '${radius}', label: '${number}', title: '${title}' }, { context: { fillColor: (feature) => feature.attributes && feature.attributes.street == currentStreetId ? '#99ee99' : '#cccccc', radius: (feature) => feature.attributes && feature.attributes.number ? Math.max(feature.attributes.number.length * 6, 10) : 10, opacity: (feature) => feature.attributes && feature.attributes.street == currentStreetId && feature.attributes.processed ? 0.3 : 1, title: (feature) => feature.attributes && feature.attributes.number && feature.attributes.street ? streetNames[feature.attributes.street] + ' ' + feature.attributes.number : '' } }) }), }); I18n.translations[I18n.currentLocale()].layers.name['quick-hn-importer'] = 'Quick HN Importer'; layer.setVisibility(false); W.map.addLayer(layer); var exitMessage = document.createElement('div'); exitMessage.style.position = 'absolute'; exitMessage.style.top = '35px'; exitMessage.style.width = '100%'; exitMessage.style.pointerEvents = 'none'; exitMessage.style.display = 'none'; exitMessage.innerHTML = `<div style="margin:0 auto; max-width:200px; text-align:center; background:rgba(0, 0, 0, 0.5); color:white; border-radius:3px; padding:5px 15px;">Press ESC to stop adding house numbers</div>`; document.getElementById('map').appendChild(exitMessage); var loadingMessage = document.createElement('div'); loadingMessage.style.position = 'absolute'; loadingMessage.style.bottom = '35px'; loadingMessage.style.width = '100%'; loadingMessage.style.pointerEvents = 'none'; loadingMessage.style.display = 'none'; loadingMessage.innerHTML = `<div style="margin:0 auto; max-width:300px; text-align:center; background:rgba(0, 0, 0, 0.5); color:white; border-radius:3px; padding:5px 15px;"><i class="fa fa-pulse fa-spinner"></i> Loading address points</div>`; document.getElementById('map').appendChild(loadingMessage); var streets = {}; // Container for all currently loaded street names var updateLayer = () => { var segmentSelection = W.selectionManager.getSegmentSelection(); if (!segmentSelection.segments || segmentSelection.segments.length == 0) { return; } loadingMessage.style.display = null; var bounds = null; segmentSelection.segments.forEach((segment) => bounds == null ? bounds = segment.attributes.geometry.getBounds() : bounds.extend(segment.attributes.geometry.getBounds())); GM_xmlhttpRequest({ method: "GET", url: `https://www.wazebelgium.be/quick-hn-import/?left=${Math.floor(bounds.left - 200)}&top=${Math.floor(bounds.top + 200)}&right=${Math.floor(bounds.right + 200)}&bottom=${Math.floor(bounds.bottom - 200)}`, onload: function(response){ var features = []; var currentHouseNumbers = getSelectionHNs(); response.responseText.split("\n").forEach((line) => { var values = line.split(','); if (values.length == 4) { // House number features.push(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(values[0], values[1]), { number: values[2], street: values[3], processed: currentHouseNumbers.indexOf(values[2]) != -1 })); } else if (values.length == 2) { // Street name streets[values[1]] = values[0]; streetNames[values[0]] = values[1]; } }); var streetIds = segmentSelection.segments[0].attributes.streetIDs; streetIds.push(segmentSelection.segments[0].attributes.primaryStreetID); var selectedStreetNames = W.model.streets.getByIds(streetIds).map((street) => street.attributes.name); var matchingStreetName = selectedStreetNames.find((streetName) => streets[streetName] != undefined); currentStreetId = streets[matchingStreetName]; layer.addFeatures(features); loadingMessage.style.display = 'none'; }, onerror: (error) => { console.error('Error', error); loadingMessage.style.display = 'none'; } }); }; var editButtons = getUIHookElement().parentNode; var menuToggle = document.createElement('wz-checkbox'); menuToggle.checked = false; menuToggle.style.display = 'none'; menuToggle.style.alignItems = 'center'; menuToggle.textContent = "Quick HN importer"; menuToggle.addEventListener('click', (e) => { if (layer.features.length == 0) { updateLayer(); } layer.setVisibility(e.target.checked); if (e.target.checked) { editButtons.querySelector('.add-house-number').click(); } }); var menuSheet = new CSSStyleSheet(); menuSheet.replaceSync(` label.wz-checkbox slot { font-weight: 500; font-size: 14px; letter-spacing: 0.3px; font-family: "Waze Boing", "Waze Boing HB", "Rubik", sans-serif; } `); menuToggle.shadowRoot.adoptedStyleSheets.push(menuSheet); getUIHookElement().after(menuToggle); var houseNumbersLayer = null; // Observe the house number markers to automatically insert the data var houseNumberObserver = new MutationObserver((mutations) => { if (!menuToggle.checked) { exitMessage.style.display = 'none'; return; } exitMessage.style.display = houseNumbersLayer.querySelector('div.content.active.new') ? 'block' : 'none'; let changeTally = 0; mutations.forEach((mutation) => { if (mutation.type == 'childList') { changeTally += mutation.addedNodes.length - mutation.removedNodes.length; } else if (mutation.type == 'attributes') { if (mutation.target.classList.contains('content') && !mutation.target.classList.contains('new') && mutation.target.classList.contains('active')) { var numberInput = mutation.target.querySelector('input.number'); if (numberInput.value == '') { // Do not interfere when adjusting an existing house number // Find nearest house number var houseNumberMarkers = W.map.getLayerByName('houseNumbersMapEditorMarkers'); var locationLonLat = houseNumberMarkers.markers.find((marker) => marker.element.classList.contains('is-active')).lonlat; var location = new OpenLayers.Geometry.Point(locationLonLat.lon, locationLonLat.lat); var nearestFeature = layer.features.filter((feature) => !feature.attributes.processed).reduce((prev, feature) => prev.geometry.distanceTo(location) > feature.geometry.distanceTo(location) ? feature : prev); // Fill in data and prepare for next click if (nearestFeature && nearestFeature.geometry.distanceTo(location) < 50) { let setValue = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; setValue.call(numberInput, nearestFeature.data.number); // dispatch event so React sees the content as changed numberInput.dispatchEvent(new InputEvent('input', { 'bubbles': true })); numberInput.blur(); nearestFeature.attributes.processed = true; layer.redraw(); editButtons.querySelector('.add-house-number').click(); } } } } }); if (changeTally != 0) { // Refresh the processed state when a house number gets removed var currentHouseNumbers = getSelectionHNs(); layer.features.forEach((feature) => feature.attributes.processed = currentHouseNumbers.indexOf(feature.attributes.number) != -1); layer.redraw(); } }); // Observe house number mode to insert the "Quick HN Importer" checkbox var menuObserver = new MutationObserver(() => { if (editButtons.querySelector('.add-house-number') != null) { getUIHookElement().style.display = 'none'; menuToggle.style.display = 'inline-flex'; houseNumbersLayer = document.querySelector('div.olLayerDiv.house-numbers-layer'); houseNumberObserver.observe(houseNumbersLayer, { childList: true, subtree: true, attributes: true }); if (menuToggle.checked) { updateLayer(); layer.setVisibility(true); } } else { getUIHookElement().style.display = null; menuToggle.style.display = 'none'; layer.setVisibility(false); layer.removeAllFeatures(); streets = {}; streetNames = {}; } }); menuObserver.observe(editButtons, { childList: true }); // Observe the edit panel's contents to add the "Nudge segment" button var nudgeButton = document.createElement('button'); nudgeButton.className = 'action-button waze-btn waze-btn-white'; nudgeButton.style.marginTop = '14px'; nudgeButton.textContent = 'Nudge segment'; nudgeButton.addEventListener('click', () => { var UpdateSegmentGeometry = require('Waze/Action/UpdateSegmentGeometry'); var MoveNode = require("Waze/Action/MoveNode"); var MultiAction = require("Waze/Action/MultiAction"); var multiAction = new MultiAction(); multiAction.setModel(W.model); multiAction._description = 'Nudge segment'; var selectedSegment = W.selectionManager.getSegmentSelection().segments[0]; if (selectedSegment.geometry.components.length > 2) { let newGeometry = selectedSegment.geometry.clone(); newGeometry.components[1].x += 0.0001; multiAction.doSubAction(new UpdateSegmentGeometry(selectedSegment, selectedSegment.geometry.clone(), newGeometry)); } else { var nodeToNudge = W.selectionManager.getSegmentSelection().segments[0].getFromNode(); var segments = nodeToNudge.getSegmentIds().map((id) => W.model.segments.getObjectById(id)); var segmentGeometries = {}; segments.forEach((segment) => { let newGeometry = segment.geometry.clone(); newGeometry.components.filter((component) => component.x == nodeToNudge.geometry.x && component.y == nodeToNudge.geometry.y).x += 0.0001; multiAction.doSubAction(new UpdateSegmentGeometry(segment, segment.geometry.clone(), newGeometry)); segmentGeometries[segment.attributes.id] = segment.geometry.clone(); }); let newGeometry = nodeToNudge.geometry.clone(); newGeometry.x += 0.0001; multiAction.doSubAction(new MoveNode(nodeToNudge, nodeToNudge.geometry.clone(), newGeometry, segmentGeometries, {})); } W.model.actionManager.add(multiAction); }); var editPanelObserver = new MutationObserver(() => { if (document.getElementById('edit-panel').style.display == 'none') { return; } var editPanelButtons = document.querySelector('#segment-edit-general .form-group.more-actions'); if (editPanelButtons) { editPanelButtons.appendChild(nudgeButton); } }); editPanelObserver.observe(document.getElementById('edit-panel'), { attributes: true }); } function getSelectionHNs() { var selectedSegmentIDs = W.selectionManager.getSegmentSelection().segments.map((segment) => segment.attributes.id); return W.model.segmentHouseNumbers.getObjectArray().filter((houseNumber) => selectedSegmentIDs.indexOf(houseNumber.attributes.segID) != -1).map((houseNumber) => houseNumber.attributes.number); } function log(message) { if (typeof message === 'string') { console.log('%cWME Quick HN Importer: %c' + message, 'color:black', 'color:#d97e00'); } else { console.log('%cWME Quick HN Importer:', 'color:black', message); } } init(); })();