<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>LiDAR Extractor - Canada HRDEM Edition</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- OpenLayers CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@7.3.0/ol.css" />
  <style>
    html, body {
      margin: 0; padding: 0; height: 100%;
      font-family: Arial, sans-serif;
    }
    #map {
      width: 100%; height: 80vh;
    }
    #controls {
      width: 100%; height: 20%;
      padding: 10px;
      background: #f3f3f3;
      box-sizing: border-box;
    }
    .control-button {
      background: #007BFF;
      color: #fff;
      padding: 8px 12px;
      border-radius: 4px;
      border: none;
      cursor: pointer;
      margin-right: 10px;
    }
    .control-button:hover {
      background: #0056b3;
    }

    /* Spinner overlay styling */
    #spinnerOverlay {
      position: fixed;
      top:0; left:0; 
      width:100%; 
      height:100%;
      background-color: rgba(0,0,0,0.4);
      display: none; /* hidden by default */
      align-items: center; 
      justify-content: center;
      z-index: 9999; /* on top of everything */
    }
    #spinnerOverlay .spinnerMessage {
      color:#fff;
      font-size:1.2em;
      text-align:center;
      margin-top:10px;
    }
    .loader {
      border: 8px solid #f3f3f3; /* Light grey */
      border-top: 8px solid #3498db; /* Blue */
      border-radius:50%;
      width:60px; height:60px;
      animation: spin 1s linear infinite;
      margin:auto;
    }
    @keyframes spin {
      0%   { transform:rotate(0deg); }
      100% { transform:rotate(360deg); }
    }
  </style>
</head>
<body>

<!-- Map container -->
<div id="map"></div>

<!-- Controls container -->
<div id="controls">
  <button class="control-button" id="drawAOIButton">Draw AOI Polygon</button>
  <button class="control-button" id="clearAOIButton">Clear AOI</button>

  <!-- Original button for EPSG:4326 -->
  <button class="control-button" id="downloadButton">Download LiDAR - EPSG:4326</button>
  <!-- New button for EPSG:3857 -->
  <button class="control-button" id="downloadButton3857">Download LiDAR - EPSG:3857</button>

  <p style="margin-top: 8px; color: #666;">
    1) Click "Draw AOI Polygon" then draw your area of interest.<br/>
    2) Click first point to finish drawing.<br/>
    3) Click Download button to request LiDAR GeoTIFF in selected coordinate system.
  </p>
</div>

<!-- Spinner Overlay -->
<div id="spinnerOverlay">
  <div>
    <div class="loader"></div>
    <div class="spinnerMessage">Doing difficult things… please wait.</div>
  </div>
</div>

<!-- OpenLayers & Dependencies -->
<script src="https://cdn.jsdelivr.net/npm/ol@7.3.0/dist/ol.js"></script>
<script>
  ////////////////////////
  // SPINNER UTILITIES  //
  ////////////////////////
  function showSpinner() {
    document.getElementById('spinnerOverlay').style.display = 'flex';
  }
  function hideSpinner() {
    document.getElementById('spinnerOverlay').style.display = 'none';
  }

  //////////////////////////
  // LAYER & MAP SETUP    //
  //////////////////////////
  const bingMapsApiKey = 'AjWMrgpfTkWSYJxJHJUPa9agXTPAFkdNO9A_cCx-u7nhsayXwPFHptErPKpBy6ns';

  // Bing Aerial
  const bingLayer = new ol.layer.Tile({
    source: new ol.source.BingMaps({
      key: bingMapsApiKey,
      imagerySet: 'AerialWithLabelsOnDemand'
    })
  });

  // Quarter Sections
  const quarterSectionsLayer = new ol.layer.Tile({
    source: new ol.source.TileWMS({
      url: 'https://gis4ag.webgis1.com/geoserver/gis4ag/ows?service=WMS',
      params: {
          'LAYERS': 'MB_SK_AB_LLD',
          'TILED': false,
          'FORMAT': 'image/png',
          'VERSION': '1.3.0'
      },
      crossOrigin: 'anonymous'
    }),
    visible: true
  });

  // Ontario Lots
  const ONLotsLayer = new ol.layer.Tile({
    source: new ol.source.TileWMS({
      url: 'https://gis4ag.webgis1.com/geoserver/gis4ag/ows?service=WMS',
      params: {
          'LAYERS': 'Ontario_Lots',
          'TILED': false,
          'FORMAT': 'image/png',
          'VERSION': '1.3.0'
      },
      crossOrigin: 'anonymous'
    }),
    visible: true
  });

  // Hillshade layer
  const hillshadeLayer = new ol.layer.Tile({
    source: new ol.source.TileWMS({
      url: 'https://datacube.services.geo.ca/wrapper/ogc/elevation-hrdem-mosaic',
      params: {
        'LAYERS': 'dtm-hillshade',
        'TILED': true,
        'FORMAT': 'image/png',
        'VERSION': '1.3.0',
        'zFactor': 10
      },
      crossOrigin: 'anonymous'
    }),
    opacity: 0.5,
    visible: true
  });

  // Map view
  const view = new ol.View({
    center: ol.proj.fromLonLat([-97, 55]),
    zoom: 4
  });

  // Vector for AOI
  const drawSource = new ol.source.Vector();
  const drawLayer = new ol.layer.Vector({
    source: drawSource,
    style: new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: 'rgba(255,0,0,0.8)',
        width: 2
      }),
      fill: new ol.style.Fill({
        color: 'rgba(255,0,0,0.2)'
      })
    })
  });

  // Create the map
  const map = new ol.Map({
    target: 'map',
    layers: [
      bingLayer,
      quarterSectionsLayer,
      ONLotsLayer,
      hillshadeLayer,
      drawLayer
    ],
    view: view
  });

  /////////////////////////
  //   DRAW INTERACTION  //
  /////////////////////////
  let drawInteraction = null;
  document.getElementById('drawAOIButton').addEventListener('click', () => {
    if (drawInteraction) {
      map.removeInteraction(drawInteraction);
      drawInteraction = null;
    }
    drawSource.clear();

    drawInteraction = new ol.interaction.Draw({
      source: drawSource,
      type: 'Polygon'
    });
    map.addInteraction(drawInteraction);

    // Turn off double-click zoom
    const dblClickZoom = map.getInteractions().getArray().find(interaction => interaction instanceof ol.interaction.DoubleClickZoom);
    if (dblClickZoom) dblClickZoom.setActive(false);

    drawInteraction.on('drawend', () => {
      setTimeout(() => {
        map.removeInteraction(drawInteraction);
        drawInteraction = null;
        if (dblClickZoom) dblClickZoom.setActive(true);
        alert('AOI polygon drawn. Now click "Download LiDAR" to fetch GeoTIFF.');
      }, 100);
    });
  });

  // Clear AOI
  document.getElementById('clearAOIButton').addEventListener('click', () => {
    drawSource.clear();
  });

  //////////////////////////////////
  //   TILE LOGIC CONFIGURATION   //
  //////////////////////////////////
  const TILE_AREA_SQKM = 200;  // anything > 200 sq km => tile it
  const TILE_SIZE_METERS = Math.sqrt(TILE_AREA_SQKM * 1e6); // ~14142 m

  ////////////////////////////////
  //        EPSG:4326          //
  ////////////////////////////////
  document.getElementById('downloadButton').addEventListener('click', async () => {
    if (drawSource.getFeatures().length === 0) {
      alert('No AOI polygon drawn yet!');
      return;
    }
    const feature = drawSource.getFeatures()[0];
    const geometry = feature.getGeometry();
    if (geometry.getType() !== 'Polygon') {
      alert('AOI feature is not a polygon.');
      return;
    }

    // 1) Get bounding box in EPSG:3857 for area measurement
    const extent3857 = geometry.getExtent();
    const widthM  = Math.abs(extent3857[2] - extent3857[0]);
    const heightM = Math.abs(extent3857[3] - extent3857[1]);
    const areaM2  = widthM * heightM;

    // 2) If area < 200 km² => single coverage in EPSG:4326
    if (areaM2 <= TILE_AREA_SQKM * 1e6) {
      // single coverage
      await downloadSingleCoverage4326(geometry);
    } else {
      // tile approach
      await downloadTiledCoverage4326(geometry, areaM2);
    }
  });

  async function downloadSingleCoverage4326(geometry) {
    const extent = geometry.getExtent();
    const extent4326 = ol.proj.transformExtent(extent, 'EPSG:3857', 'EPSG:4326');

    const wcsBaseUrl = 'https://datacube.services.geo.ca/wrapper/ogc/elevation-hrdem-mosaic';
    const coverage = 'dtm';
    const wcsFormat = 'image/geotiff';

    const [minX, minY, maxX, maxY] = extent4326;

    // ~1000 dimension
    const lonSpan = Math.abs(maxX - minX);
    const latSpan = Math.abs(maxY - minY);
    let width = 1000, height = 1000;
    if (lonSpan > latSpan) {
      height = Math.round((latSpan / lonSpan) * width);
    } else {
      width = Math.round((lonSpan / latSpan) * height);
    }

    showSpinner();
    const wcsUrl = `${wcsBaseUrl}?service=WCS&request=GetCoverage&version=1.1.1`
      + `&identifier=${coverage}`
      + `&format=${encodeURIComponent(wcsFormat)}`
      + `&boundingbox=${minY},${minX},${maxY},${maxX},urn:ogc:def:crs:EPSG::4326`
      + `&width=${width}&height=${height}`;

    try {
      const response = await fetch(wcsUrl);
      if (!response.ok) {
        hideSpinner();
        alert('WCS request failed: ' + response.statusText);
        return;
      }
      const arrayBuffer = await response.arrayBuffer();

      hideSpinner();

      const blob = new Blob([arrayBuffer], { type: 'image/tiff' });
      const urlObj = URL.createObjectURL(blob);

      const link = document.createElement('a');
      link.href = urlObj;
      link.download = 'lidar_hrdem_4326.tif';
      link.click();

      URL.revokeObjectURL(urlObj);
      alert('LiDAR GeoTIFF (EPSG:4326) downloaded!');
    } catch (err) {
      hideSpinner();
      console.error(err);
      alert('Error fetching WCS coverage: ' + err);
    }
  }

  async function downloadTiledCoverage4326(geometry, areaM2) {
    alert(`AOI ~${(areaM2/1e6).toFixed(2)} km² (>200). We'll break it into tiles of ~200 km² each.`);

    // measure bounding box in EPSG:3857
    const extent3857 = geometry.getExtent();
    const xMin = extent3857[0];
    const yMin = extent3857[1];
    const xMax = extent3857[2];
    const yMax = extent3857[3];

    // compute tile counts
    const totalWidthM  = Math.abs(xMax - xMin);
    const totalHeightM = Math.abs(yMax - yMin);

    const tileCountX = Math.ceil(totalWidthM / TILE_SIZE_METERS);
    const tileCountY = Math.ceil(totalHeightM / TILE_SIZE_METERS);
    const totalTiles = tileCountX * tileCountY;

    alert(`Tiling into ${tileCountX}×${tileCountY} = ${totalTiles} tiles…`);

    let tileIndex = 0;
    for (let row = 0; row < tileCountY; row++) {
      for (let col = 0; col < tileCountX; col++) {
        tileIndex++;
        // sub-bbox in EPSG:3857
        const subXMin = xMin + col * TILE_SIZE_METERS;
        const subXMax = Math.min(subXMin + TILE_SIZE_METERS, xMax);
        const subYMin = yMin + row * TILE_SIZE_METERS;
        const subYMax = Math.min(subYMin + TILE_SIZE_METERS, yMax);

        // transform sub-bbox to EPSG:4326
        const subBbox4326 = ol.proj.transformExtent([subXMin, subYMin, subXMax, subYMax], 'EPSG:3857', 'EPSG:4326');

        await downloadOneTile4326(subBbox4326, tileIndex, totalTiles);
        // short delay so user sees each download
        await new Promise(r => setTimeout(r,500));
      }
    }
    alert('All tiles downloaded in EPSG:4326.');
  }

  async function downloadOneTile4326(bbox4326, tileIndex, totalTiles) {
    const [minX, minY, maxX, maxY] = bbox4326;

    // ~1000 dimension
    const lonSpan = Math.abs(maxX - minX);
    const latSpan = Math.abs(maxY - minY);
    let width = 1000, height = 1000;
    if (lonSpan > latSpan) {
      height = Math.round((latSpan / lonSpan) * width);
    } else {
      width = Math.round((lonSpan / latSpan) * height);
    }

    showSpinner();
    alert(`Downloading tile ${tileIndex}/${totalTiles}...`);

    const wcsBaseUrl = 'https://datacube.services.geo.ca/wrapper/ogc/elevation-hrdem-mosaic';
    const coverage = 'dtm';
    const wcsFormat = 'image/geotiff';

    const wcsUrl = `${wcsBaseUrl}?service=WCS&request=GetCoverage&version=1.1.1`
      + `&identifier=${coverage}`
      + `&format=${encodeURIComponent(wcsFormat)}`
      + `&boundingbox=${minY},${minX},${maxY},${maxX},urn:ogc:def:crs:EPSG::4326`
      + `&width=${width}&height=${height}`;

    try {
      const response = await fetch(wcsUrl);
      if (!response.ok) {
        hideSpinner();
        alert('Tile request failed: ' + response.statusText);
        return;
      }
      const arrayBuffer = await response.arrayBuffer();

      hideSpinner();

      const blob = new Blob([arrayBuffer], { type: 'image/tiff' });
      const urlObj = URL.createObjectURL(blob);

      const link = document.createElement('a');
      link.href = urlObj;
      link.download = `lidar_tile_${tileIndex}_4326.tif`;
      link.click();

      URL.revokeObjectURL(urlObj);
    } catch (err) {
      hideSpinner();
      console.error(err);
      alert('Error fetching tile coverage: ' + err);
    }
  }

  ////////////////////////////////
  //        EPSG:3857          //
  ////////////////////////////////
  document.getElementById('downloadButton3857').addEventListener('click', async () => {
    if (drawSource.getFeatures().length === 0) {
      alert('No AOI polygon drawn yet!');
      return;
    }
    const feature = drawSource.getFeatures()[0];
    const geometry = feature.getGeometry();
    if (geometry.getType() !== 'Polygon') {
      alert('AOI feature is not a polygon.');
      return;
    }

    // bounding box in EPSG:3857
    const extent3857 = geometry.getExtent();
    const widthM  = Math.abs(extent3857[2] - extent3857[0]);
    const heightM = Math.abs(extent3857[3] - extent3857[1]);
    const areaM2  = widthM * heightM;

    // if area <= 200 km² => single coverage
    if (areaM2 <= TILE_AREA_SQKM * 1e6) {
      await downloadSingleCoverage3857(geometry);
    } else {
      await downloadTiledCoverage3857(geometry, areaM2);
    }
  });

  async function downloadSingleCoverage3857(geometry) {
    const extent3857 = geometry.getExtent();
    const [xMin, yMin, xMax, yMax] = extent3857;

    const wcsBaseUrl = 'https://datacube.services.geo.ca/wrapper/ogc/elevation-hrdem-mosaic';
    const coverage = 'dtm';
    const wcsFormat = 'image/geotiff';

    const xSpan = Math.abs(xMax - xMin);
    const ySpan = Math.abs(yMax - yMin);
    let width = 1000, height = 1000;
    if (xSpan > ySpan) {
      height = Math.round((ySpan / xSpan) * width);
    } else {
      width = Math.round((xSpan / ySpan) * height);
    }

    showSpinner();
    const wcsUrl = `${wcsBaseUrl}?service=WCS&request=GetCoverage&version=1.1.1`
      + `&identifier=${coverage}`
      + `&format=${encodeURIComponent(wcsFormat)}`
      + `&boundingbox=${xMin},${yMin},${xMax},${yMax},urn:ogc:def:crs:EPSG::3857`
      + `&width=${width}&height=${height}`;

    try {
      const response = await fetch(wcsUrl);
      if (!response.ok) {
        hideSpinner();
        alert('WCS request failed: ' + response.statusText);
        return;
      }
      const arrayBuffer = await response.arrayBuffer();

      hideSpinner();

      const blob = new Blob([arrayBuffer], { type: 'image/tiff' });
      const urlObj = URL.createObjectURL(blob);

      const link = document.createElement('a');
      link.href = urlObj;
      link.download = 'lidar_hrdem_3857.tif';
      link.click();

      URL.revokeObjectURL(urlObj);
      alert('LiDAR GeoTIFF (EPSG:3857) downloaded!');
    } catch (err) {
      hideSpinner();
      console.error(err);
      alert('Error fetching WCS coverage (3857): ' + err);
    }
  }

  async function downloadTiledCoverage3857(geometry, areaM2) {
    alert(`AOI ~${(areaM2/1e6).toFixed(2)} km² (>200). We'll break it into tiles of ~200 km² each.`);

    const extent3857 = geometry.getExtent();
    const xMin = extent3857[0];
    const yMin = extent3857[1];
    const xMax = extent3857[2];
    const yMax = extent3857[3];

    const totalWidthM  = Math.abs(xMax - xMin);
    const totalHeightM = Math.abs(yMax - yMin);

    const tileCountX = Math.ceil(totalWidthM / TILE_SIZE_METERS);
    const tileCountY = Math.ceil(totalHeightM / TILE_SIZE_METERS);
    const totalTiles = tileCountX * tileCountY;

    alert(`Tiling into ${tileCountX}×${tileCountY} = ${totalTiles} tiles…`);

    let tileIndex = 0;
    for (let row = 0; row < tileCountY; row++) {
      for (let col = 0; col < tileCountX; col++) {
        tileIndex++;
        const subXMin = xMin + col * TILE_SIZE_METERS;
        const subXMax = Math.min(subXMin + TILE_SIZE_METERS, xMax);
        const subYMin = yMin + row * TILE_SIZE_METERS;
        const subYMax = Math.min(subYMin + TILE_SIZE_METERS, yMax);

        await downloadOneTile3857([subXMin, subYMin, subXMax, subYMax], tileIndex, totalTiles);
        await new Promise(r => setTimeout(r, 500));
      }
    }
    alert('All tiles downloaded in EPSG:3857.');
  }

  async function downloadOneTile3857(subExtent, tileIndex, totalTiles) {
    const [xMin, yMin, xMax, yMax] = subExtent;

    // ~1000 dimension
    const xSpan = Math.abs(xMax - xMin);
    const ySpan = Math.abs(yMax - yMin);
    let width = 1000, height = 1000;
    if (xSpan > ySpan) {
      height = Math.round((ySpan / xSpan) * width);
    } else {
      width = Math.round((xSpan / ySpan) * height);
    }

    showSpinner();
    alert(`Downloading tile ${tileIndex}/${totalTiles}...`);

    const wcsBaseUrl = 'https://datacube.services.geo.ca/wrapper/ogc/elevation-hrdem-mosaic';
    const coverage = 'dtm';
    const wcsFormat = 'image/geotiff';

    const wcsUrl = `${wcsBaseUrl}?service=WCS&request=GetCoverage&version=1.1.1`
      + `&identifier=${coverage}`
      + `&format=${encodeURIComponent(wcsFormat)}`
      + `&boundingbox=${xMin},${yMin},${xMax},${yMax},urn:ogc:def:crs:EPSG::3857`
      + `&width=${width}&height=${height}`;

    try {
      const response = await fetch(wcsUrl);
      if (!response.ok) {
        hideSpinner();
        alert('Tile request failed: ' + response.statusText);
        return;
      }
      const arrayBuffer = await response.arrayBuffer();

      hideSpinner();

      const blob = new Blob([arrayBuffer], { type: 'image/tiff' });
      const urlObj = URL.createObjectURL(blob);

      const link = document.createElement('a');
      link.href = urlObj;
      link.download = `lidar_tile_${tileIndex}_3857.tif`;
      link.click();

      URL.revokeObjectURL(urlObj);
    } catch (err) {
      hideSpinner();
      console.error(err);
      alert('Error fetching tile coverage: ' + err);
    }
  }
</script>
</body>
</html>