1044 lines
31 KiB
HTML
1044 lines
31 KiB
HTML
<!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>
|