diff --git a/gui.py b/gui.py new file mode 100755 index 0000000..e13aea5 --- /dev/null +++ b/gui.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Launch the Meshtastic Tile Generator GUI in a browser.""" + +import http.server +import socketserver +import webbrowser +import threading +import signal +import sys +from pathlib import Path + +PORT = 8000 + +def main(): + os_dir = Path(__file__).parent + + class Handler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(os_dir), **kwargs) + + def log_message(self, format, *args): + pass # Suppress request logging + + with socketserver.TCPServer(("", PORT), Handler) as httpd: + url = f"http://localhost:{PORT}/maps.html" + print(f"πŸ—ΊοΈ Meshtastic Tile Generator GUI") + print(f" Opening {url}") + print(f" Press Ctrl+C to stop\n") + + # Open browser after short delay + threading.Timer(0.5, lambda: webbrowser.open(url)).start() + + # Handle Ctrl+C gracefully + signal.signal(signal.SIGINT, lambda *_: sys.exit(0)) + + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + + print("\nServer stopped.") + +if __name__ == "__main__": + main() diff --git a/maps.html b/maps.html index 54cb212..d589b3c 100644 --- a/maps.html +++ b/maps.html @@ -237,11 +237,20 @@

πŸ—ΊοΈ Meshtastic Tile Generator

How to use:
- 1. Click and drag on the map to select an area
+ 1. Search for a location or Shift+drag to select
2. Choose your zoom levels and map source
3. Copy the generated command and run it
+
+

πŸ” Search Location

+
+ + +
+
+
+

πŸ“ Area Selection

@@ -367,16 +376,16 @@

🌟 Quick Presets

map.on('mousemove', onMouseMove); map.on('mouseup', onMouseUp); - // Prevent default selection behavior - map.dragging.disable(); - map.on('mousedown', function() { map.dragging.disable(); }); - map.on('mouseup', function() { map.dragging.enable(); }); + } - // Mouse event handlers for area selection + // Mouse event handlers for area selection (Shift+drag to select) function onMouseDown(e) { + if (!e.originalEvent.shiftKey) return; + isSelecting = true; startPoint = e.latlng; + map.dragging.disable(); if (selectionRectangle) { map.removeLayer(selectionRectangle); @@ -408,6 +417,7 @@

🌟 Quick Presets

if (!isSelecting || !startPoint) return; isSelecting = false; + map.dragging.enable(); const bounds = L.latLngBounds(startPoint, e.latlng); // Update form fields @@ -598,6 +608,69 @@

🌟 Quick Presets

}); }); + // Search for a location using Nominatim + async function searchLocation() { + const query = document.getElementById('searchInput').value.trim(); + const status = document.getElementById('searchStatus'); + + if (!query) { + status.textContent = 'Enter a location to search'; + return; + } + + status.textContent = 'Searching...'; + + try { + const response = await fetch( + `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=1` + ); + const data = await response.json(); + + if (!data || data.length === 0) { + status.textContent = 'Location not found'; + return; + } + + const result = data[0]; + const bbox = result.boundingbox; // [south, north, west, east] + const south = parseFloat(bbox[0]); + const north = parseFloat(bbox[1]); + const west = parseFloat(bbox[2]); + const east = parseFloat(bbox[3]); + + // Update form fields + document.getElementById('north').value = north.toFixed(6); + document.getElementById('south').value = south.toFixed(6); + document.getElementById('east').value = east.toFixed(6); + document.getElementById('west').value = west.toFixed(6); + + // Draw rectangle and fit map + const bounds = L.latLngBounds([south, west], [north, east]); + + if (selectionRectangle) { + map.removeLayer(selectionRectangle); + } + + selectionRectangle = L.rectangle(bounds, { + color: '#3498db', + weight: 2, + fill: true, + fillOpacity: 0.2 + }).addTo(map); + + map.fitBounds(bounds, { padding: [20, 20] }); + + status.textContent = `Found: ${result.display_name.substring(0, 50)}...`; + document.getElementById('selectionInfo').classList.remove('hidden'); + updateBoundsDisplay(bounds); + calculateStats(); + generateCommand(); + + } catch (err) { + status.textContent = 'Search failed: ' + err.message; + } + } + // Initialize everything initMap();