import L from "leaflet";
import "leaflet/dist/leaflet.css";
import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";
// Fixar så att Leeflet-markören funkar i Vite-build
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: markerIcon2x,
iconUrl: markerIcon,
shadowUrl: markerShadow,
});
/**
* Hämtar koordinater för en plats via Open-Meteo Geocoding API.
* @async
* @param {string} query - Platsnamn som användaren söker efter tex "Stockholm"
* @returns {Promise<{lat:number, lon:number, name:string} | null>} Returnerar ett objekt med lat/lon och en label, eller null om ingen träff hittades.
* @throws {Error} Om API-anropet misslyckats (tex nätverksfel eller 4xx/5xx).
*/
async function geocode(query) {
const url = "https://geocoding-api.open-meteo.com/v1/search?" + new URLSearchParams({
name: query,
count: "1",
language: "sv",
format: "json",
});
const res = await fetch(url, { headers: { Accept: "application/json" } });
if (!res.ok) throw new Error(`Kunde inte söka plats: ${res.status}`);
const data = await res.json();
const first = data?.results?.[0];
if (!first) return null;
const label = [first.name, first.admin1, first.country]
.filter(Boolean)
.join(", ");
return {
lat: Number(first.latitude),
lon: Number(first.longitude),
name: label || query,
};
}
/**
* Initierar Leaflet-kartan, lägger till tile-layer och en startmarkör.
* @returns {{map: any, marker: any}}
* Ett objekt med kartinstansen och markören så de kan uppdateras vid sökning.
* @throws {Error} Om #map-elementet saknas i HTML.
*/
function initMap() {
const mapEl = document.getElementById("map");
if (!mapEl) throw new Error("#map saknas i HTML");
// default view
const start = [62.3908, 17.3069];
const map = L.map(mapEl, { zoomControl: true}).setView(start,12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors",
}).addTo(map);
const marker = L.marker(start).addTo(map);
// Säkerställer att kartan renderas korrekt när layouten är klar.
setTimeout(() =>
map.invalidateSize(), 250);
return { map, marker };
}
/**
* Startar kart-sidan: sätter upp karta och kopplar formuläre till sökfunktionen.
* @async
* @returns {Promise<void>}
*/
async function main() {
const form = document.querySelector("#placeForm");
const input = document.querySelector("#placeInput");
const status = document.querySelector("#mapStatus");
if (!form || !input || !status) return;
const { map, marker } = initMap();
form.addEventListener("submit", async (e) => {
e.preventDefault();
const q = input.value.trim();
if (!q) return;
status.textContent = "Söker...";
try {
const place = await geocode(q);
if (!place) {
status.textContent = "Ingen plats hittades.";
return;
}
marker.setLatLng([place.lat, place.lon]);
map.setView([place.lat, place.lon], 13, { animate: true });
setTimeout(() => map.invalidateSize(),200);
status.textContent = `Visar: ${place.name}`;
} catch (err) {
console.error(err);
status.textContent = "Något gick fel. Kolla Console.";
}
});
}
// Kör när sidan är laddad
window.addEventListener("load", () =>
{
main();
});