map-maker/index.html

1044 lines
31 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8" />
<title>Okupacja Moskwy ok. 1612 + Generator granic + Obrazy</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Leaflet CSS -->
<link rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<!-- Leaflet.draw CSS -->
<link rel="stylesheet"
href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css"/>
<style>
html, body {
height: 100%;
margin: 0;
background: #f2e3c6;
font-family: system-ui, sans-serif;
}
#map { height: 100%; width: 100%; }
.map-title {
position: absolute;
top: 10px; left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(42,24,0,0.9);
color: #f8f1df;
padding: 6px 14px;
border: 1px solid #b19671;
border-radius: 4px;
font-size: 14px;
text-transform: uppercase;
}
.legend {
position: absolute;
bottom: 10px; left: 10px;
z-index: 1000;
background: rgba(42,24,0,0.9);
padding: 8px 12px;
border-radius: 4px;
border: 1px solid #b19671;
font-size: 12px;
color:#f8f1df;
}
.legend-item { display: flex; align-items: center; margin-bottom: 4px; }
.legend-color {
width: 12px; height: 12px;
border-radius: 50%;
margin-right: 6px;
border: 1px solid #333;
}
.red { background: #b30000; }
.blue { background: #4ca0ff; }
.green { background: #2ecc71; }
.gray { background: #b3b3b3; }
.panel-label {
font-size: 11px;
display: inline-block;
margin-top: 4px;
}
.panel-row {
margin-top: 4px;
}
.panel-row input[type="range"] {
width: 60%;
}
.panel-row input[type="number"] {
width: 70px;
margin-left: 4px;
font-size: 11px;
}
.panel-separator {
margin: 6px 0;
border-top: 1px solid #b19671;
}
</style>
</head>
<body>
<div class="map-title">
RZECZPOSPOLITA & MOSKWA SYTUACJA OK. 1612 R.
</div>
<div id="map"></div>
<!-- PANEL KOORDYNATÓW -->
<div style="
position:absolute;
right:10px;
bottom:10px;
z-index:1000;
width:270px;
background:rgba(42,24,0,0.92);
color:#f8f1df;
border:1px solid #b19671;
border-radius:4px;
padding:6px 8px;
font-size:12px;">
<b>Rysowanie → koordynaty</b><br>
Kliknij ikonę ołówka i narysuj linię.<br><br>
<textarea id="coordsOutput"
style="width:100%;height:130px;font-size:10px;background:#1b1207;color:#ffeccc;border:1px solid #b19671;">
</textarea>
<div style="display:flex; gap:6px; margin-top:6px;">
<button id="reverseArrayBtn" style="flex:1; padding:6px; font-size:11px; cursor:pointer; background:#6c5a8a; color:#fff; border:1px solid #5a4a70; border-radius:3px;">
🔄 Odwróć
</button>
<button id="exportJsonBtn" style="flex:1; padding:6px; font-size:11px; cursor:pointer; background:#4a6c8a; color:#fff; border:1px solid #3a5a70; border-radius:3px;">
📥 Eksportuj
</button>
</div>
</div>
<!-- PANEL OBRAZÓW -->
<div style="
position:absolute;
right:10px;
top:70px;
z-index:1000;
width:290px;
background:rgba(42,24,0,0.92);
color:#f8f1df;
border:1px solid #b19671;
border-radius:4px;
padding:8px;
font-size:12px;">
<b>Dodaj obraz na mapę</b><br>
<input type="file" id="imgInput" accept="image/*"
style="width:100%; margin-top:6px; margin-bottom:6px;">
<label class="panel-label">Przezroczystość:</label>
<input type="range" id="imgOpacity" min="0" max="1" step="0.05" value="1" style="width:100%;">
<div class="panel-separator"></div>
<b style="font-size:11px;">Bounds obrazu (prostokąt):</b>
<div class="panel-row">
<span class="panel-label">⬉ Północno-zachodni (górny-lewy):</span><br>
<label style="font-size:10px;">Lat:</label>
<input type="number" id="boundsNorthLat" step="0.0001" style="width:80px; font-size:10px;">
<label style="font-size:10px; margin-left:4px;">Lng:</label>
<input type="number" id="boundsWestLng" step="0.0001" style="width:80px; font-size:10px;">
</div>
<div class="panel-row">
<span class="panel-label">⬊ Południowo-wschodni (dolny-prawy):</span><br>
<label style="font-size:10px;">Lat:</label>
<input type="number" id="boundsSouthLat" step="0.0001" style="width:80px; font-size:10px;">
<label style="font-size:10px; margin-left:4px;">Lng:</label>
<input type="number" id="boundsEastLng" step="0.0001" style="width:80px; font-size:10px;">
</div>
<div class="panel-row" style="margin-top:8px;">
<button id="applyBounds" style="width:100%; padding:6px; font-size:11px; cursor:pointer;">
Zastosuj koordynaty
</button>
</div>
<div class="panel-row" style="margin-top:6px;">
<button id="toggleDrag" style="width:100%; padding:8px; font-size:11px; cursor:pointer; background:#4a7c59; color:#fff; border:1px solid #3d6b4a; border-radius:3px;">
✋ Włącz przeciąganie
</button>
</div>
<div class="panel-row" style="margin-top:6px;">
<button id="toggleScale" style="width:100%; padding:8px; font-size:11px; cursor:pointer; background:#5a6c8a; color:#fff; border:1px solid #4a5a70; border-radius:3px;">
⤡ Włącz skalowanie
</button>
</div>
<div style="margin-top:8px; font-size:10px; color:#c9b896;">
<b>Skalowanie:</b> przeciągnij róg obrazka<br>
<b>Shift</b> krok 0.1 | <b>Ctrl</b> zachowaj proporcje
</div>
<div class="panel-separator"></div>
<b style="font-size:11px;">🔍 Skanowanie linii z obrazu:</b>
<div class="panel-row">
<span class="panel-label">Kolor do wykrycia:</span><br>
<input type="color" id="scanColor" value="#ff0000" style="width:50px; height:24px; border:1px solid #b19671; cursor:pointer;">
<button id="pickColorBtn" style="padding:4px 8px; font-size:10px; cursor:pointer; margin-left:4px;">
🎯 Pobierz z mapy
</button>
</div>
<div class="panel-row">
<span class="panel-label">Tolerancja koloru:</span><br>
<input type="range" id="colorTolerance" min="5" max="100" value="30" style="width:60%;">
<span id="toleranceVal" style="font-size:10px;">30</span>
</div>
<div class="panel-row">
<span class="panel-label">Uproszczenie linii:</span><br>
<input type="range" id="simplifyTolerance" min="1" max="20" value="5" style="width:60%;">
<span id="simplifyVal" style="font-size:10px;">5</span>
</div>
<div class="panel-row" style="margin-top:6px;">
<button id="scanImageBtn" style="width:100%; padding:8px; font-size:11px; cursor:pointer; background:#8a6c5a; color:#fff; border:1px solid #705a4a; border-radius:3px;">
🔍 Skanuj obraz
</button>
</div>
<div id="scanStatus" style="margin-top:6px; font-size:10px; color:#c9b896;"></div>
</div>
<!-- LEGENDA -->
<div class="legend">
<div class="legend-item"><span class="legend-color red"></span> Garnizony RON</div>
<div class="legend-item"><span class="legend-color blue"></span> Miasta uznające Władysława</div>
<div class="legend-item"><span class="legend-color green"></span> Miasta sprzeciwu</div>
<div class="legend-item"><span class="legend-color gray"></span> Miasta neutralne</div>
<hr style="border-color:#b19671;">
<div class="legend-item"><span class="legend-color" style="background:#8b0000;"></span> Granice RON</div>
<div class="legend-item"><span class="legend-color" style="background:#004600;"></span> Granice Carstwa Rosyjskiego</div>
</div>
<!-- JS: Leaflet, draw -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<!-- Polygony z zewnętrznych plików -->
<script src="polygons/countries/RON-1612.js"></script>
<script src="polygons/countries/Muscovy-1600.js"></script>
<!-- Skaner obrazów -->
<script src="js/image-scanner.js"></script>
<script>
// ===============================================
// MAPA
// ===============================================
const map = L.map('map');
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom:10 }).addTo(map);
// Utworzenie warstwy (pane) dla miast z wyższym z-index
map.createPane('citiesPane');
map.getPane('citiesPane').style.zIndex = 650;
// ===============================================
// MIASTA
// ===============================================
const allPoints = [];
function mark(city, border, fill) {
L.circleMarker([city.lat, city.lng], {
radius: 6, weight: 1,
color: border, fillColor: fill, fillOpacity: 0.85,
pane: 'citiesPane' // Użyj warstwy z wyższym z-index
}).addTo(map).bindPopup(`<b>${city.name}</b>`);
allPoints.push([city.lat, city.lng]);
}
const citiesOccupied = [
{ name:"Moskwa",lat:55.7522,lng:37.6156 },
{ name:"Smoleńsk",lat:54.7818,lng:32.0401 },
{ name:"Wiaźma",lat:55.2101,lng:34.2926 },
{ name:"Możajsk",lat:55.5044,lng:36.0246 },
{ name:"Kaługa",lat:54.5293,lng:36.2754 },
{ name:"Rosławl",lat:53.9531,lng:32.8638 },
{ name:"Wołokołamsk",lat:56.0363,lng:35.9573 },
{ name:"Kołomna",lat:55.0794,lng:38.7783 }
];
const citiesRecognizing = [
{ name:"Nowogród Wielki",lat:58.5213,lng:31.2710 },
{ name:"Psków",lat:57.8136,lng:28.3496 },
{ name:"Kostroma",lat:57.7665,lng:40.9269 },
{ name:"Rostów",lat:57.1956,lng:39.4132 },
{ name:"Suzdal",lat:56.4230,lng:40.4470 },
{ name:"Biełoziersk",lat:60.0299,lng:37.8057 },
{ name:"Ustiug",lat:60.7680,lng:46.3070 }
];
const citiesOpposing = [
{ name:"Niżny Nowogród",lat:56.2965,lng:43.9361 },
{ name:"Jarosław",lat:57.6299,lng:39.8737 },
{ name:"Riazań",lat:54.6292,lng:39.7367 },
{ name:"Twer",lat:56.8587,lng:35.9176 },
{ name:"Kazań",lat:55.7963,lng:49.1088 }
];
const citiesNeutral = [
{ name:"Wołogda",lat:59.2205,lng:39.8915 },
{ name:"Wiatka",lat:58.6035,lng:49.6679 },
{ name:"Kursk",lat:51.7308,lng:36.1926 },
{ name:"Briańsk",lat:53.2521,lng:34.3717 }
];
citiesOccupied.forEach(c => mark(c,"#4d0000","#b30000"));
citiesRecognizing.forEach(c => mark(c,"#003d80","#4ca0ff"));
citiesOpposing.forEach(c => mark(c,"#064017","#2ecc71"));
citiesNeutral.forEach(c => mark(c,"#4d4d4d","#b3b3b3"));
// ===============================================
// POLYGONY RON + ROSJA
// ===============================================
// polygonRON jest wczytywany z: polygons/countries/RON-1612.js
// polygonRussia jest wczytywany z: polygons/countries/Muscovy-1600.js
const ronLayer = L.geoJSON(polygonRON, {
style:{ color:"rgb(139,0,0)", weight:3, fillColor:"rgba(139,0,0,0.5)", fillOpacity:0.5 }
}).addTo(map);
const rusLayer = L.geoJSON(polygonRussia, {
style:{ color:"rgb(0,70,0)", weight:3, fillColor:"rgba(0,70,0,0.5)", fillOpacity:0.5 }
}).addTo(map);
// ===============================================
// RYSOWANIE LINII → KOORDYNATY
// ===============================================
const drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
const drawControl = new L.Control.Draw({
draw: {
polyline:{ shapeOptions:{ color:"#ffffff", weight:3 }},
polygon:false, rectangle:false, circle:false,
marker:false, circlemarker:false
},
edit: { featureGroup: drawnItems, remove:true }
});
map.addControl(drawControl);
// Funkcja do aktualizacji textarea z koordynatami
function updateCoordsOutput() {
const allCoords = [];
drawnItems.eachLayer(function(layer) {
if (layer.getLatLngs) {
const latlngs = layer.getLatLngs();
// Dla polyline getLatLngs() zwraca tablicę punktów
// Dla polygon może zwracać tablicę tablic
const flatLatLngs = Array.isArray(latlngs[0]) && latlngs[0].lat === undefined
? latlngs[0]
: latlngs;
const coords = flatLatLngs.map(p => [ Number(p.lng.toFixed(4)), Number(p.lat.toFixed(4)) ]);
allCoords.push(...coords);
}
});
document.getElementById("coordsOutput").value =
allCoords.length > 0 ? JSON.stringify(allCoords, null, 2) : '';
}
map.on(L.Draw.Event.CREATED, function(event){
const layer = event.layer;
drawnItems.addLayer(layer);
updateCoordsOutput();
});
// Aktualizuj przy edycji (po zapisaniu zmian)
map.on(L.Draw.Event.EDITED, function(event) {
updateCoordsOutput();
});
// Aktualizuj przy usunięciu
map.on(L.Draw.Event.DELETED, function(event) {
updateCoordsOutput();
});
// Aktualizuj podczas edycji w czasie rzeczywistym (onchange)
map.on('draw:editvertex', function(event) {
updateCoordsOutput();
});
// Eksport JSON
// Odwróć kolejność tablicy
document.getElementById("reverseArrayBtn").addEventListener("click", function() {
const content = document.getElementById("coordsOutput").value.trim();
if (!content) {
alert("Brak danych do odwrócenia!");
return;
}
try {
const arr = JSON.parse(content);
if (!Array.isArray(arr)) {
alert("Zawartość nie jest tablicą!");
return;
}
// Odwróć kolejność elementów
const reversed = arr.reverse();
document.getElementById("coordsOutput").value = JSON.stringify(reversed, null, 2);
} catch (e) {
alert("Nieprawidłowy format JSON!");
}
});
document.getElementById("exportJsonBtn").addEventListener("click", function() {
const content = document.getElementById("coordsOutput").value.trim();
if (!content) {
alert("Brak danych do eksportu! Narysuj najpierw linię.");
return;
}
// Walidacja JSON
try {
JSON.parse(content);
} catch (e) {
alert("Nieprawidłowy format JSON!");
return;
}
// Utwórz plik do pobrania
const blob = new Blob([content], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'coordinates_' + new Date().toISOString().slice(0,10) + '.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// ===============================================
// DODAWANIE OBRAZÓW + STEROWANIE
// ===============================================
let currentImageOverlay = null;
let currentImageURL = null;
// Aktualne bounds obrazu
const imgBounds = {
north: 0,
south: 0,
west: 0,
east: 0
};
// Aktualizacja inputów na podstawie stanu
function updateBoundsInputs() {
document.getElementById("boundsNorthLat").value = imgBounds.north.toFixed(4);
document.getElementById("boundsWestLng").value = imgBounds.west.toFixed(4);
document.getElementById("boundsSouthLat").value = imgBounds.south.toFixed(4);
document.getElementById("boundsEastLng").value = imgBounds.east.toFixed(4);
}
// Pobierz bounds z inputów i zastosuj do obrazu
function applyBoundsInputs() {
if (!currentImageOverlay) return;
imgBounds.north = parseFloat(document.getElementById("boundsNorthLat").value) || 0;
imgBounds.west = parseFloat(document.getElementById("boundsWestLng").value) || 0;
imgBounds.south = parseFloat(document.getElementById("boundsSouthLat").value) || 0;
imgBounds.east = parseFloat(document.getElementById("boundsEastLng").value) || 0;
const bounds = L.latLngBounds(
[imgBounds.south, imgBounds.west], // SW corner
[imgBounds.north, imgBounds.east] // NE corner
);
currentImageOverlay.setBounds(bounds);
}
// Obsługa przycisku "Zastosuj koordynaty"
document.getElementById("applyBounds").addEventListener("click", applyBoundsInputs);
document.getElementById("imgInput").addEventListener("change", function(e){
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(evt){
currentImageURL = evt.target.result;
// Pobierz aktualne centrum widoku użytkownika
const center = map.getCenter();
const img = new Image();
img.onload = function() {
const imgWidth = img.width || 1000;
const imgHeight = img.height || 1000;
const aspectRatio = imgHeight / imgWidth;
// Pobierz rozmiar widoku w pikselach
const mapSize = map.getSize();
// Domyślna wielkość obrazka: 1/2 szerokości widoku, wysokość proporcjonalna
const targetWidthPx = mapSize.x / 2;
const targetHeightPx = targetWidthPx * aspectRatio;
// Przelicz centrum widoku na punkt pikselowy
const centerPoint = map.latLngToContainerPoint(center);
// Oblicz punkty rogów w pikselach (zachowując proporcje obrazka)
const halfW = targetWidthPx / 2;
const halfH = targetHeightPx / 2;
// NW (top-left) i SE (bottom-right)
const nwPx = L.point(centerPoint.x - halfW, centerPoint.y - halfH);
const sePx = L.point(centerPoint.x + halfW, centerPoint.y + halfH);
// Przelicz na koordynaty geograficzne
const nwLatLng = map.containerPointToLatLng(nwPx);
const seLatLng = map.containerPointToLatLng(sePx);
// Ustaw bounds
imgBounds.north = nwLatLng.lat;
imgBounds.west = nwLatLng.lng;
imgBounds.south = seLatLng.lat;
imgBounds.east = seLatLng.lng;
// Zaktualizuj inputy
updateBoundsInputs();
const bounds = L.latLngBounds(
[imgBounds.south, imgBounds.west],
[imgBounds.north, imgBounds.east]
);
// Usuń poprzedni overlay jeśli istnieje
if (currentImageOverlay) {
map.removeLayer(currentImageOverlay);
}
// Utwórz standardowy imageOverlay
currentImageOverlay = L.imageOverlay(currentImageURL, bounds, {
opacity: Number(document.getElementById("imgOpacity").value || 1),
interactive: true
}).addTo(map);
// Reset stanu przeciągania
dragModeActive = false;
updateDragButton();
};
img.src = currentImageURL;
};
reader.readAsDataURL(file);
});
// Przezroczystość
document.getElementById("imgOpacity").addEventListener("input", function(e){
if (currentImageOverlay) {
currentImageOverlay.setOpacity(Number(e.target.value));
}
});
// Tryb przeciągania
let dragModeActive = false;
let dragStartLatLng = null;
let dragStartBounds = null;
const toggleDragBtn = document.getElementById("toggleDrag");
function updateDragButton() {
if (dragModeActive) {
toggleDragBtn.textContent = "✋ Wyłącz przeciąganie";
toggleDragBtn.style.background = "#a64d4d";
toggleDragBtn.style.borderColor = "#8b3d3d";
} else {
toggleDragBtn.textContent = "✋ Włącz przeciąganie";
toggleDragBtn.style.background = "#4a7c59";
toggleDragBtn.style.borderColor = "#3d6b4a";
}
}
toggleDragBtn.addEventListener("click", function() {
if (!currentImageOverlay) {
alert("Najpierw wczytaj obraz!");
return;
}
dragModeActive = !dragModeActive;
updateDragButton();
// Wyłącz tryb skalowania jeśli włączamy przeciąganie
if (dragModeActive && scaleModeActive) {
scaleModeActive = false;
updateScaleButton();
}
if (dragModeActive) {
map.dragging.disable();
} else {
if (!scaleModeActive) {
map.dragging.enable();
}
}
});
// Obsługa przeciągania obrazka
map.on('mousedown', function(e) {
if (!dragModeActive || !currentImageOverlay) return;
const bounds = currentImageOverlay.getBounds();
if (bounds.contains(e.latlng)) {
dragStartLatLng = e.latlng;
dragStartBounds = {
north: imgBounds.north,
south: imgBounds.south,
west: imgBounds.west,
east: imgBounds.east
};
map.on('mousemove', onDragMove);
map.on('mouseup', onDragEnd);
}
});
function onDragMove(e) {
if (!dragStartLatLng || !dragStartBounds) return;
const deltaLat = e.latlng.lat - dragStartLatLng.lat;
const deltaLng = e.latlng.lng - dragStartLatLng.lng;
imgBounds.north = dragStartBounds.north + deltaLat;
imgBounds.south = dragStartBounds.south + deltaLat;
imgBounds.west = dragStartBounds.west + deltaLng;
imgBounds.east = dragStartBounds.east + deltaLng;
const newBounds = L.latLngBounds(
[imgBounds.south, imgBounds.west],
[imgBounds.north, imgBounds.east]
);
currentImageOverlay.setBounds(newBounds);
updateBoundsInputs();
}
function onDragEnd() {
dragStartLatLng = null;
dragStartBounds = null;
map.off('mousemove', onDragMove);
map.off('mouseup', onDragEnd);
}
// ===============================================
// Tryb skalowania
// ===============================================
let scaleModeActive = false;
let scaleCorner = null; // 'nw', 'ne', 'sw', 'se'
let scaleStartLatLng = null;
let scaleStartBounds = null;
let originalAspectRatio = 1;
const toggleScaleBtn = document.getElementById("toggleScale");
let cornerMarkers = [];
function updateScaleButton() {
if (scaleModeActive) {
toggleScaleBtn.textContent = "⤡ Wyłącz skalowanie";
toggleScaleBtn.style.background = "#8a5a6c";
toggleScaleBtn.style.borderColor = "#704a5a";
showCornerMarkers();
} else {
toggleScaleBtn.textContent = "⤡ Włącz skalowanie";
toggleScaleBtn.style.background = "#5a6c8a";
toggleScaleBtn.style.borderColor = "#4a5a70";
hideCornerMarkers();
}
}
// Pokaż markery rogów
function showCornerMarkers() {
hideCornerMarkers();
if (!currentImageOverlay) return;
const corners = [
{ lat: imgBounds.north, lng: imgBounds.west, label: 'NW' },
{ lat: imgBounds.north, lng: imgBounds.east, label: 'NE' },
{ lat: imgBounds.south, lng: imgBounds.west, label: 'SW' },
{ lat: imgBounds.south, lng: imgBounds.east, label: 'SE' }
];
corners.forEach(corner => {
const marker = L.circleMarker([corner.lat, corner.lng], {
radius: 8,
color: '#ff6b6b',
fillColor: '#fff',
fillOpacity: 1,
weight: 3
}).addTo(map);
cornerMarkers.push(marker);
});
}
// Ukryj markery rogów
function hideCornerMarkers() {
cornerMarkers.forEach(marker => map.removeLayer(marker));
cornerMarkers = [];
}
// Aktualizuj pozycje markerów
function updateCornerMarkers() {
if (!scaleModeActive || cornerMarkers.length !== 4) return;
const corners = [
[imgBounds.north, imgBounds.west],
[imgBounds.north, imgBounds.east],
[imgBounds.south, imgBounds.west],
[imgBounds.south, imgBounds.east]
];
corners.forEach((pos, i) => {
cornerMarkers[i].setLatLng(pos);
});
}
toggleScaleBtn.addEventListener("click", function() {
if (!currentImageOverlay) {
alert("Najpierw wczytaj obraz!");
return;
}
scaleModeActive = !scaleModeActive;
updateScaleButton();
// Wyłącz tryb przeciągania jeśli włączamy skalowanie
if (scaleModeActive && dragModeActive) {
dragModeActive = false;
updateDragButton();
}
if (scaleModeActive) {
map.dragging.disable();
// Oblicz oryginalny aspect ratio
const width = imgBounds.east - imgBounds.west;
const height = imgBounds.north - imgBounds.south;
originalAspectRatio = height / width;
} else {
if (!dragModeActive) {
map.dragging.enable();
}
}
});
// Sprawdź czy punkt jest blisko rogu (tolerancja w pikselach)
function getCornerAtPoint(latlng, tolerance) {
if (!currentImageOverlay) return null;
const corners = {
nw: L.latLng(imgBounds.north, imgBounds.west),
ne: L.latLng(imgBounds.north, imgBounds.east),
sw: L.latLng(imgBounds.south, imgBounds.west),
se: L.latLng(imgBounds.south, imgBounds.east)
};
const clickPoint = map.latLngToContainerPoint(latlng);
for (const [corner, cornerLatLng] of Object.entries(corners)) {
const cornerPoint = map.latLngToContainerPoint(cornerLatLng);
const distance = clickPoint.distanceTo(cornerPoint);
if (distance <= tolerance) {
return corner;
}
}
return null;
}
// Obsługa skalowania
map.on('mousedown', function(e) {
if (!scaleModeActive || !currentImageOverlay) return;
const corner = getCornerAtPoint(e.latlng, 20); // 20px tolerancja
if (corner) {
scaleCorner = corner;
scaleStartLatLng = e.latlng;
scaleStartBounds = {
north: imgBounds.north,
south: imgBounds.south,
west: imgBounds.west,
east: imgBounds.east
};
// Oblicz aspect ratio na starcie
const width = scaleStartBounds.east - scaleStartBounds.west;
const height = scaleStartBounds.north - scaleStartBounds.south;
originalAspectRatio = height / width;
map.on('mousemove', onScaleMove);
map.on('mouseup', onScaleEnd);
}
});
function onScaleMove(e) {
if (!scaleStartLatLng || !scaleStartBounds || !scaleCorner) return;
let newNorth = scaleStartBounds.north;
let newSouth = scaleStartBounds.south;
let newWest = scaleStartBounds.west;
let newEast = scaleStartBounds.east;
const shiftPressed = e.originalEvent.shiftKey;
const ctrlPressed = e.originalEvent.ctrlKey;
let deltaLat = e.latlng.lat - scaleStartLatLng.lat;
let deltaLng = e.latlng.lng - scaleStartLatLng.lng;
// Shift: zaokrąglij do 0.1
if (shiftPressed) {
deltaLat = Math.round(deltaLat * 10) / 10;
deltaLng = Math.round(deltaLng * 10) / 10;
}
// Modyfikuj odpowiedni róg
switch (scaleCorner) {
case 'nw':
newNorth = scaleStartBounds.north + deltaLat;
newWest = scaleStartBounds.west + deltaLng;
break;
case 'ne':
newNorth = scaleStartBounds.north + deltaLat;
newEast = scaleStartBounds.east + deltaLng;
break;
case 'sw':
newSouth = scaleStartBounds.south + deltaLat;
newWest = scaleStartBounds.west + deltaLng;
break;
case 'se':
newSouth = scaleStartBounds.south + deltaLat;
newEast = scaleStartBounds.east + deltaLng;
break;
}
// Ctrl: zachowaj proporcje
if (ctrlPressed) {
const newWidth = newEast - newWest;
const newHeight = newNorth - newSouth;
const currentRatio = newHeight / newWidth;
if (Math.abs(currentRatio - originalAspectRatio) > 0.001) {
// Dopasuj wysokość do szerokości
const targetHeight = newWidth * originalAspectRatio;
const heightDiff = targetHeight - newHeight;
switch (scaleCorner) {
case 'nw':
case 'ne':
newNorth += heightDiff;
break;
case 'sw':
case 'se':
newSouth -= heightDiff;
break;
}
}
}
// Upewnij się, że bounds są prawidłowe (north > south, east > west)
if (newNorth > newSouth && newEast > newWest) {
imgBounds.north = newNorth;
imgBounds.south = newSouth;
imgBounds.west = newWest;
imgBounds.east = newEast;
const newBounds = L.latLngBounds(
[imgBounds.south, imgBounds.west],
[imgBounds.north, imgBounds.east]
);
currentImageOverlay.setBounds(newBounds);
updateBoundsInputs();
updateCornerMarkers();
}
}
function onScaleEnd() {
scaleCorner = null;
scaleStartLatLng = null;
scaleStartBounds = null;
map.off('mousemove', onScaleMove);
map.off('mouseup', onScaleEnd);
}
// ===============================================
// SKANER OBRAZÓW
// ===============================================
let colorPickMode = false;
let scannedLayers = [];
// Aktualizacja wyświetlanych wartości
document.getElementById('colorTolerance').addEventListener('input', function(e) {
document.getElementById('toleranceVal').textContent = e.target.value;
});
document.getElementById('simplifyTolerance').addEventListener('input', function(e) {
document.getElementById('simplifyVal').textContent = e.target.value;
});
// Tryb pobierania koloru z mapy
document.getElementById('pickColorBtn').addEventListener('click', function() {
if (!currentImageOverlay) {
alert('Najpierw wczytaj obraz!');
return;
}
colorPickMode = !colorPickMode;
if (colorPickMode) {
this.textContent = '❌ Anuluj';
this.style.background = '#a64d4d';
document.getElementById('scanStatus').textContent = 'Kliknij na obraz aby pobrać kolor...';
map.getContainer().style.cursor = 'crosshair';
} else {
this.textContent = '🎯 Pobierz z mapy';
this.style.background = '';
document.getElementById('scanStatus').textContent = '';
map.getContainer().style.cursor = '';
}
});
// Obsługa kliknięcia na mapę w trybie pobierania koloru
map.on('click', async function(e) {
if (!colorPickMode || !currentImageOverlay) return;
try {
// Załaduj obraz do skanera
await ImageScanner.loadImage(currentImageOverlay, imgBounds);
// Pobierz kolor
const color = ImageScanner.getColorAtLatLng(e.latlng, map);
if (color) {
const hex = ImageScanner.rgbToHex(color.r, color.g, color.b);
document.getElementById('scanColor').value = hex;
document.getElementById('scanStatus').textContent =
`Pobrany kolor: RGB(${color.r}, ${color.g}, ${color.b})`;
} else {
document.getElementById('scanStatus').textContent = 'Kliknięto poza obrazem!';
}
} catch (err) {
document.getElementById('scanStatus').textContent = 'Błąd: ' + err;
}
// Wyłącz tryb pobierania
colorPickMode = false;
document.getElementById('pickColorBtn').textContent = '🎯 Pobierz z mapy';
document.getElementById('pickColorBtn').style.background = '';
map.getContainer().style.cursor = '';
});
// Skanowanie obrazu
document.getElementById('scanImageBtn').addEventListener('click', async function() {
if (!currentImageOverlay) {
alert('Najpierw wczytaj obraz!');
return;
}
const statusEl = document.getElementById('scanStatus');
statusEl.textContent = 'Ładowanie obrazu...';
try {
// Załaduj obraz
await ImageScanner.loadImage(currentImageOverlay, imgBounds);
statusEl.textContent = 'Skanowanie...';
// Pobierz parametry
const hexColor = document.getElementById('scanColor').value;
const sampleColor = ImageScanner.hexToRgb(hexColor);
const tolerance = parseInt(document.getElementById('colorTolerance').value);
const simplify = parseInt(document.getElementById('simplifyTolerance').value);
// Skanuj (z opóźnieniem aby UI się odświeżył)
await new Promise(resolve => setTimeout(resolve, 50));
const results = await ImageScanner.scan(sampleColor, {
tolerance: tolerance,
simplifyTolerance: simplify,
maxGap: 5,
minPoints: 10
});
// Usuń poprzednie wyniki
scannedLayers.forEach(layer => map.removeLayer(layer));
scannedLayers = [];
// Dodaj wykryte linie
results.lines.forEach(coords => {
const latLngs = coords.map(c => [c[1], c[0]]);
const line = L.polyline(latLngs, {
color: hexColor,
weight: 2,
opacity: 0.8
}).addTo(map);
scannedLayers.push(line);
});
// Dodaj wykryte polygony
results.polygons.forEach(coords => {
const latLngs = coords.map(c => [c[1], c[0]]);
const polygon = L.polygon(latLngs, {
color: hexColor,
fillColor: hexColor,
fillOpacity: 0.3,
weight: 2
}).addTo(map);
scannedLayers.push(polygon);
});
statusEl.textContent = `Znaleziono: ${results.lines.length} linii, ${results.polygons.length} polygonów`;
// Eksportuj do textarea
if (results.lines.length > 0 || results.polygons.length > 0) {
const allCoords = [...results.lines.flat(), ...results.polygons.flat()];
document.getElementById('coordsOutput').value = JSON.stringify(
results.lines.length > 0 ? results.lines[0] : results.polygons[0],
null, 2
);
}
} catch (err) {
statusEl.textContent = 'Błąd: ' + err;
console.error(err);
}
});
// ===============================================
// Widok startowy
// ===============================================
let bounds = L.latLngBounds(allPoints);
bounds.extend(ronLayer.getBounds());
bounds.extend(rusLayer.getBounds());
map.fitBounds(bounds.pad(0.2));
</script>
</body>
</html>