let map, poleLayer, connectionLayer, tileLayer;
let poles = [];
let poleCounter = 1;
let currentPoleType = "1";
let currentPoleStatus = "projected";
let currentExistingType = "communication";
let history = [];
let historyIndex = -1;
const POLE_TYPES = {
"1": { name: "Промежуточная", color: "#2563eb", icon: "●" },
"2": { name: "Угловая", color: "#dc2626", icon: "◆" },
"3": { name: "С муфтой", color: "#ca8a04", icon: "◎" },
"4": { name: "С ОРКсп", color: "#16a34a", icon: "◇" },
"5": { name: "С муфтой и ОРКсп", color: "#7c3aed", icon: "★" },
};
const TILE_PROVIDERS = {
street: { name: "Схема", url: "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", attr: "© CARTO" },
satellite: { name: "Спутник", url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", attr: "© Esri" },
};
function initApp() {
initMap();
initSelectors();
initPoleControls();
updateTable();
updateUndoButtons();
}
function initMap() {
map = L.map("map", { zoomControl: true }).setView([48.0, 68.0], 6);
tileLayer = L.tileLayer(TILE_PROVIDERS.street.url, { attribution: TILE_PROVIDERS.street.attr, maxZoom: 19 }).addTo(map);
poleLayer = L.featureGroup().addTo(map);
connectionLayer = L.featureGroup().addTo(map);
map.on("click", function (e) {
saveHistory();
addPole(e.latlng.lat, e.latlng.lng);
});
document.querySelectorAll(".map-mode-btn").forEach((btn) => {
btn.addEventListener("click", function () {
document.querySelectorAll(".map-mode-btn").forEach((b) => b.classList.remove("active"));
this.classList.add("active");
const mode = this.dataset.mode;
const provider = TILE_PROVIDERS[mode];
tileLayer.setUrl(provider.url);
tileLayer.setAttribution(provider.attr);
});
});
}
function saveHistory() {
const snapshot = poles.map((p) => ({ ...p }));
if (historyIndex < history.length - 1) {
history = history.slice(0, historyIndex + 1);
}
history.push(snapshot);
historyIndex = history.length - 1;
}
function undo() {
if (historyIndex < 0) return;
historyIndex--;
restoreHistory();
}
function redo() {
if (historyIndex >= history.length - 1) return;
historyIndex++;
restoreHistory();
}
function restoreHistory() {
if (historyIndex < 0 || historyIndex >= history.length) {
poles = [];
} else {
poles = history[historyIndex].map((p) => ({ ...p }));
}
poleCounter = poles.length > 0 ? Math.max(...poles.map((p) => p.id)) + 1 : 1;
poleLayer.clearLayers();
connectionLayer.clearLayers();
poles.forEach((p) => renderPole(p));
updateTable();
updateConnections();
updateUndoButtons();
}
function updateUndoButtons() {
document.getElementById("undoBtn").style.opacity = historyIndex >= 0 ? "1" : "0.3";
document.getElementById("redoBtn").style.opacity = historyIndex < history.length - 1 ? "1" : "0.3";
}
function initSelectors() {
const oblastSel = document.getElementById("oblast");
const rayonSel = document.getElementById("rayon");
const villageSel = document.getElementById("village");
const searchInput = document.getElementById("searchInput");
const manualVillage = document.getElementById("manualVillage");
KZ_DATA.forEach((o) => {
const opt = document.createElement("option");
opt.value = o.oblast;
opt.textContent = o.oblast;
oblastSel.appendChild(opt);
});
function updateRayons() {
rayonSel.innerHTML = '';
villageSel.innerHTML = '';
const oblast = KZ_DATA.find((o) => o.oblast === oblastSel.value);
if (!oblast) return;
oblast.rayons.forEach((r) => {
const opt = document.createElement("option");
opt.value = r.rayon;
opt.textContent = `${r.rayon} (${r.villages.length} сёл)`;
rayonSel.appendChild(opt);
});
}
function updateVillages() {
villageSel.innerHTML = '';
const oblast = KZ_DATA.find((o) => o.oblast === oblastSel.value);
if (!oblast) return;
const rayon = oblast.rayons.find((r) => r.rayon === rayonSel.value);
if (!rayon) return;
rayon.villages.forEach((v) => {
const opt = document.createElement("option");
const vname = typeof v === "string" ? v : v.name;
opt.value = vname;
opt.textContent = vname;
villageSel.appendChild(opt);
});
}
oblastSel.addEventListener("change", function() {
updateRayons();
const oblast = KZ_DATA.find((o) => o.oblast === this.value);
if (oblast) map.setView(oblast.center, 8);
});
rayonSel.addEventListener("change", function() {
updateVillages();
const oblast = KZ_DATA.find((o) => o.oblast === oblastSel.value);
if (!oblast) return;
const rayon = oblast.rayons.find((r) => r.rayon === this.value);
const center = (rayon && rayon.center) ? rayon.center : (oblast ? oblast.center : null);
if (center) map.setView(center, 10);
});
function getVillageCenter(v) {
if (typeof v === "object" && v.lat && v.lng) return [v.lat, v.lng];
return null;
}
function findVillageData(name) {
const q = name.toLowerCase().trim();
for (const o of KZ_DATA) {
for (const r of o.rayons) {
for (const v of r.villages) {
const vname = typeof v === "string" ? v : v.name;
if (vname.toLowerCase() === q) {
const center = getVillageCenter(v) || r.center || o.center;
return { village: vname, rayon: r.rayon, oblast: o.oblast, center };
}
}
}
}
return null;
}
function goToVillage(name) {
if (!name) return;
const found = findVillageData(name);
if (found) {
oblastSel.value = found.oblast;
updateRayons();
rayonSel.value = found.rayon;
updateVillages();
villageSel.value = found.village;
map.setView(found.center, 13);
L.popup()
.setLatLng(found.center)
.setContent(`${found.village}
${found.oblast}, ${found.rayon}`)
.openOn(map);
return;
}
const oblast = KZ_DATA.find((o) => o.oblast === oblastSel.value);
if (!oblast) return;
const rayon = oblast.rayons.find((r) => r.rayon === rayonSel.value);
const center = (rayon && rayon.center) ? rayon.center : oblast.center;
map.setView(center, 12);
L.popup()
.setLatLng(center)
.setContent(`${name}
${oblast.oblast}, ${rayon ? rayon.rayon : ''}`)
.openOn(map);
}
villageSel.addEventListener("change", function () {
const name = this.value;
if (!name) return;
const oblast = KZ_DATA.find((o) => o.oblast === oblastSel.value);
if (!oblast) return;
const rayon = oblast.rayons.find((r) => r.rayon === rayonSel.value);
if (!rayon) return;
const village = rayon.villages.find((v) => (typeof v === "string" ? v : v.name) === name);
const center = getVillageCenter(village) || (rayon ? rayon.center : null) || oblast.center;
if (center) {
map.setView(center, 14);
L.popup().setLatLng(center).setContent(`${name}
${oblast.oblast}, ${rayon.rayon}`).openOn(map);
}
});
manualVillage.addEventListener("keydown", function (e) {
if (e.key === "Enter" && this.value.trim()) {
goToVillage(this.value.trim());
}
});
manualVillage.addEventListener("blur", function () {
if (this.value.trim()) {
goToVillage(this.value.trim());
}
});
searchInput.addEventListener("input", function () {
const q = this.value.toLowerCase().trim();
const list = document.getElementById("searchResults");
list.innerHTML = "";
if (q.length < 2) return;
const results = [];
KZ_DATA.forEach((o) => {
o.rayons.forEach((r) => {
r.villages.forEach((v) => {
const vname = typeof v === "string" ? v : v.name;
if (vname.toLowerCase().includes(q)) {
const center = getVillageCenter(v) || r.center || o.center;
results.push({ village: vname, rayon: r.rayon, oblast: o.oblast, center });
}
});
});
});
if (results.length === 0) {
list.innerHTML = "