Skip to content

Instantly share code, notes, and snippets.

@mdsumner
Last active January 6, 2026 04:35
Show Gist options
  • Select an option

  • Save mdsumner/374b0fb842f591c55e47fd804bb47268 to your computer and use it in GitHub Desktop.

Select an option

Save mdsumner/374b0fb842f591c55e47fd804bb47268 to your computer and use it in GitHub Desktop.

a plumber2 function to get imagery for a website from a URI (/vsizip//vsicurl/ vsiaz, WMTS: etc etc)

#* @get /tile
#* @serializer image/png
function(query) {

Sys.setenv(
  GDAL_CACHEMAX = "2048",           # MB for block cache
  VSI_CACHE = "TRUE",              # Cache remote reads
  VSI_CACHE_SIZE = "500000000",    # 500MB
  GDAL_HTTP_MULTIPLEX = "YES",     # HTTP/2 multiplexing
  GDAL_HTTP_MAX_RETRY = "3"
)

  bbox_raw <- query$bbox
  if (is.null(bbox_raw)) {
    bbox <- c(-1000000, -1000000, 1000000, 1000000)
  } else if (length(bbox_raw) == 4) {
    bbox <- as.numeric(bbox_raw)
  } else {
    bbox <- as.numeric(strsplit(bbox_raw, ",")[[1]])
  }

  crs <- if (!is.null(query$crs)) query$crs[1] else "EPSG:3031"
  source <- if (!is.null(query$source)) query$source[1] else "/data/ibcso-small.tif"
source <- if (!is.null(query$source)) query$source[1] else "/data/ibcso-small.tif"
message("Received source: ", source)
  width <- if (!is.null(query$width)) query$width[1] else "256"
  height <- if (!is.null(query$height)) query$height[1] else "256"

  png_dsn <- tempfile(tmpdir = "/vsimem", fileext = ".png")
  #dsn <- sprintf("vrt://%s?scale=true&ot=Byte", source)
  dsn <- source
  gdalraster::warp(
    dsn, png_dsn, t_srs = crs,
    cl_arg = c("-ts", width, height, "-te", as.character(bbox), "-r", "bilinear", "-ot", "Byte")
  )

  vsi <- new(VSIFile, png_dsn)
  png_bytes <- vsi$ingest(-1)
  vsi$close()
  vsi_unlink(png_dsn)
 png_bytes

}
@mdsumner
Copy link
Author

mdsumner commented Jan 5, 2026

here is the current js content, I'm still actively working on what I want this for

<script>
        const API_BASE = '/tiles';
        const TILE_SIZE = 512;
        let map = null;
        let tileLayer = null;
        let currentLon0 = 0;

        function getSource() {
            return document.getElementById('source-input').value.trim();
        }

        function setStatus(text, ok) {
            const el = document.getElementById('status');
            el.textContent = text;
            el.className = 'status ' + (ok ? 'ok' : 'error');
        }

        function loadSource(src, bands) {
            document.getElementById('source-input').value = src;
            document.getElementById('bands-input').value = bands || '1';
            rebuildMap();
        }

        function resetLon0() {
            document.getElementById('lon0-slider').value = 0;
            document.getElementById('lon0-value').textContent = '0°';
            currentLon0 = 0;
            rebuildMap();
        }

        // Antarctic Stereographic CRS
        const extent = 8000000;
        const resolutions = [];
        for (let i = 0; i <= 10; i++) {
            resolutions.push((extent * 2) / TILE_SIZE / Math.pow(2, i));
        }
        //for (let i = 1; i <= 11; i++) {
        //  resolutions.push((extent * 2) / TILE_SIZE / Math.pow(2, i));
        //}
        // Build CRS with current lon_0
        function buildCRS(lon0) {
            const crsCode = `AntarcticPolarStereographic:${lon0}`;
            const proj4def = `+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=${lon0} +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs`;
            console.log(proj4def);
            return new L.Proj.CRS(
                crsCode,
                proj4def,
                {
                    resolutions: resolutions,
                    origin: [-extent, extent],
                    bounds: L.bounds([-extent, -extent], [extent, extent])
                }
            );
        }

        let crs = buildCRS(0);

const Base64TileLayer = L.TileLayer.extend({
    createTile: function(coords, done) {
        const tile = document.createElement('img');

        const tileSize = TILE_SIZE;
        const zoom = coords.z;
        const resolution = crs.options.resolutions[zoom];
        const origin = crs.options.origin;
        const tileMeters = tileSize * resolution;
        const xmin = origin[0] + (coords.x * tileMeters);
        const xmax = xmin + tileMeters;
        const ymax = origin[1] - (coords.y * tileMeters);
        const ymin = ymax - tileMeters;
        const bbox = [xmin, ymin, xmax, ymax].join(',');

        const bands = document.getElementById('bands-input').value.trim() || '1';
        const proj4crs = `+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=${currentLon0} +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs`;

        tile.onload = () => done(null, tile);
        tile.onerror = (err) => done(err, tile);
console.log('tile URL:', `${API_BASE}/tile?bbox=${bbox}&source=${encodeURIComponent(getSource())}&bands=${bands}&crs=${encodeURIComponent(proj4crs)}`);

        tile.src = `${API_BASE}/tile?bbox=${bbox}&source=${encodeURIComponent(getSource())}&bands=${bands}&crs=${encodeURIComponent(proj4crs)}`;

        return tile;
    }
});


        function rebuildMap() {
            const center = map ? map.getCenter() : L.latLng(-90, 0);
            const zoom = map ? map.getZoom() : 1;

            // Rebuild CRS with current lon_0
            crs = buildCRS(currentLon0);

            if (map) {
                map.remove();
            }

            map = L.map('map', {
                crs: crs,
                zoomSnap: 1,  // Force integer zoom levels
                zoomDelta: 1,
                center: center,
                zoom: zoom,
                maxZoom: 20,
                minZoom: 0
            });

            tileLayer = new Base64TileLayer('', { tileSize: TILE_SIZE });
            tileLayer.addTo(map);
            setStatus('loaded', true);
        }

        // lon_0 slider handlers
        const lon0Slider = document.getElementById('lon0-slider');
        const lon0Value = document.getElementById('lon0-value');

        lon0Slider.addEventListener('input', function() {
            lon0Value.textContent = this.value + '°';
        });

        lon0Slider.addEventListener('change', function() {
            currentLon0 = parseInt(this.value);
            rebuildMap();
        });

        // Bands input handler
        document.getElementById('bands-input').addEventListener('change', function() {
            rebuildMap();
        });

        // Check API health and init
        fetch(API_BASE + '/health')
            .then(r => r.json())
            .then(d => {
                console.log('API:', d);
                setStatus('ready', true);
                rebuildMap();
            })
            .catch(e => setStatus('API offline', false));
    </script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment