Greasy Fork 还支持 简体中文。

WME Kadastr 🇺🇦

Adds kadastr layer to the map

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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"
    });
  }
})();