您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds kadastr layer to the map
// ==UserScript== // @name WME Kadastr 🇺🇦 // @author Andrei Pavlenko, Anton Shevchuk, madnut // @version 2024.07.03.001 // @match https://beta.waze.com/*editor* // @match https://www.waze.com/*editor* // @exclude https://www.waze.com/*user/*editor/* // @grant none // @description Adds kadastr layer to the map // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js // @require https://greasyfork.org/scripts/450160-wme-bootstrap/code/WME-Bootstrap.js // @require https://update.greasyfork.org/scripts/450320/1281847/WME-UI.js // @namespace https://greasyfork.org/uk/users/160654-waze-ukraine // ==/UserScript== /* jshint esversion: 11 */ /* global $ */ /* global W */ /* global WazeWrap */ /* global WMEUIHelper */ /* global WMEUI */ /* global OpenLayers */ (function () { 'use strict'; const NAME = 'kadastr-ua'; const LOCAL_STORAGE_ITEM = NAME + '-layer'; const KADASTR_ID = '#' + NAME; const SWITCHER_ID = '#layer-switcher-item_map_' + NAME; const AREA_DATA_ID = `.${NAME}-area-data`; const LOCALITY_ID = `${NAME}-locality-name`; const KAD_TITLE_EN = "Kadastr 🇺🇦"; const KAD_TITLE_UA = "Кадастр 🇺🇦"; const RESOLUTIONS = [156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135, 0.298582141697406, 0.1, 0.05, 0.025 ]; let isLoaded = false; let kadastrLayer, markerLayer, markerIcon; let helper, tab; let visibility = !!localStorage.getItem(LOCAL_STORAGE_ITEM); const markerIconURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABR5JREFUeNrsXF1uEzEQdtKkSX/TqDwUISCIV6TkGQklHACpPQHhBuEG2xOQnIDkBqk4AImQeC4Sz9DwQh9AZUtp0iZtmakcRMG760121/bujGStlLU93vlmxp/t3aSurq4YiTpJkwkIAAKAhAAgAEgIAAKAhAAgAEgIAAKAhAAgAEgIgERIxqTBHj57UoELlpJDlQMo+1tv3u2b8kwp3c8DwOh1uGxDqUEpSDazofSgdAGMNgHg3+gbcGnwUpizOwSjiQXA+EEAeBsfjW4FYHgREBaA0CQAnL2+C6Uasqo+pjRdokELAPjk2gvB692ioabDZJ1OoPEZ19XjupMLgCLjawWCshQED45cft+v8S9hvKPJBTsdj2/8vpzNsnxmgaVTqVnSUQXS0UHSFmJdP8a3z87Z0WjEzsD4Ivk+HF1fcwBCMZ9nhdyin0jo8gVeMlIQeD/SzLJM3ZPzMft0ZLPDk1+Oxv9bsA7WxTbYVlLKfEzxT0F+Ug8aEj1/rkQPkbC1uqJtKlIRAVZUxp+mLuxLMhVZsY4Avtg6isr4M0ZCMcpFWtQRUJfJ+R7Gx1TRgbID5SkvO/w32y0SJOeEepQGyegEAFLMr+7pAo3cqL7/KPLQbv/xI4ww3Ot5LmqMfT8sFryoap33Ea8UJJN+vp0O/9BJgbwAw7dldAEQaMTXonubS3l2a3lJmzQUZQqqeVU4dk49LVnjo/C6LZ86fI3VRAAqXvx9fHkpujWYkZ1YvO0NQR0S64lK4gA4nUycbrUdcr5XFGCbtk9dsQZgw+3mhdj7p1sW82x3+NElNVbjtyJEMnTwSvDkmffsndoOvSOAJQ6ApIo2AOQWFpwoZWnWPp3aOulKNAAui6N5KGHNp65YA9Bzu4kHKiFsDdR96pIaq6kAHLgD4LgrUoVUsj1D+sE2VZH3u+iSGmssIwDF5RSrDQat+DB+xWkNsLaYDWSsxgHADzoGbnU2nfdorg/QwbA1CePXmMtB/6b3PtAgykOZqCdh10VVNp1mxXzODYS3YOCuKCXhb3gP6zgZH/tGHfOMMWiJ+kAGaeFntzq4Jf3l+KfU+S/IB371PF/Gw/p762syDOhBbCOAP1jfi47eXlmRpYplGeP76LOflDNhKW+VSBdMti+8BjE24wEAD8MJsiNjuNLGOluVYy1CwbY+jN/hY0vEShhfQbdlUsedtVV2F4wowd1vrCmwDbaVTGU2H1PkovLVRHzgV37a4GEKHqyfTSb/Hd5gusplMtdeP0PqeqnquwGlr6cDCDjh3Ve8HYO8v6RKuerNuDpTL0rHoBQAPun1FQ6hr2Li1SkCVHug8ghUDgBf+LQUqG6p+iZAtwiYLoDsCPXZKhZd2gLA30KL0iAWfSUppqX4FkM5ZDUfwPgVXZ5Zt7ciGjHRYSYAnBLuhahiTzXt1D0CwvbQhm4Pqx0AnBruhtD1rg6004QIQGkGTEun/5jCCAB5Whpkumjo+Fc12tHQkGipVrTTlBQU5KTZ0PkBtQYgAFqqHe00LQLm9eCG7g+nPQBz0FItaaeJETClpQMf9Qe60k4jAZhht9TSlXYaRUMFtBQnVK8/9cNjxpopz2TaN2JWQHUIgDloqdtbdR3daafpETD1cNE+kW2a9xsJAKeWIobTNIF2xiECRLTUGNoZCwAEtNQY2mk0DXWgpcwk2vmvZJjZ0jB8/GZHQByE/qyDACAASAgAAoCEACAASAgAAoCEACAASAgAAoCEAEiG/BZgAIdH+4FfAgoVAAAAAElFTkSuQmCC'; WMEUI.addStyle(` #loader-thinking { display: inline-block; margin: 0; padding: 0; animation-name: spin; animation-duration: 5000ms; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes spin { from { transform:rotate(0deg); } to { transform:rotate(360deg); } } `); $(document) .on('bootstrap.wme', ready); function ready() { if (isLoaded) { // ugly fix for script duplications return; } isLoaded = true; polyfillOpenLayers(); createMarkerIcon(); addKadastrLayer(); addMarkerLayer(); createSwitcher(); createTab(); addHandlers(); } function createSwitcher() { let $ul = $('.collapsible-GROUP_DISPLAY'); let $li = document.createElement('li'); let checkbox = document.createElement("wz-checkbox"); checkbox.id = 'layer-switcher-item_map_' + NAME; checkbox.className = "hydrated"; checkbox.checked = visibility; checkbox.appendChild(document.createTextNode(KAD_TITLE_UA)); $li.append(checkbox); $ul.append($li); } function switchLayer(flag) { localStorage.setItem(LOCAL_STORAGE_ITEM, flag ? '1' : ''); visibility = flag; kadastrLayer.setVisibility(flag); markerLayer.setVisibility(flag); if (flag) { $(KADASTR_ID).tab('show'); $(AREA_DATA_ID).html("Оберіть об'єкт для отримання інформації"); } } function createTab() { // Setup Tab with options helper = new WMEUIHelper(NAME); tab = helper.createTab(KAD_TITLE_UA, ''); let text = visibility ? "Оберіть об'єкт для отримання посилання" : "Ввімкніть шар кадастру та оберіть об'єкт для отримання інформації"; tab.addText('area-data', text); tab.inject(); } function addKadastrLayer() { const maxZoom = 22; kadastrLayer = new OpenLayers.Layer.XYZ( KAD_TITLE_EN, 'https://cdn.kadastr.live/tiles/raster/styles/parcels/${z}/${x}/${y}.png', { sphericalMercator: true, isBaseLayer: false, visibility: visibility, zoomOffset: 12, RESOLUTION_PROPERTIES: {}, resolutions: RESOLUTIONS, serverResolutions: RESOLUTIONS.slice(0, maxZoom), transitionEffect: "resize", attribution: "", uniqueName: NAME } ); W.map.addLayer(kadastrLayer); } function addMarkerLayer() { markerLayer = new OpenLayers.Layer.Markers( 'Kadastr marker', { isBaseLayer: false, visibility: visibility } ); W.map.addLayer(markerLayer); } function addHandlers() { W.map.events.register('click', null, e => { if (!visibility) return false; let coordinates = W.map.getLonLatFromPixel(e.xy); drawMarker(coordinates); prepareLink(coordinates); //fetchAreaData(coordinates); $(KADASTR_ID).tab('show'); }); $(document).on('click', SWITCHER_ID, e => { switchLayer(e.target.checked); }); /* name, desc, group, title, shortcut, callback, scope */ new WazeWrap.Interface.Shortcut( KAD_TITLE_EN, 'Відображення кадастру', 'layers', KAD_TITLE_UA, 'S+K', function () { let checked = localStorage.getItem(LOCAL_STORAGE_ITEM); switchLayer(!checked); $(SWITCHER_ID).prop('checked', !checked); }, null ).add(); } function createMarkerIcon() { const size = new OpenLayers.Size(50, 50); const offset = new OpenLayers.Pixel(-(size.w/2), -size.h*0.8); markerIcon = new OpenLayers.Icon(markerIconURL, size, offset); } function drawMarker(coordinates) { let {lon, lat} = coordinates; let lonLat = new OpenLayers.LonLat(lon, lat); markerLayer.clearMarkers(); markerLayer.addMarker(new OpenLayers.Marker(lonLat, markerIcon)); } function prepareLink(coordinates) { // https://kadastr.live/parcel/4610136300:04:017:0088 let url = new URL('https://kadastr.live/parcel/'); //url.searchParams.set('cc', coordinates.lon +','+ coordinates.lat); //url.searchParams.set('z', '16'); //url.searchParams.set('l', 'kadastr'); //url.searchParams.set('bl', 'ortho10k_all'); let $area = $(AREA_DATA_ID); $area.html(''); //$area.html('<a href="'+ url.toString() +'" target="_blank">'+ url.hostname +'?cc='+ url.searchParams.get('cc') +'</a>'); $area.html('<a href="'+ url.toString() +'" target="_blank">'+ url.toString() +'</a>'); } function fetchAreaData(coordinates) { // https://cdn.kadastr.live/tiles/maps/kadastr/16/37131/22279.pbf let $area = $(AREA_DATA_ID); $area.html('<div id="loader-thinking">🤔</div> Завантаження'); let params = new URLSearchParams(); params.set('x', coordinates.lat); params.set('y', coordinates.lon); params.set('zoom', '13'); params.set('actLayers[]', 'kadastr'); fetch('https://waze.com.ua/kadastr_api', { method: 'POST', body: params }).then(data => data.json()).then(data => { let parcel = data.parcel; let district = data.district; if (!parcel) { $area.html('😕 Ділянку не знайдено'); return; } parcel = parcel[0]; $area.html(''); $area.append(` <div><strong>Ділянка: </strong>${parcel.cadnum}</div> <div><strong>Область: </strong>${district.natoobl}</div> <div><strong>Населений пункт: </strong><span id="${LOCALITY_ID}">не визначено</span></div> <div><strong>Тип власності: </strong>${parcel.ownership}</div> <div><strong>Цільове призначення: </strong>${parcel.use}</div> <div><strong>Площа: </strong>${parcel.area+' '+parcel.unit_area}</div> <div style="margin-top: 10px;"><a target="_blank" style="color: #26bae8; padding: 5px 0;" href="${parcel.linkToOwnershipInfo}">Інформація про ділянку</a></div> `); getLocalityName(parcel.koatuu, data.ikk.zona); }).catch(err => { $area.html('⛔ Помилка'); console.error(err); }); } function getLocalityName(koatuu, zoneNumber) { fetch(`https://waze.com.ua/kadastr_locality?code=${koatuu}&zone_number=${zoneNumber}`) .then(response => response.json()) .then(data => { if (data.name) { let localityName = data.name.toLowerCase().replace(/^./, data.name[0].toUpperCase()); if (/\//.test(localityName)) return; $('#' + LOCALITY_ID).html(localityName); } }); } /** * This polyfill is required for OpenLayers.Icon functionality * @link ? */ function polyfillOpenLayers() { OpenLayers.Icon = OpenLayers.Class({ url: null, size: null, offset: null, calculateOffset: null, imageDiv: null, px: null, initialize: function initialize(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; var e = OpenLayers.Util.createUniqueID("OL_Icon_"); this.imageDiv = OpenLayers.Util.createAlphaImageDiv(e) }, destroy: function destroy() { this.erase(), OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild), this.imageDiv.innerHTML = "", this.imageDiv = null }, clone: function clone() { return new OpenLayers.Icon(this.url, this.size, this.offset, this.calculateOffset) }, setSize: function setSize(a) { null != a && (this.size = a), this.draw() }, setUrl: function setUrl(a) { null != a && (this.url = a), this.draw() }, draw: function draw(a) { return OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, this.size, this.url, "absolute"), this.moveTo(a), this.imageDiv }, erase: function erase() { null != this.imageDiv && null != this.imageDiv.parentNode && OpenLayers.Element.remove(this.imageDiv) }, setOpacity: function setOpacity(a) { OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, null, null, null, null, a) }, moveTo: function moveTo(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 display(a) { this.imageDiv.style.display = a ? "" : "none" }, isDrawn: function isDrawn() { return this.imageDiv && this.imageDiv.parentNode && 11 !== this.imageDiv.parentNode.nodeType; }, CLASS_NAME: "OpenLayers.Icon" }); } })();