KML / Leaflet Maps

Mis on KML?

KML (Keyhole Markup Language) on XML-põhine failiformaat, mida kasutatakse geograafilise info esitamiseks ja vahetamiseks.

See töötati algselt välja ettevõttes Keyhole Inc. (hiljem ostis Google selle ära) ning on eriti tuntud seoses Google Earthi ja Google Mapsi kasutusega.

Peamised omadused:

  • Struktuur: põhineb XML-il, seega on see inimloetav ja masintöödeldav.
  • Sisu: võimaldab kirjeldada punkte, jooni, polügoone, kõrguseid, pilte, linke ja tekste.
  • Stiilid: saab määrata objektidele värve, ikoone, läbipaistvust ja muid visuaalseid omadusi.
  • Aja dimensioon: toetab ka ajaga seotud andmeid (nt animatsioonid või ajaloolised liikumisteekonnad).

Milleks kasutatakse:

  • Kaardil asukohtade näitamiseks (nt kodu, töökoht, huvipunktid).
  • Marsruutide ja teekondade jagamiseks (jalutuskäigud, lennutrajektoorid).
  • Ruumilise analüüsi tulemuste kuvamiseks (näiteks GIS-andmed).
  • Visuaalsete projektide jaoks (nt kinnisvarakaardid, turismikaardid, looduskaitsealad).

Lühidalt: KML on formaat, mis võimaldab geograafilist infot standardiseeritult kirjeldada ja jagada, et seda oleks võimalik visualiseerida kaartidel ja 3D-maailmades (nagu Google Earth).

KML-faili loomine ja kodukoha näitamine:

  • Ava uus leht
  • Loo uus kaart.
  • Paneme punktid oma kodukoha peale ja kuhu näiteks me tahame sõita.
  • Vali Export as KML/KMZ.

Tee koolist koju:

KML-faili saab lisada joone (LineString), mis näitab teekonda koolist koju. Seda saab teha Google My Mapsis, tõmmates joon vastava tööriistaga, või Google Earthis, kasutades funktsiooni Add Path.

Kodukoha tähistamine kujundiga

Lisaks markerile saab KML-faili lisada ka kujundi (nt ring, polügoon või kohandatud ikoon), mis tähistab kodukohta. Näiteks võib joonistada polügooni ümber oma maja või kasutada spetsiaalset ikooni (nt maja pilt).

Kaardi lisamine veebilehele Leaflet Maps abil

Eesti keeles:
LeafletJS on üks kõige populaarsemaid ja lihtsamaid JavaScripti teeke kaartide loomiseks. See on kerge, töötab kõigis brauserites ning võimaldab kaarte väga kiiresti lisada.
KML-faili kuvamiseks Leafletis on vaja:

  1. HTML-leht – kuhu lisatakse <div id="map"></div>, mis toimib kaardi konteinerina.
  2. Leafleti teek – lisatakse <script> ja <link> abil (või CDN-i kaudu).
  3. Taustakaart – tavaliselt OpenStreetMap (tasuta), mis kuvatakse Leafleti L.tileLayer abil.
  4. KML-faili laadimine – kuna Leaflet ei toeta otse KML-faile, tuleb need konverteerida GeoJSON-iks. Selleks saab kasutada raamatukogu togeojson.
  5. GeoJSON-i lisamine kaardile – Leaflet oskab kuvada punkte, jooni ja polügoone L.geoJSON funktsiooniga.
  6. Interaktiivsus – igale objektile saab lisada hüpikakna (bindPopup), muuta stiili (värv, paksus, läbipaistvus), grupeerida kihte ning lubada kasutajal neid sisse-välja lülitada.

Näiteks:

  • Kui õpilane teeb KML-faili, kus on tema kodukoht ja kooli tee, saab ta seda otse kaardil kuvada.
  • KML-fail laaditakse üles samasse kausta, kus HTML asub, ja leht avades kuvatakse automaatselt kaart koos õpilase andmetega.

index.html:

&lt;!DOCTYPE html>
&lt;html lang="ru">
&lt;head>
  &lt;meta charset="utf-8" />
  &lt;title>KML-Map&lt;/title>
  &lt;meta name="viewport" content="width=device-width,initial-scale=1" />

  &lt;!-- Leaflet -->
  &lt;link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
  &lt;script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js">&lt;/script>

  &lt;!-- togeojson -->
  &lt;script src="https://unpkg.com/@tmcw/togeojson@5.8.1/dist/togeojson.umd.js">&lt;/script>

  &lt;style>
    html, body { height: 100%; margin: 0; }
    #map { height: 100%; }
    header {
      position: absolute; top: 0; left: 0; right: 0; z-index: 2000;
      background:#0d47a1; color:#fff; padding:12px 16px; text-align:center;
      font:600 20px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
    }
    .toolbar {
      position: absolute; z-index: 1000; top: 60px; left: 10px;
      background: #fff; padding: 10px; border-radius: 8px;
      box-shadow: 0 4px 12px rgba(0,0,0,.15); max-width: 320px;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; font-size: 14px;
    }
    .toolbar h3 { margin: 0 0 8px 0; font-size: 16px; }
    .toolbar .row { display: flex; gap: 6px; margin: 8px 0; }
    .toolbar input[type="text"] { flex: 1; padding: 6px 8px; border: 1px solid #ddd; border-radius: 6px; }
    .toolbar button {
      padding: 6px 10px; border: 1px solid #0a58ca; background: #0d6efd; color: #fff;
      border-radius: 6px; cursor: pointer;
    }
    .dropzone { border:2px dashed #bbb; border-radius:8px; padding:8px; text-align:center; color:#666; }
    .dropzone.drag { border-color:#0d6efd; color:#0d6efd; background:rgba(13,110,253,.05); }
    .legend {
      position: absolute; z-index: 1000; bottom: 10px; left: 10px;
      background: #fff; padding: 8px 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,.15);
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; font-size: 13px;
    }
    .legend div { display: flex; align-items: center; gap: 6px; margin: 2px 0; }
    .swatch { width: 12px; height: 12px; border-radius: 2px; display: inline-block; }
  &lt;/style>
&lt;/head>
&lt;body>
  &lt;header>KML-Map&lt;/header>
  &lt;div id="map">&lt;/div>

  &lt;div class="toolbar">
    &lt;h3>Загрузка KML&lt;/h3>
    &lt;div class="row">
      &lt;input type="file" id="file" accept=".kml,.xml" />
      &lt;button id="loadFileBtn">Загрузить&lt;/button>
    &lt;/div>
    &lt;div class="dropzone" id="dropzone">Перетащи сюда KML-файл (или нажми «Загрузить»)&lt;/div>
    &lt;div class="row" style="margin-top:8px;">
      &lt;input type="text" id="urlInput" placeholder="https://example.com/your.kml" />
      &lt;button id="loadUrlBtn">URL&lt;/button>
    &lt;/div>
    &lt;small>Поддерживаются точки, пути (LineString) и полигоны.&lt;/small>
  &lt;/div>

  &lt;div class="legend">
    &lt;div>&lt;span class="swatch" style="background:#2b8a3e">&lt;/span> Полигоны&lt;/div>
    &lt;div>&lt;span class="swatch" style="background:#0d6efd">&lt;/span> Пути (линии)&lt;/div>
    &lt;div>&lt;span class="swatch" style="background:#6c757d; border-radius:50%">&lt;/span> Точки&lt;/div>
  &lt;/div>

&lt;script>
  const map = L.map('map', { preferCanvas: true }).setView([59.4427, 24.8147], 11);
  const osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 22, attribution: '&amp;copy; OpenStreetMap'
  }).addTo(map);

  const pointsLayer   = L.layerGroup().addTo(map);
  const linesLayer    = L.layerGroup().addTo(map);
  const polygonsLayer = L.layerGroup().addTo(map);

  L.control.layers(
    { 'OpenStreetMap': osm },
    { 'Точки': pointsLayer, 'Пути': linesLayer, 'Полигоны': polygonsLayer },
    { collapsed: false }
  ).addTo(map);

  function styleForFeature(feature) {
    const type = feature.geometry?.type || '';
    if (type === 'Polygon' || type === 'MultiPolygon') {
      return { weight: 1.2, color: '#2b8a3e', fillColor: '#2b8a3e', fillOpacity: 0.25 };
    }
    if (type === 'LineString' || type === 'MultiLineString') {
      return { weight: 3, color: '#0d6efd' };
    }
    return {};
  }

  function popupHtml(props = {}) {
    const lines = [];
    if (props.name) lines.push(`&lt;div>&lt;strong>${escapeHtml(props.name)}&lt;/strong>&lt;/div>`);
    if (props.description) lines.push(`&lt;div>${props.description}&lt;/div>`);
    const other = Object.entries(props)
      .filter(([k]) => k !== 'name' &amp;&amp; k !== 'description')
      .map(([k, v]) => `&lt;div>&lt;b>${escapeHtml(k)}:&lt;/b> ${escapeHtml(String(v))}&lt;/div>`)
      .join('');
    if (other) lines.push(`&lt;hr style="margin:6px 0" />${other}`);
    return lines.join('') || '&lt;em>Нет атрибутов&lt;/em>';
  }

  function escapeHtml(s) {
    return s.replaceAll('&amp;','&amp;amp;').replaceAll('&lt;','&amp;lt;').replaceAll('>','&amp;gt;')
            .replaceAll('"','&amp;quot;').replaceAll("'",'&amp;#39;');
  }

  function renderGeoJSON(geojson) {
    pointsLayer.clearLayers();
    linesLayer.clearLayers();
    polygonsLayer.clearLayers();
    const bounds = L.latLngBounds([]);

    L.geoJSON(geojson, {
      style: styleForFeature,
      pointToLayer: (feature, latlng) =>
        L.circleMarker(latlng, {
          radius: 6, weight: 1, color: '#6c757d', fillColor: '#6c757d', fillOpacity: 0.9
        }),
      onEachFeature: (feature, layer) => {
        layer.bindPopup(popupHtml(feature.properties || {}));
        const t = feature.geometry?.type;
        if (t === 'Point' || t === 'MultiPoint') pointsLayer.addLayer(layer);
        else if (t === 'LineString' || t === 'MultiLineString') linesLayer.addLayer(layer);
        else if (t === 'Polygon' || t === 'MultiPolygon') polygonsLayer.addLayer(layer);

        try {
          const b = layer.getBounds?.() || (layer.getLatLng &amp;&amp; L.latLngBounds([layer.getLatLng()]));
          if (b &amp;&amp; b.isValid &amp;&amp; b.isValid()) bounds.extend(b);
        } catch {}
      }
    });

    if (bounds.isValid()) map.fitBounds(bounds.pad(0.05));
  }

  function loadKmlFromText(kmlText) {
    const parser = new DOMParser();
    const kmlDom = parser.parseFromString(kmlText, 'text/xml');
    const gj = window.toGeoJSON.kml(kmlDom, { styles: true });
    renderGeoJSON(gj);
  }

  async function loadKmlFromUrl(url) {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`Не удалось загрузить KML: HTTP ${res.status}`);
    const text = await res.text();
    loadKmlFromText(text);
  }

  // Автозагрузка: сразу тянем MinuMap.kml из той же папки
  document.addEventListener('DOMContentLoaded', () => {
    // добавил кэш-бастер, чтобы подхватывались свежие изменения файла
    loadKmlFromUrl('MinuMap.kml?ts=' + Date.now()).catch(console.error);
  });

  // Кнопки/drag&amp;drop остаются рабочими
  const fileInput = document.getElementById('file');
  const loadFileBtn = document.getElementById('loadFileBtn');
  const urlInput = document.getElementById('urlInput'); 
  const loadUrlBtn = document.getElementById('loadUrlBtn');
  const dropzone = document.getElementById('dropzone');

  loadFileBtn.addEventListener('click', async () => {
    if (!fileInput.files?.length) { alert('Выберите KML-файл.'); return; }
    const file = fileInput.files[0];
    const text = await file.text();
    loadKmlFromText(text);
  });

  loadUrlBtn.addEventListener('click', async () => {
    const url = urlInput.value.trim();
    if (!url) { alert('Вставьте URL KML-файла.'); return; }
    try {
      await loadKmlFromUrl(url);
    } catch (e) {
      console.error(e);
      alert(e.message || 'Ошибка загрузки по URL');
    }
  });

  ['dragenter','dragover'].forEach(evt =>
    dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.add('drag'); })
  );
  ['dragleave','drop'].forEach(evt =>
    dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.remove('drag'); })
  );
  dropzone.addEventListener('drop', async (e) => {
    const file = e.dataTransfer?.files?.[0];
    if (!file) return;
    const text = await file.text();
    loadKmlFromText(text);
  });
&lt;/script>
&lt;/body>
&lt;/html>

MinuMap.kml on väga suur sest ma ei panne siia kml faili sissu

Link tulemusele (zone.ee)

Siin on minu veebileht mis ma olen salvestatud zone.ee serveris: https://vsevolodtsarev23.thkit.ee/KML_Maps/index.html

OpenLayers kaardil KML

OpenLayers on teine väga võimas JavaScripti teek, mis sobib keerulisemate GIS-rakenduste jaoks. Erinevalt Leafletist saab OpenLayers otse lugeda ja kuvada KML-faile ilma konversioonita.

Kuidas see töötab:

  1. Loome ol.Map objekti ja lisame taustakaardi (nt OpenStreetMap).
  2. Lisame uue vektorkihi (ol.layer.Vector), mille andmeallikas on ol.source.Vector.
  3. Andmeallikas loeb KML-faili ol.format.KML abil otse failist või URL-ist.
  4. Kujundust (värvid, joone paksus, punktide suurus) saab määrata stiili abil (ol.style.Style).
  5. Kaardile saab lisada ka pop-up akna, mis näitab KML-faili atribuute (nt nimi, kirjeldus).

Näide: Kui KML-fail sisaldab õpilase kodu ja koolitee, siis OpenLayers oskab selle otse kaardile panna ilma täiendavate teekideta.

Kokkuvõte

  • Google My Maps ja Google Earth – sobivad kõige paremini KML-failide loomiseks. Need on mugavad tööriistad, millega saab kiiresti kaardile lisada punkte, jooni ja polügoone. Sobivad algajatele, koolitööks ja lihtsate kaartide koostamiseks.
  • LeafletJS – on ideaalne, kui on vaja KML-faili näidata veebilehel. See on kerge ja töötab hästi lihtsate projektide puhul (nt õpilaste kodukoha kaardid). Miinus: ei toeta otse KML-faili, vaid vajab konversiooni GeoJSON-iks.
  • OpenLayers – sobib professionaalsemate ja keerulisemate projektide jaoks. Toetab otsest KML lugemist, töötab erinevate projektsioonidega, võimaldab teha GIS-analüüsi. Miinus: keerulisem kasutada ja õppida kui Leaflet.

Kokkuvõte:

  • Kui eesmärk on lihtne kaart veebis → vali LeafletJS.
  • Kui eesmärk on keeruline GIS-lahendus → kasuta OpenLayers.
  • Kui eesmärk on lihtsalt teha KML-fail ja näidata marsruuti → kasuta Google My Maps või Google Earth.

Siin te võiksite minu mappi näha: KML_Map