let map, houseGroup, entransePolygonsGroup, entranseNumPolygonsGroup, splitPolygonsGroup, RectangleGroup; let numApartments = 1; let mode = ''; const Constructor = { info: { type: null, title: null, number: null, points: [], points_number: [], point_icons: [], geo: [], osm_id: [], settlement: [], description: null, entrance: [], apartments: {}, zoom: null }, init: async () => { let html = await fetch('/lib/pages/constructor/index.html').then((response) => response.text()); app.innerHTML = html; map = ""; map = ""; houseGroup = ""; entransePolygonsGroup = ""; entranseNumPolygonsGroup = ""; splitPolygonsGroup = ""; RectangleGroup = ""; numApartments = 1; Constructor.apartments.init(); document.querySelectorAll('input[name="info-type"]').forEach(radio => { radio.addEventListener('change', event => { console.log(`Выбран: ${event.target.value}`); Constructor.info.type = event.target.value; let detailsInfo_number = document.getElementById('details-info-number'); let detailsInfo_osm = document.getElementById('details-info-osm'); let detailsInfo_number_title = document.getElementById('info-number-title'); let detailsInfo_osm_title = document.getElementById('info-osm-title'); let detailsMap_title = document.getElementById('details-map-title'); let detailsMap_buttons_entranse = document.getElementById('details-map-buttons-entranse'); let detailsMap_buttons_homestead = document.getElementById('details-map-buttons-homestead'); let detailsMap_button = document.getElementById('map-form-button'); let detailsArea = document.getElementById('details-area'); switch (event.target.value) { case 'points': detailsInfo_number.style.display = "none"; detailsInfo_osm.style.display = "none"; detailsInfo_number_title.removeAttribute("required"); detailsInfo_osm_title.removeAttribute("required"); detailsMap_title.innerHTML = "Крок 2. Створення точок на карті" detailsMap_buttons_entranse.style.display = "none"; detailsMap_buttons_homestead.style.display = "none"; detailsMap_button.innerText = "Зберегти"; detailsArea.style.display = "none"; break; case 'homestead': detailsInfo_number.style.display = ""; detailsInfo_osm.style.display = ""; detailsInfo_number_title.setAttribute("required", ""); detailsInfo_osm_title.setAttribute("required", ""); detailsMap_title.innerHTML = "Крок 2. Створення житлових територій" detailsMap_buttons_entranse.style.display = "none"; detailsMap_buttons_homestead.style.display = ""; detailsMap_button.innerText = "Зберегти"; detailsArea.style.display = "none"; break; default: detailsInfo_number.style.display = ""; detailsInfo_osm.style.display = ""; detailsInfo_number_title.setAttribute("required", ""); detailsInfo_osm_title.setAttribute("required", ""); detailsMap_title.innerHTML = "Крок 2. Створення підʼїздів" detailsMap_buttons_entranse.style.display = ""; detailsMap_buttons_homestead.style.display = "none"; detailsMap_button.innerText = "Далі"; detailsArea.style.display = ""; break; } }); }); document.getElementById("info-form").addEventListener("submit", function (event) { event.preventDefault(); let details_map = document.getElementById("details-map"); details_map.removeAttribute("disabled"); details_map.open = true; const infoForm = document.getElementById("info-form"); let osm = () => { const a = document.getElementById("info-osm-title").value; const b = a.replace(/\s+/g, "").split(',') return b; } Constructor.info.type = infoForm.querySelector('input[name="info-type"]:checked').value; Constructor.info.title = document.getElementById("info-address-title").value; Constructor.info.number = document.getElementById("info-number-title").value; Constructor.info.settlement = document.getElementById("info-settlement-title").value; Constructor.info.osm_id = osm(); Constructor.osm.init(); Constructor.osm.setMap(); }) document.getElementById("map-form").addEventListener("submit", async function (event) { event.preventDefault(); let details_area = document.getElementById("details-area"); details_area.removeAttribute("disabled"); details_area.open = true; switch (Constructor.info.type) { case 'points': break; case 'homestead': await Constructor.api.setPack(); break; default: let details_area = document.getElementById("details-area"); details_area.removeAttribute("disabled"); details_area.open = true; break; } }) document.getElementById("area-form").addEventListener("submit", async function (event) { event.preventDefault(); await Constructor.api.setPack(); }) }, apartments: { init: () => { let listArea = document.getElementById('list-area'); listArea.innerHTML = ``; for (const key in Constructor.info.apartments) { const element = Constructor.info.apartments[key]; const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(key); console.log(element); let listArea = document.getElementById('list-area'); listArea.innerHTML += `

${Constructor.info.entrance[pos].title}

`; element.sort((a, b) => a.floors_number - b.floors_number); let uniqueFloors = [...new Set(element.map(obj => obj.floors_number))]; for (let i = 0; i < uniqueFloors.length; i++) { let num = uniqueFloors[i]; let areaBlock = document.getElementById(`area-${key}`); let div = document.createElement('div'); div.className = "block-apartments-floors"; div.id = `floors-${key}-${num}` div.innerHTML = ` `; areaBlock.prepend(div); } element.sort((a, b) => b.title - a.title); for (let i = 0; i < element.length; i++) { const apartment = element[i]; let num = apartment.floors_number; let floorsBlock = document.getElementById(`floors-${key}-${apartment.floors_number}`); let div = document.createElement('div'); div.className = "block-apartments-number"; div.id = `block-apartments-${key}-${apartment.editor_id}` div.innerHTML = ` `; floorsBlock.prepend(div); } element.sort((a, b) => b.floors_number - a.floors_number); } }, editNum: (element) => { numApartments = Number(element.value); }, addFloors: (area) => { let areaBlock = document.getElementById(`area-${area}`); let uniqueFloors = [...new Set(Constructor.info.apartments[area].map(obj => obj.floors_number))]; let new_floors = uniqueFloors.length + 1; let new_id = makeid(5); let div = document.createElement('div'); div.className = "block-apartments-floors"; div.id = `floors-${area}-${new_floors}` div.innerHTML = `

Поверх ${new_floors}

` areaBlock.prepend(div); Constructor.info.apartments[area].push({ editor_id: new_id, entrance_id: null, apartment_number: Constructor.info.apartments[area].length, title: numApartments, floors_number: new_floors }); numApartments++; let next_apartment_title = document.getElementById('next-apartment-title'); next_apartment_title.value = numApartments; }, addApartment: (area, floors) => { let new_id = makeid(5); Constructor.info.apartments[area].push({ editor_id: new_id, entrance_id: null, apartment_number: Constructor.info.apartments[area].length, title: numApartments, floors_number: Number(floors) }) let floorsBlock = document.getElementById(`floors-${area}-${floors}`); document.getElementById(`buttonApartment-${area}-${floors}`).remove(); floorsBlock.innerHTML += `
`; floorsBlock.innerHTML += `` numApartments++; let next_apartment_title = document.getElementById('next-apartment-title'); next_apartment_title.value = numApartments; }, editApartment: (area, apartament) => { let input = document.getElementById(`apartament-${area}-${apartament}`); input.setAttribute("value", input.value); const pos = Constructor.info.apartments[area].map(e => e.editor_id).indexOf(apartament); info.apartments[area][pos].title = input.value; }, deleteApartment: (area, apartament) => { document.getElementById(`block-apartments-${area}-${apartament}`).remove(); const pos = Constructor.info.apartments[area].map(e => e.editor_id).indexOf(apartament); Constructor.info.apartments[area].splice(pos, 1); numApartments--; let next_apartment_title = document.getElementById('next-apartment-title'); next_apartment_title.value = numApartments; } }, osm: { init: () => { let center = { lat: 49.5629016, lng: 25.6145625 }; let zoom = 19; let googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', { maxZoom: 20, minZoom: 15, subdomains: ['mt0', 'mt1', 'mt2', 'mt3'] }); let osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, minZoom: 15 }); let mytile = L.tileLayer('https://sheep-service.com/map/{z}/{x}/{y}.webp', { maxZoom: 20, minZoom: 15, tms: true }); if (!map) { houseGroup = new L.FeatureGroup(); splitPolygonsGroup = new L.FeatureGroup(); RectangleGroup = new L.FeatureGroup(); entransePolygonsGroup = new L.FeatureGroup(); entranseNumPolygonsGroup = new L.FeatureGroup(); map = L.map('map', { renderer: L.canvas(), center, zoom, layers: [ googleHybrid, osm, mytile, houseGroup, entransePolygonsGroup, entranseNumPolygonsGroup, splitPolygonsGroup, RectangleGroup, ], zoomControl: false }); let baseMaps = { "Google Hybrid": googleHybrid, "OpenStreetMap": osm, "Territory Map": mytile }; let overlayMaps = { "Будинки": houseGroup, "Під'їзди": entransePolygonsGroup, "Номера під'їздів": entranseNumPolygonsGroup, "Слой редактирования": splitPolygonsGroup, "Слой линейки": RectangleGroup, }; L.control.layers(baseMaps, overlayMaps, { position: 'bottomright' }).addTo(map); map.pm.setLang("ua"); map.pm.addControls({ position: 'bottomright', drawCircleMarker: false, drawPolyline: false, drawPolygon: false, drawRectangle: false, drawCircle: false, drawText: false, drawMarker: false, cutPolygon: false, tooltips: false }); map.pm.toggleControls() map.pm.setGlobalOptions({ layerGroup: splitPolygonsGroup }) } Constructor.editor.init(); }, getOSM: async (wayId) => { const overpassUrl = `https://overpass-api.de/api/interpreter?data=[out:json];way(${wayId});(._;>;);out;`; return await fetch(overpassUrl) .then(response => response.json()) .then(data => { const nodes = new Map(); data.elements.forEach(el => { if (el.type === "node") { nodes.set(el.id, { lat: el.lat, lng: el.lon }); } }); const way = data.elements.find(el => el.type === "way"); if (way) { const coordinates = way.nodes.map(nodeId => nodes.get(nodeId)); console.log("Координаты точек:", coordinates); return [coordinates]; } else { console.log("Way не найден!"); } }) .catch(error => console.error("Ошибка запроса:", error)); }, setMap: async () => { houseGroup.clearLayers(); if (!Constructor.info.osm_id) { let osm = () => { const a = document.getElementById("info-osm-title").value; const b = a.replace(/\s+/g, "").split(',') return b; } Constructor.info.osm_id = osm(); } for (let i = 0; i < Constructor.info.osm_id.length; i++) { const element = await Constructor.osm.getOSM(Constructor.info.osm_id[i]); let coords = []; element[0].forEach((feature) => coords.push([feature.lat, feature.lng])); let centerPoint = turf.centerOfMass(turf.polygon([coords])); if (Constructor.info.type == "homestead") { map.setView([centerPoint.geometry.coordinates[0], centerPoint.geometry.coordinates[1]], 17); L.polygon(element, { color: "#f2bd53", radius: 500, fillOpacity: 0.3, dashArray: '20, 15', dashOffset: '20', }).addTo(houseGroup); } else if (Constructor.info.type == "house") { map.setView([centerPoint.geometry.coordinates[0], centerPoint.geometry.coordinates[1]], 18); Constructor.info.points.push(element); Constructor.info.points_number.push(element[0][0]); L.polygon(element, { color: "#585858", fillColor: "#f2bd53", fillOpacity: 0.8, tm_id: `house_${i}` }).addTo(houseGroup); } } console.log(Constructor.info); } }, api: { setPack: async () => { let area_form_button = document.getElementById('area-form-button'); area_form_button.innerText = "Зачекайте..."; Constructor.info.geo = await map.getCenter(); Constructor.info.zoom = await map.getZoom(); console.log(Constructor.info); const uuid = localStorage.getItem('uuid'); const URL = `${CONFIG.api}constructor`; await fetch(URL, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": uuid }, body: JSON.stringify(Constructor.info) }) .then(response => { if (response.status == 200) { console.log({ 'setPack': 'ok' }); area_form_button.innerText = "Запис додано"; return response.json() } else { console.log('err'); area_form_button.innerText = "Помилка запису"; return } }) .then(data => { console.log(data); Territory.house.list = []; Territory.homestead.list = []; Router.navigate(`/territory/manager/${Constructor.info.type}/${data.id}`); setTimeout(() => { area_form_button.innerText = "Зберегти"; }, 3000); }) .catch(err => { console.log(err); area_form_button.innerText = "Помилка запису"; }) } }, editor: { init: () => { map.on('pm:create', function (event) { let layer = event.layer; let newCoords = layer.getLatLngs()[0].map(function (coords) { return [coords.lng, coords.lat]; }); newCoords.push(newCoords[0]); let turfNew = turf.polygon([newCoords]); if (mode == 'entranse') { console.log(L.PM.Utils.findLayers(houseGroup)); for (let i = 0; i < L.PM.Utils.findLayers(houseGroup).length; i++) { const polygon = L.PM.Utils.findLayers(houseGroup)[i]._latlngs; let polygonCoords = polygon[0].map(function (coords) { return [coords.lng, coords.lat]; }); polygonCoords.push(polygonCoords[0]); // Замикаємо полігон let turfPolygon = turf.polygon([polygonCoords]); // Пошук точки перехрестя let intersections = turf.intersect(turfNew, turfPolygon); if (intersections) { let points = []; let coords = []; intersections.geometry.coordinates[0].forEach(function (feature) { coords.push([feature[1], feature[0]]) points.push({ lat: feature[1], lng: feature[0] }) }); let centerPoint = turf.centerOfMass(turf.polygon([coords])); let points_number = { lat: centerPoint.geometry.coordinates[0], lng: centerPoint.geometry.coordinates[1] } let newID = makeid(6); Constructor.info.entrance.push({ editor_id: newID, house_id: null, entrance_number: Constructor.info.entrance.length, title: `Під'їзд ${Constructor.info.entrance.length + 1}`, points: points, points_number: points_number, floors_quantity: null, apartments_quantity: null, created_at: null }) Constructor.info.apartments[newID] = []; Constructor.apartments.init(); let listEntranse = document.getElementById('list-entranse'); listEntranse.style.display = ""; listEntranse.innerHTML += `
`; L.polygon(coords, { color: 'red' }).addTo(entransePolygonsGroup); let myIcon = L.divIcon({ className: 'entranse_number', html: `
${Constructor.info.entrance.length}
` }); L.marker(centerPoint.geometry.coordinates, { icon: myIcon }).addTo(entranseNumPolygonsGroup); } } } }); }, drawEntranse: () => { mode = 'entranse'; map.pm.setGlobalOptions({ layerGroup: splitPolygonsGroup }) map.pm.setPathOptions({ color: '#f2bd53', fillColor: '#f2bd53', fillOpacity: 0.5, radius: 500 }); if (map.pm.globalDragModeEnabled()) { map.pm.disableDraw(); } else { map.pm.enableDraw("Polygon", { snappable: true, snapDistance: 20, tooltips: false, templineStyle: { color: '#f2bd53' }, hintlineStyle: { color: '#f2bd53', dashArray: [5, 5] } }); } }, drawRectangle: () => { mode = 'rectangle'; document.getElementById('ruler_divide').style.display = 'block' document.getElementById('ruler').style.width = "calc(50% - 5px)" document.getElementById('ruler_divide').style.width = "calc(50% - 5px)" document.getElementById('ruler').innerHTML = 'Лінійка' map.pm.toggleControls() RectangleGroup.clearLayers(); RectangleGroup.addTo(map); map.pm.setGlobalOptions({ layerGroup: RectangleGroup }) map.pm.setPathOptions({ color: '#b645ef', fillColor: '#b645ef', fillOpacity: 0.5, radius: 500 }); if (map.pm.globalDragModeEnabled()) { map.pm.disableDraw(); } else { map.pm.enableDraw("Rectangle", { snappable: true, snapDistance: 20, tooltips: false, templineStyle: { color: '#b645ef' }, hintlineStyle: { color: '#b645ef', dashArray: [5, 5] } }); } }, ruler: (n) => { n = prompt('На сколько поделить линейку ?', 2); const polygon = L.PM.Utils.findLayers(RectangleGroup)[0]._latlngs; let newCoords = polygon[0].map(function (coords) { return [coords.lng, coords.lat]; }); newCoords.push(newCoords[0]); let turfNew = turf.polygon([newCoords]); console.log(turfNew); var line = turf.polygonToLine(turfNew); console.log(line.geometry.coordinates); coords = line.geometry.coordinates; for (let i = 1; i < n; i++) { let a1 = (((coords[2][1] - coords[1][1]) / n) * i + coords[1][1]) let b1 = (((coords[2][0] - coords[1][0]) / n) * i + coords[1][0]) let a2 = (((coords[3][1] - coords[0][1]) / n) * i + coords[0][1]) let b2 = (((coords[3][0] - coords[0][0]) / n) * i + coords[0][0]) let c1 = (((coords[1][1] - coords[0][1]) / n) * i + coords[0][1]) let d1 = (((coords[1][0] - coords[0][0]) / n) * i + coords[0][0]) let c2 = (((coords[2][1] - coords[3][1]) / n) * i + coords[3][1]) let d2 = (((coords[2][0] - coords[3][0]) / n) * i + coords[3][0]) L.circleMarker([a1, b1], { radius: 2, color: 'red' }).addTo(RectangleGroup); L.circleMarker([a2, b2], { radius: 2, color: 'red' }).addTo(RectangleGroup); L.circleMarker([c1, d1], { radius: 2, color: 'red' }).addTo(RectangleGroup); L.circleMarker([c2, d2], { radius: 2, color: 'red' }).addTo(RectangleGroup); } coords.forEach(function (feature) { L.circleMarker([feature[1], feature[0]], { radius: 2, color: 'red' }).addTo(RectangleGroup); }); }, dellEntranse: (id) => { delete Constructor.info.apartments[id]; const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(id); console.log(pos); Constructor.info.entrance.splice(pos, 1); console.log(id); document.getElementById(`Entranse_${id}`).remove(); let Entranse = Object.keys(entransePolygonsGroup._layers); let numsEntranse = Object.keys(entranseNumPolygonsGroup._layers); console.log(Entranse, Entranse[pos]); console.log(numsEntranse, numsEntranse[pos]); entransePolygonsGroup.removeLayer(entransePolygonsGroup._layers[Entranse[pos]]); entranseNumPolygonsGroup.removeLayer(entranseNumPolygonsGroup._layers[numsEntranse[pos]]); Constructor.apartments.init(); }, editEntranse: (id) => { const input = document.getElementById(`Entranse_input_${id}`); const pos = Constructor.info.entrance.map(e => e.editor_id).indexOf(id); Constructor.info.entrance[pos].entrance_number = Number(input.value) - 1; Constructor.info.entrance[pos].title = `Під'їзд ${input.value}`; let numsEntranse = Object.keys(entranseNumPolygonsGroup._layers); let newIcon = L.divIcon({ className: 'entranse_number', html: `
${input.value}
` }); entranseNumPolygonsGroup._layers[numsEntranse[pos]].setIcon(newIcon); input.setAttribute("value", input.value); Constructor.apartments.init(); } } }