google maps | cache locations nearby | load google panorama | Search

This code fetches and processes depth map data for a given Google Street View panorama, enabling applications to access 3D information about the captured environment.

Run example

npm run import -- "extract depth maps"

extract depth maps

// FROM: https://github.com/sidequestlegend/GSVPanoDepth.js/blob/master/src/GSVPanoDepth.js
var importer = require('../Core')
var {request} = importer.import("http request")


async function loadDepthMap(panoId, onDepthLoad) {
    console.log("Getting depth map for panoid:", panoId)
    var url

    // This url no longer works
    // url = "http://maps.google.com/cbk?output=json&cb_client=maps_sv&v=4&dm=1&pm=1&ph=1&hl=en&panoid=" + panoId;

    url = "https://www.google.com/maps/photometa/v1?authuser=0&hl=en&gl=uk&pb=!1m4!1smaps_sv.tactile!11m2!2m1!1b1!2m2!1sen!2suk!3m3!1m2!1e2!2s" +
        panoId +
        "!4m57!1e1!1e2!1e3!1e4!1e5!1e6!1e8!1e12!2m1!1e1!4m1!1i48!5m1!1e1!5m1!1e2!6m1!1e1!6m1!1e2!9m36!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!" + 
        "1e3!2b1!3e2!1m3!1e3!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e1!2b0!3e3!1m3!1e4!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e3"
    console.log(url)
    var res = await request({
        method: 'GET',
        url: url
    })
    var dm = JSON.parse(res.body.substr(4))[1][0][5][0][5][1][2]
    var decoded, depthMap;
    try {
        decoded = decodeDepthMap(dm)
        depthMap = parseDepthMap(decoded)
    } catch(e) {
        console.error("Error loading depth map for pano " + panoId + "\n" + e.message + "\nAt " + e.filename + "(" + e.lineNumber + ")");
        depthMap = createEmptyDepthMap()
    }
    if(onDepthLoad) {
        await onDepthLoad(depthMap)
    }
}

function decodeDepthMap(rawDepthMap) {
    var i,
        compressedDepthMapData,
        depthMap,
        decompressedDepthMap

    // Append '=' in order to make the length of the array a multiple of 4
    while(rawDepthMap.length %4 != 0)
        rawDepthMap += '='

    // Replace '-' by '+' and '_' by '/'
    rawDepthMap = rawDepthMap.replace(/-/g,'+')
    rawDepthMap = rawDepthMap.replace(/_/g,'/')

    // Decode and decompress data
    decompressedDepthMap = Buffer.from(rawDepthMap, 'base64').toString('binary')

    // Convert output of decompressor to Uint8Array
    depthMap = new Uint8Array(decompressedDepthMap.length)
    for(i=0; i<decompressedDepthMap.length; ++i)
        depthMap[i] = decompressedDepthMap.charCodeAt(i)
    return depthMap
}

function parseDepthHeader(depthMap) {
    return {
        headerSize : depthMap.getUint8(0),
        numberOfPlanes : depthMap.getUint16(1, true),
        width: depthMap.getUint16(3, true),
        height: depthMap.getUint16(5, true),
        offset: depthMap.getUint16(7, true)
    }
}

function parseDepthPlanes(header, depthMap) {
    var planes = [],
        indices = [],
        i,
        n = [0, 0, 0],
        d,
        byteOffset

    for(i=0; i<header.width*header.height; ++i) {
        indices.push(depthMap.getUint8(header.offset + i))
    }

    for(i=0; i<header.numberOfPlanes; ++i) {
        byteOffset = header.offset + header.width*header.height + i*4*4
        n[0] = depthMap.getFloat32(byteOffset, true)
        n[1] = depthMap.getFloat32(byteOffset + 4, true)
        n[2] = depthMap.getFloat32(byteOffset + 8, true)
        d    = depthMap.getFloat32(byteOffset + 12, true)
        planes.push({
            n: n.slice(0),
            d: d
        })
    }

    return { planes: planes, indices: indices }
}

function computeDepthMap(header, indices, planes) {
    var depthMap = null,
        x, y,
        planeIdx,
        phi, theta,
        v = [0, 0, 0],
        w = header.width, h = header.height,
        plane, t, p

    depthMap = new Float32Array(w*h)

    var sin_theta = new Float32Array(h)
    var cos_theta = new Float32Array(h)
    var sin_phi   = new Float32Array(w)
    var cos_phi   = new Float32Array(w)

    for(y=0; y<h; ++y) {
        theta = (h - y - 0.5) / h * Math.PI
        sin_theta[y] = Math.sin(theta)
        cos_theta[y] = Math.cos(theta)
    }
    for(x=0; x<w; ++x) {
        phi = (w - x - 0.5) / w * 2 * Math.PI + Math.PI/2
        sin_phi[x] = Math.sin(phi)
        cos_phi[x] = Math.cos(phi)
    }

    for(y=0; y<h; ++y) {
        for(x=0; x<w; ++x) {
            planeIdx = indices[y*w + x]

            v[0] = sin_theta[y] * cos_phi[x]
            v[1] = sin_theta[y] * sin_phi[x]
            v[2] = cos_theta[y]

            if(planeIdx > 0) {
                plane = planes[planeIdx]

                t = Math.abs( plane.d / (v[0]*plane.n[0] + v[1]*plane.n[1] + v[2]*plane.n[2]) )
                depthMap[y*w + (w-x-1)] = t
            } else {
                depthMap[y*w + (w-x-1)] = 9999999999999999999.
            }
        }
    }

    return {
        width: w,
        height: h,
        depthMap: depthMap
    }
}

function parseDepthMap(depthMap) {
    var depthMapData,
        header,
        data,
        depthMap

    depthMapData = new DataView(depthMap.buffer)
    header = parseDepthHeader(depthMapData)
    data = parseDepthPlanes(header, depthMapData)
    depthMap = computeDepthMap(header, data.indices, data.planes)

    return depthMap
}

function createEmptyDepthMap() {
    var depthMap = {
        width: 512,
        height: 256,
        depthMap: new Float32Array(512*256)
    }
    for(var i=0; i<512*256; ++i)
        depthMap.depthMap[i] = 9999999999999999999.
    return depthMap
}

module.exports = {
    loadDepthMap
}

What the code could have been:

const { request } = require('../Core/importer');
const debug = require('debug')('gsvpandepth');

const DEPTH_MAP_URL = 'https://www.google.com/maps/photometa/v1';

async function loadDepthMap(panoId, onDepthLoad) {
  debug(`Getting depth map for panoid: ${panoId}`);

  const url = `${DEPTH_MAP_URL}?authuser=0&hl=en&gl=uk&panoid=${panoId}`;
  const res = await request({ method: 'GET', url });
  const dm = JSON.parse(res.body.substr(4))[1][0][5][0][5][1][2];

  try {
    const decoded = decodeDepthMap(dm);
    const depthMap = parseDepthMap(decoded);
    if (onDepthLoad) await onDepthLoad(depthMap);
    return depthMap;
  } catch (e) {
    console.error(`Error loading depth map for pano ${panoId}\n${e.message}\nAt ${e.filename}(${e.lineNumber})`);
    const depthMap = createEmptyDepthMap();
    if (onDepthLoad) await onDepthLoad(depthMap);
    return depthMap;
  }
}

function decodeDepthMap(rawDepthMap) {
  // Append '=' in order to make the length of the array a multiple of 4
  while (rawDepthMap.length % 4!== 0) rawDepthMap += '=';

  // Replace '-' by '+' and '_' by '/'
  rawDepthMap = rawDepthMap.replace(/-/g, '+');
  rawDepthMap = rawDepthMap.replace(/_/g, '/');

  // Decode and decompress data
  const decompressedDepthMap = Buffer.from(rawDepthMap, 'base64').toString('binary');

  return decompressedDepthMap;
}

function parseDepthHeader(depthMap) {
  const headerSize = depthMap.getUint8(0);
  const numberOfPlanes = depthMap.getUint16(1, true);
  const width = depthMap.getUint16(3, true);
  const height = depthMap.getUint16(5, true);
  const offset = depthMap.getUint16(7, true);

  return { headerSize, numberOfPlanes, width, height, offset };
}

function parseDepthPlanes(header, depthMap) {
  const planes = [];
  const indices = [];
  const n = [0, 0, 0];
  const byteOffset = 0;

  for (let i = 0; i < header.width * header.height; ++i) {
    indices.push(depthMap.getUint8(header.offset + i));
  }

  for (let i = 0; i < header.numberOfPlanes; ++i) {
    byteOffset += header.width * header.height + i * 4 * 4;

    n[0] = depthMap.getFloat32(byteOffset, true);
    n[1] = depthMap.getFloat32(byteOffset + 4, true);
    n[2] = depthMap.getFloat32(byteOffset + 8, true);
    const d = depthMap.getFloat32(byteOffset + 12, true);

    planes.push({ n: n.slice(0), d });
  }

  return { planes, indices };
}

function computeDepthMap(header, indices, planes) {
  const depthMap = new Float32Array(header.width * header.height);
  const sinTheta = new Float32Array(header.height);
  const cosTheta = new Float32Array(header.height);
  const sinPhi = new Float32Array(header.width);
  const cosPhi = new Float32Array(header.width);

  for (let y = 0; y < header.height; ++y) {
    const theta = (header.height - y - 0.5) / header.height * Math.PI;
    sinTheta[y] = Math.sin(theta);
    cosTheta[y] = Math.cos(theta);
  }
  for (let x = 0; x < header.width; ++x) {
    const phi = (header.width - x - 0.5) / header.width * 2 * Math.PI + Math.PI / 2;
    sinPhi[x] = Math.sin(phi);
    cosPhi[x] = Math.cos(phi);
  }

  for (let y = 0; y < header.height; ++y) {
    for (let x = 0; x < header.width; ++x) {
      const planeIdx = indices[y * header.width + x];

      const v = [
        sinTheta[y] * cosPhi[x],
        sinTheta[y] * sinPhi[x],
        cosTheta[y],
      ];

      if (planeIdx > 0) {
        const plane = planes[planeIdx];

        const t = Math.abs(plane.d / (v[0] * plane.n[0] + v[1] * plane.n[1] + v[2] * plane.n[2]));
        depthMap[y * header.width + (header.width - x - 1)] = t;
      } else {
        depthMap[y * header.width + (header.width - x - 1)] = 9999999999999999999;
      }
    }
  }

  return { width: header.width, height: header.height, depthMap };
}

function parseDepthMap(depthMap) {
  const depthMapData = new DataView(depthMap.buffer);
  const header = parseDepthHeader(depthMapData);
  const data = parseDepthPlanes(header, depthMapData);
  const depthMap = computeDepthMap(header, data.indices, data.planes);

  return depthMap;
}

function createEmptyDepthMap() {
  const depthMap = {
    width: 512,
    height: 256,
    depthMap: new Float32Array(512 * 256),
  };
  for (let i = 0; i < 512 * 256; ++i) {
    depthMap.depthMap[i] = 9999999999999999999;
  }
  return depthMap;
}

module.exports = { loadDepthMap };

This code snippet defines an asynchronous function loadDepthMap that retrieves and processes depth map data for a given Google Street View panorama ID.

Here's a breakdown:

  1. Imports:

  2. loadDepthMap Function:

Purpose:

This function retrieves and processes depth map data for a Google Street View panorama, likely used for applications that require 3D information about the environment captured in the panorama.