Переработаны роутеры приложения

Переписано APi WebSocket для работы с новыми роутерами
This commit is contained in:
2025-10-03 17:11:31 +03:00
parent d75fb7ec3d
commit 6ec6523d71
54 changed files with 2593 additions and 3749 deletions

View File

@@ -0,0 +1,909 @@
let map, houseGroup, homesteadGroup, buildingGroup, pointsGroup;
let numApartments = 1;
let mode = '';
const Territory_constructor = {
info: {},
async init() {
let html = await fetch('/lib/pages/territory/constructor/index.html').then((response) => response.text());
app.innerHTML = html;
map = "";
houseGroup = "";
homesteadGroup = "";
buildingGroup = "";
pointsGroup = "";
numApartments = 1;
this.info = {
type: 'house',
points: [],
points_number: [],
geo: {},
osm_id: [],
zoom: 17,
title: null,
number: null,
settlement: null
}
const infoTypeInputs = document.querySelectorAll('input[name="info-type"]');
const infoForm = document.getElementById('info-form');
const infoLabels = {
points: {
title: "Назва",
number: "Номер",
settlement: "Місто",
init: () => Territory_constructor.points.init(),
next: () => Territory_constructor.points.next(),
save: () => Territory_constructor.points.save()
},
homestead: {
title: "Назва району / села",
number: "Номер району",
settlement: "Місто",
init: () => Territory_constructor.homestead.init(),
next: () => Territory_constructor.homestead.next(),
save: () => Territory_constructor.homestead.save()
},
house: {
title: "Назва вулиці",
number: "Номер будинку",
settlement: "Місто",
init: () => Territory_constructor.house.init(),
next: () => Territory_constructor.house.next(),
save: () => Territory_constructor.house.save()
}
};
let currentInit = infoLabels['house'].init;
let currentNext = infoLabels['house'].next;
let currentSave = infoLabels['house'].save;
function renderInfoForm(type) {
const labels = infoLabels[type];
currentInit = labels.init;
currentNext = labels.next;
currentSave = labels.save;
infoForm.innerHTML = `
<div>
<label for="info-title">${labels.title}</label>
<input type="text" id="info-title" name="address" required value=""/>
</div>
<div>
<label for="info-number">${labels.number}</label>
<input type="text" id="info-number" name="number" required value=""/>
</div>
<div>
<label for="info-settlementv">${labels.settlement}</label>
<input type="text" id="info-settlement" name="settlement" required value="Тернопіль"/>
</div>
<button type="submit" id="part-1-button">Далі</button>
`;
}
// Обработчик submit формы
infoForm.addEventListener('submit', (event) => {
event.preventDefault();
document.getElementById('part-1-button').style.display = "none";
['title', 'number', 'settlement'].forEach(key => {
Territory_constructor.info[key] = document.getElementById(`info-${key}`).value;
});
if (currentInit) currentInit();
});
// Слушатели радиокнопок
infoTypeInputs.forEach(radio => {
radio.addEventListener('change', event => {
const value = event.target.value;
const part_2 = document.getElementById('part-2');
const part_2_button = document.getElementById('part-2-button');
part_2.style.display = "none";
part_2_button.style.display = "";
const part_3 = document.getElementById('part-3');
part_3.style.display = "none";
renderInfoForm(value);
console.log(`Вибрано: ${value}`);
this.info.type = value;
this.info.osm_id = null;
this.info.geo = {};
this.info.points_number = [];
this.info.points = [];
this.house.apartments.quantity = 0;
this.house.apartments.list = [];
});
});
document.getElementById('part-2-button').addEventListener('click', (event) => {
event.preventDefault();
document.getElementById('part-2-button').style.display = "none";
if (currentNext) currentNext();
});
document.getElementById('part-3-button').addEventListener('click', (event) => {
event.preventDefault();
// document.getElementById('part-3-button').style.display = "none";
if (currentSave) currentSave();
});
},
points: {
init() {
console.log('points');
// const part_2 = document.getElementById('part-2');
// const part_2_title = document.getElementById('part-2-title');
// part_2_title.innerHTML = `<span>Крок 2.</span> Створення точок на карті`;
// part_2.style.display = "";
},
next() {
console.log('points next');
},
save() {
console.log('points next save');
}
},
homestead: {
init() {
console.log('homestead');
const part_2 = document.getElementById('part-2');
const part_2_title = document.getElementById('part-2-title');
part_2_title.innerHTML = `<span>Крок 2.</span> Створення ділянки`;
part_2.style.display = "";
Territory_constructor.osm.init();
},
next() {
console.log('homestead next');
const part_3 = document.getElementById('part-3');
const title = part_3.querySelector('h1');
const button = part_3.querySelector('#part-3-button');
part_3.innerHTML = '';
title.innerHTML = `<span>Крок 3.</span> Створення будинків`;
part_3.appendChild(title);
part_3.innerHTML += `
<div class="info">
<p>*Натисніть кнопку нижче, а потім клацайте на карті, щоб додати будинки. Після цього натисніть "Зберегти".</p>
<p>*Щоб видалити будинок, клацніть на ньому та у спливаючому вікні оберіть "Видалити".</p>
<br />
<button onclick="Territory_constructor.homestead.building.newHouse(this)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M 22.828125 3 C 22.316375 3 21.804562 3.1954375 21.414062 3.5859375 L 19 6 L 24 11 L 26.414062 8.5859375 C 27.195062 7.8049375 27.195062 6.5388125 26.414062 5.7578125 L 24.242188 3.5859375 C 23.851688 3.1954375 23.339875 3 22.828125 3 z M 17 8 L 5.2597656 19.740234 C 5.2597656 19.740234 6.1775313 19.658 6.5195312 20 C 6.8615312 20.342 6.58 22.58 7 23 C 7.42 23.42 9.6438906 23.124359 9.9628906 23.443359 C 10.281891 23.762359 10.259766 24.740234 10.259766 24.740234 L 22 13 L 17 8 z M 4 23 L 3.0566406 25.671875 A 1 1 0 0 0 3 26 A 1 1 0 0 0 4 27 A 1 1 0 0 0 4.328125 26.943359 A 1 1 0 0 0 4.3378906 26.939453 L 4.3632812 26.931641 A 1 1 0 0 0 4.3691406 26.927734 L 7 26 L 5.5 24.5 L 4 23 z"></path></svg>
<span>Додати будинок</span>
</button>
</div>
`;
part_3.appendChild(button);
part_3.style.display = "";
this.building.init();
},
async save() {
Territory_constructor.info.buildings = Territory_constructor.homestead.building.list;
console.log(Territory_constructor.info);
Territory_constructor.save();
},
building: {
list: [], editing: false,
async init() {
this.editing = false;
setLeafletCursor('pointer');
// Обробник кліку на карту
homesteadGroup.on('click', e => {
console.log(this.editing);
if (e.layer instanceof L.Marker || !this.editing) return;
const { lat, lng } = e.latlng;
console.log(`Координати: ${lat.toFixed(5)}, ${lng.toFixed(5)}`);
setLeafletCursor('progress');
this.editing = false;
this.addBuilding({ geo: e.latlng, title: this.list.length + 1 });
});
},
async addBuilding({ geo, title }) {
this.list.push({
title: title,
geo: geo
});
// Додаємо маркер на карту
const redDot = L.divIcon({
className: "leaflet_drop",
html: `<div id="redDot_${this.list.length}"></div>`,
iconSize: [16, 16],
iconAnchor: [8, 8]
});
const marker = L.marker(geo, { icon: redDot }).addTo(buildingGroup);
marker.bindPopup(`
Будинок: ${this.list.length}<br>
Координати: ${geo.lat.toFixed(5)}, ${geo.lng.toFixed(5)}<br>
<button class="map_dell" onclick="Territory_constructor.homestead.building.delleteBuilding({id: ${this.list.length}})" type="button">Видалити</button>
`);
setLeafletCursor('crosshair');
this.editing = true;
},
async delleteBuilding({ id }) {
const el = document.getElementById(`redDot_${id}`);
if (el) el.remove();
this.list = this.list.filter(item => item.title !== id);
const block = document.getElementById(`Building_${id}`);
if (block) block.remove();
houseGroup.eachLayer(layer => {
if (layer instanceof L.Marker && layer.getPopup()?.getContent().includes(`Будинок: ${id}`)) {
houseGroup.removeLayer(layer);
}
});
},
newHouse(element) {
const btn = element;
this.editing = !this.editing;
setLeafletCursor(this.editing ? 'crosshair' : 'pointer');
btn.innerHTML = this.editing
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"> <path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z" ></path> </svg><span>Завершити додавання</span>`
: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"> <path d="M 22.828125 3 C 22.316375 3 21.804562 3.1954375 21.414062 3.5859375 L 19 6 L 24 11 L 26.414062 8.5859375 C 27.195062 7.8049375 27.195062 6.5388125 26.414062 5.7578125 L 24.242188 3.5859375 C 23.851688 3.1954375 23.339875 3 22.828125 3 z M 17 8 L 5.2597656 19.740234 C 5.2597656 19.740234 6.1775313 19.658 6.5195312 20 C 6.8615312 20.342 6.58 22.58 7 23 C 7.42 23.42 9.6438906 23.124359 9.9628906 23.443359 C 10.281891 23.762359 10.259766 24.740234 10.259766 24.740234 L 22 13 L 17 8 z M 4 23 L 3.0566406 25.671875 A 1 1 0 0 0 3 26 A 1 1 0 0 0 4 27 A 1 1 0 0 0 4.328125 26.943359 A 1 1 0 0 0 4.3378906 26.939453 L 4.3632812 26.931641 A 1 1 0 0 0 4.3691406 26.927734 L 7 26 L 5.5 24.5 L 4 23 z" ></path> </svg><span>Додати будинок</span>`;
if (this.editing) alert("Натискаючи на карту будуть створюватись нові точки (будинки)");
}
}
},
house: {
init() {
console.log('house');
const part_2 = document.getElementById('part-2');
const part_2_title = document.getElementById('part-2-title');
part_2_title.innerHTML = `<span>Крок 2.</span> Конструктор будинків`;
part_2.style.display = "";
Territory_constructor.osm.init();
},
next() {
console.log('house next');
const part_3 = document.getElementById('part-3');
const title = part_3.querySelector('h1');
const button = part_3.querySelector('#part-3-button');
part_3.innerHTML = '';
title.innerHTML = `<span>Крок 3.</span> Конструктор квартир`;
part_3.appendChild(title);
part_3.innerHTML += `<input onchange="Territory_constructor.house.apartments.editQuantity(this.value)" type="number" value="1" id="next-apartment-title" title="Авто-номер наступної квартири">`
part_3.appendChild(button);
part_3.style.display = "";
this.apartments.init();
},
async save() {
console.log('house next save');
Territory_constructor.info.entrances = this.apartments.list.map((entrance, entranceIndex) => {
let apartments = [];
let apartmentCounter = 0;
entrance.list.forEach((floor, floorIndex) => {
floor.forEach(apartment => {
apartments.push({
title: apartment.title,
apartment_number: apartmentCounter++,
floors_number: floorIndex + 1
});
});
});
return {
title: entrance.title,
entrance_number: entranceIndex,
apartments
};
});
Territory_constructor.save();
},
apartments: {
quantity: 0,
list: [],
init() {
const part_3 = document.getElementById("part-3");
const part_3_Button = part_3.querySelector("#part-3-button");
this.quantity++;
const newEntrance = {
title: `Під'їзд ${this.list.length + 1}`,
list: [[{ title: this.quantity }]]
};
this.list.push(newEntrance);
const eIndex = this.list.length - 1;
const floorIndex = 0;
const apartmentIndex = 0;
const houseDiv = this.createHouse(eIndex);
const entranceDiv = this.createEntrance(eIndex);
const floorDiv = this.createFloor(eIndex, floorIndex);
const apartmentDiv = this.createApartment(eIndex, floorIndex, apartmentIndex, this.quantity);
floorDiv.insertBefore(apartmentDiv, floorDiv.querySelector(".floor-info"));
entranceDiv.appendChild(floorDiv);
houseDiv.insertBefore(entranceDiv, houseDiv.querySelector(".entrance-button"));
part_3.insertBefore(houseDiv, part_3_Button);
},
createApartment(entrance, floor, apartment, value) {
const div = document.createElement("div");
div.className = "apartment";
div.id = `apartment-${entrance}-${floor}-${apartment}`;
div.innerHTML = `
<input onchange="Territory_constructor.house.apartments.editApartment(${entrance}, ${floor}, ${apartment}, this.value)" type="text" value="${value}">
<button onclick="Territory_constructor.house.apartments.deleteApartment(${entrance}, ${floor}, ${apartment})" title="Видалити квартиру" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
`;
return div;
},
createFloor(entrance, floor) {
const div = document.createElement("div");
div.className = "floor";
div.id = `floor-${entrance}-${floor}`;
div.innerHTML = `
<div class="floor-info">
<h2>Поверх ${floor + 1}</h2>
<button onclick="Territory_constructor.house.apartments.addApartment(${entrance}, ${floor})" title="Додати квартиру" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>`;
return div;
},
createEntrance(entrance) {
const div = document.createElement("div");
div.className = "entrance";
div.id = `entrance-${entrance}`;
div.innerHTML = `
<div class="entrance-info">
<input onchange="Territory_constructor.house.apartments.editEntrance(${entrance}, this.value)" type="text" value="Під'їзд ${entrance + 1}">
<button onclick="Territory_constructor.house.apartments.addFloors(${entrance})" title="Додати поверх" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>
</div>`;
return div;
},
createHouse() {
const div = document.createElement("div");
div.id = `house`;
div.innerHTML = `
<button class="entrance-button" onclick="Territory_constructor.house.apartments.addEntrance()" title="Додати під'їзд" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"> <path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>
</button>`;
return div;
},
addEntrance() {
const blockHouse = document.getElementById("house");
const houseButton = blockHouse.querySelector(".entrance-button");
this.editQuantity(this.quantity + 1);
const newEntrance = {
title: `Під'їзд ${this.list.length + 1}`,
list: [[{ title: this.quantity }]]
};
this.list.push(newEntrance);
const eIndex = this.list.length - 1;
const floorIndex = 0;
const apartmentIndex = 0;
const entranceDiv = this.createEntrance(eIndex);
const floorDiv = this.createFloor(eIndex, floorIndex);
const apartmentDiv = this.createApartment(eIndex, floorIndex, apartmentIndex, this.quantity);
floorDiv.insertBefore(apartmentDiv, floorDiv.querySelector(".floor-info"));
entranceDiv.appendChild(floorDiv);
blockHouse.insertBefore(entranceDiv, houseButton);
},
editEntrance(entrance, value) {
this.list[entrance].title = value;
},
addFloors(entrance) {
const entranceBlock = document.getElementById(`entrance-${entrance}`);
const entranceInfo = entranceBlock.querySelector(".entrance-info");
this.editQuantity(this.quantity + 1);
this.list[entrance].list.push([{ title: this.quantity }]);
const fIndex = this.list[entrance].list.length - 1;
const floorDiv = this.createFloor(entrance, fIndex);
const aptDiv = this.createApartment(entrance, fIndex, 0, this.quantity);
floorDiv.insertBefore(aptDiv, floorDiv.querySelector(".floor-info"));
entranceInfo.after(floorDiv);
},
addApartment(entrance, floor) {
const blockFloor = document.getElementById(`floor-${entrance}-${floor}`);
const floorInfo = blockFloor.querySelector(".floor-info");
this.editQuantity(this.quantity + 1);
this.list[entrance].list[floor].push({ title: this.quantity });
const aIndex = this.list[entrance].list[floor].length - 1;
const aptDiv = this.createApartment(entrance, floor, aIndex, this.quantity);
blockFloor.insertBefore(aptDiv, floorInfo);
},
editApartment(entrance, floor, apartment, value) {
this.list[entrance].list[floor][apartment].title = value;
},
deleteApartment(entrance, floor, apartment) {
this.list[entrance].list[floor].splice(apartment, 1);
document.getElementById(`apartment-${entrance}-${floor}-${apartment}`)?.remove();
this.editQuantity(this.quantity - 1);
},
editQuantity(value) {
const next_apartment_title = document.getElementById('next-apartment-title');
next_apartment_title.style.display = "";
next_apartment_title.value = value;
this.quantity = Number(value);
}
}
},
osm: {
init() {
const center = { lat: 49.5629016, lng: 25.6145625 };
const zoom = 19;
const googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
});
const osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
const mytile = L.tileLayer('https://sheep-service.com/map/{z}/{x}/{y}.webp', {
maxZoom: 20, minZoom: 15, tms: true
});
if (!map) {
houseGroup = new L.FeatureGroup();
homesteadGroup = new L.FeatureGroup();
buildingGroup = new L.FeatureGroup();
pointsGroup = new L.FeatureGroup();
map = L.map('map', {
renderer: L.canvas(), center, zoom,
layers: [googleHybrid, osm, mytile, houseGroup, homesteadGroup, buildingGroup, pointsGroup],
zoomControl: false
});
L.control.layers(
{ "Google Hybrid": googleHybrid, "OpenStreetMap": osm, "Territory Map": mytile },
{
"Багатоповерхові будинки": houseGroup,
"Житлові райони": homesteadGroup,
"Приватні будинки": buildingGroup,
"Точки на карті": pointsGroup
},
{ position: 'bottomright' }
).addTo(map);
map.pm.addControls({
position: 'bottomright',
drawCircleMarker: false,
drawPolyline: false,
drawPolygon: false,
drawRectangle: false,
drawCircle: false,
drawText: false,
drawMarker: false,
cutPolygon: false,
tooltips: false,
editMode: true,
dragMode: true,
});
map.pm.toggleControls()
// Событие после завершения рисования
map.on('pm:create', e => {
const layer = e.layer;
let LatLngs = layer.getLatLngs();
LatLngs[0].push(LatLngs[0][0]);
Territory_constructor.info.points.push(LatLngs);
Territory_constructor.info.points_number.push(this.center(layer.getLatLngs()));
let geo = this.center(layer.getLatLngs());
const house = layer; // сохраняем именно слой
if (Territory_constructor.info.type === 'house') {
houseGroup.addLayer(house);
} else if (Territory_constructor.info.type === 'homestead') {
homesteadGroup.addLayer(house);
}
house.bindPopup(`
Координати: ${geo.lat.toFixed(5)}, ${geo.lng.toFixed(5)}<br>
<button class="map_dell" type="button">Видалити</button>
`);
// при открытии popup вешаем обработчик удаления
house.on('popupopen', (e) => {
if (Territory_constructor.homestead.building.editing) {
house.closePopup();
return;
}
const btn = e.popup.getElement().querySelector('.map_dell');
if (btn) {
btn.addEventListener('click', () => {
Territory_constructor.osm.delete(house);
});
}
});
Territory_constructor.osm.autoZoom(Territory_constructor.info.points);
});
map.pm.setLang("ua");
}
houseGroup.clearLayers();
homesteadGroup.clearLayers();
buildingGroup.clearLayers();
pointsGroup.clearLayers();
},
newPoligon() {
if (Territory_constructor.info.type === 'house') {
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
layerGroup: houseGroup,
templineStyle: {
color: '#585858',
radius: 500,
fillOpacity: 0.4,
dashArray: '5, 10',
dashOffset: '20',
},
hintlineStyle: {
color: '#C14D4D',
dashArray: '5, 10'
},
pathOptions: {
color: "#585858",
fillColor: "#f2bd53",
fillOpacity: 0.8
}
});
} else if (Territory_constructor.info.type === 'homestead') {
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
layerGroup: houseGroup,
templineStyle: {
color: '#585858',
radius: 500,
fillOpacity: 0.3,
dashArray: '5, 10',
dashOffset: '20',
},
hintlineStyle: {
color: '#C14D4D',
dashArray: '5, 10'
},
pathOptions: {
color: "#f2bd53",
fillColor: "#f2bd53",
radius: 500,
fillOpacity: 0.3,
dashArray: '5, 10'
}
});
}
},
async autoPoligon(IDs) {
if (!IDs) return;
const ids_list = IDs.replace(/\s+/g, "").split(',');
Territory_constructor.info.osm_id = ids_list;
houseGroup.clearLayers();
homesteadGroup.clearLayers();
Territory_constructor.info.points = [];
Territory_constructor.info.points_number = [];
Territory_constructor.info.geo = {}
// 1006306041, 1006306065
for (let i = 0; i < ids_list.length; i++) {
const element = await Territory_constructor.osm.getOSM(Territory_constructor.info.osm_id[i]);
// Преобразуем координаты в LatLng
const LatLngs = [[]];
element[0].forEach(feature => LatLngs[0].push({ lat: feature.lat, lng: feature.lng }));
// Замыкаем полигон
if (LatLngs[0][0] && LatLngs[0][0] !== LatLngs[0][LatLngs[0].length - 1]) {
LatLngs[0].push(LatLngs[0][0]);
}
// Считаем центр
const center = this.center(LatLngs);
// Сохраняем в points / points_number
Territory_constructor.info.points.push(LatLngs);
Territory_constructor.info.points_number.push(center);
// Создаем L.polygon
const polyOptions = Territory_constructor.info.type === 'homestead'
? { color: "#f2bd53", fillColor: "#f2bd53", fillOpacity: 0.4, dashArray: '5,10' }
: { color: "#585858", fillColor: "#f2bd53", fillOpacity: 0.8 };
const house = L.polygon(LatLngs, polyOptions);
// Добавляем в нужную группу
if (Territory_constructor.info.type === 'house') {
houseGroup.addLayer(house);
} else if (Territory_constructor.info.type === 'homestead') {
homesteadGroup.addLayer(house);
}
// Bind popup с кнопкой удаления
house.bindPopup(`
Координати: ${center.lat.toFixed(5)}, ${center.lng.toFixed(5)}<br>
<button class="map_dell" type="button">Видалити</button>
`);
house.on('popupopen', (e) => {
if (Territory_constructor.homestead.building.editing) {
house.closePopup();
return;
}
const btn = e.popup.getElement().querySelector('.map_dell');
if (btn) {
btn.addEventListener('click', () => {
Territory_constructor.osm.delete(house);
});
}
});
}
Territory_constructor.osm.autoZoom(Territory_constructor.info.points);
},
center(geo) {
// Получаем координаты полигона Leaflet
let latlngs = geo[0];
// Преобразуем в формат GeoJSON для Turf
const coordinates = latlngs.map(ll => [ll.lng, ll.lat]);
const polygonGeoJSON = {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [coordinates]
}
};
// Находим центроид
const centroid = turf.centroid(polygonGeoJSON);
latlngs = { lat: centroid.geometry.coordinates[1], lng: centroid.geometry.coordinates[0] }
return latlngs;
},
autoZoom(polygons) {
if (!polygons || !polygons.length) return;
const allBounds = [];
polygons.forEach(polygon => {
const ring = polygon[0];
if (!ring || ring.length < 3) return;
const coords = ring.map(p => [p.lng, p.lat]);
if (coords[0][0] !== coords[coords.length - 1][0] || coords[0][1] !== coords[coords.length - 1][1]) {
coords.push(coords[0]);
}
const polygonGeoJSON = turf.polygon([coords]);
const bbox = turf.bbox(polygonGeoJSON);
const bounds = L.latLngBounds(
[bbox[1], bbox[0]],
[bbox[3], bbox[2]]
);
allBounds.push(bounds);
});
if (!allBounds.length) return;
// Если один полигон, просто fitBounds на него
if (allBounds.length === 1) {
map.fitBounds(allBounds[0]);
} else {
// Несколько полигонов → объединяем bounds
let finalBounds = allBounds[0];
for (let i = 1; i < allBounds.length; i++) {
finalBounds = finalBounds.extend(allBounds[i]);
}
map.fitBounds(finalBounds);
}
if (map.getZoom() > 18) map.setZoom(18);
setTimeout(() => {
Territory_constructor.info.zoom = map.getZoom();
Territory_constructor.info.geo = map.getCenter();
}, 200)
},
delete(house) {
// убрать слой с карты
if (Editor.info.type === 'house') {
houseGroup.removeLayer(house);
} else if (Editor.info.type === 'homestead') {
homesteadGroup.removeLayer(house);
}
// найти индекс полигона в points
const target = house.getLatLngs()[0]; // вершины полигона
const index = Editor.info.points.findIndex((p) => {
const copy = p.slice(); // делаем копию
copy[0].pop(); // убираем последний элемент
if (isSamePolygon(p, target)) return true; // проверка как есть
if (isSamePolygon(copy, target)) return true; // проверка без последнего
return false;
});
function isSamePolygon(a, b) {
if (a.length !== b.length) return false;
return a.every((pt, i) => pt.lat === b[i].lat && pt.lng === b[i].lng);
}
if (index) {
// удалить из points и points_number по индексу
Editor.info.points.splice(index, 1);
Editor.info.points_number.splice(index, 1);
}
Editor.osm.autoZoom(Editor.info.points);
},
async getOSM(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));
return [coordinates];
} else {
console.error("Way не найден!");
}
})
.catch(error => console.error("Ошибка запроса:", error));
},
},
async save() {
const part_3_button = document.getElementById('part-3-button');
console.log(Territory_constructor.info);
setLeafletCursor('pointer');
Territory_constructor.homestead.building.editing = false;
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(Territory_constructor.info)
})
.then(response => {
if (response.status == 200) {
console.log({ 'setPack': 'ok' });
part_3_button.innerText = "Запис додано";
return response.json()
} else {
console.log('err');
part_3_button.innerText = "Помилка запису";
return
}
})
.then(data => {
console.log(data);
Territory.house.list = [];
Territory.homestead.list = [];
Router.navigate(`/territory/manager/${Territory_constructor.info.type}/${data.id}`);
setTimeout(() => {
part_3_button.innerText = "Зберегти";
}, 3000);
})
.catch(err => {
console.log(err);
part_3_button.innerText = "Помилка запису";
})
}
}