npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nakasyou/google-earth-internal-api-client

v0.1.1

Published

Typed client for Google Earth RockTree meshes and Earth place search

Readme

@nakasyou/google-earth-internal-api-client

An unofficial TypeScript SDK for Google Earth RockTree geometry and Earth place search. Runs on Node.js 22+ and Bun. No account, API key, cookie, or login token was used in the recorded live verification.

Install

npm install @nakasyou/google-earth-internal-api-client
# or
bun add @nakasyou/google-earth-internal-api-client

Search, meshes, and building labels

import { EarthClient, transformPositions, toObj } from '@nakasyou/google-earth-internal-api-client'

const earth = new EarthClient({ language: 'ja' })
const search = await earth.search('東京タワー')
const tower = search.places[0]
if (!tower) throw new Error('Place not found')

const result = await earth.getMeshesAt(tower, { level: 18 })
for (const tile of result.tiles) {
  for (const mesh of tile.meshes) {
    console.log(mesh.positions, mesh.indices, mesh.uv, mesh.textures)
    const ecef = transformPositions(mesh.positions, tile.matrixGlobeFromMesh)
    console.log(ecef) // Float64Array of Earth-centred XYZ coordinates in metres
  }
  const obj = toObj(tile)
  // Save obj with your filesystem API.
}

const labels = await earth.getBuildingLabels({
  query: '東京タワー',
  bounds: { north: 35.67, south: 35.65, east: 139.76, west: 139.73 },
})
// [{ text, latitude, longitude, placeId, address, source: 'earth-search' }]

getBuildingLabels returns place-name label anchors from an explicit building/landmark search. It does not enumerate all buildings in a viewport, classify each result as a building, or attach a name to each mesh. RockTree tiles are photogrammetric surfaces; meshes do not supply a building-name-to-triangle mapping. The optional bounds strictly filter returned anchors, including bounds crossing the antimeridian. Search location and span only bias server ranking.

API

| Method | Result | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | getPlanetoid(options?) | Radius and current root metadata epoch | | getBulk(path, epoch, options?) | Four levels of node metadata; bulk path length must be a multiple of four | | getNodeMetadata(path, options?) | Resolve the current node epoch and flags by traversing metadata | | findNodes({latitude, longitude}, {level?, maxNodes?, signal?}) | All available altitude branches at the requested horizontal location and exact level | | getMesh(path, {metadata?, textureFormat?, signal?}) | Decode one mesh tile; metadata may be supplied from findNodes | | getMeshesAt(point, options?) | Resolve and fetch the available mesh tiles at one point | | search(query, options?) | Place names, IDs, coordinates, address, optional phone/website/description | | getBuildingLabels({query, bounds?, ...searchOptions}) | Place label anchors for a building query | | clearCache() | Drop cached raw successful responses |

Exported helpers: decodeMeshTile, transformPositions, triangleStripToTriangles, toObj.

Geometry

  • Positions are interleaved local XYZ (Float32Array). Use the column-major matrixGlobeFromMesh (Float64Array, 16 elements) to obtain Earth-centred XYZ metres. For WebGL rendering, subtract a nearby ECEF origin before converting back to float32.
  • Indices are a triangle list (Uint32Array), with degenerate strip triangles removed and alternating strip winding corrected. The original triangle strip and layer ranges are also retained.
  • The triangle list includes layers 0–2 (overground and visible terrain). Hidden terrain, water, skirts, and overlay surfaces are not merged into it.
  • UVs use a bottom-left convention. Each texture includes its format, width, height, and original encoded byte blocks. JPEG is selected when available; otherwise CRN-DXT1 bytes are returned. Set textureFormat: 1 to explicitly request JPEG. CRN texture decompression and stored normal-vector decoding are not implemented; renderers can compute vertex normals from geometry.
  • toObj exports geometry and UVs. It does not create materials or write files. The live example also writes JPEGs and MTL files, using a shared ECEF origin for all tiles.
  • level defaults to 18 and supports 2–22. No silent lower-detail fallback occurs. A point query covers its horizontal octant and available altitude branches, not the whole footprint of a named building or surrounding city. maxNodes defaults to 32 (up to 256); there is no unbounded region crawler.
  • The response preserves copyrightIds; it does not interpret them as building IDs.

Network controls and errors

const earth = new EarthClient({
  timeoutMs: 20_000,
  maxResponseBytes: 32 * 1024 * 1024,
  cacheEntries: 32,
  cacheTtlMs: 60_000,
  language: 'en',
  fetch, // optional injection, useful for tests or a server-side proxy
})
const controller = new AbortController()
const request = earth.search('Eiffel Tower', { signal: controller.signal })
controller.abort()
await request // rejects with cancellation

Requests omit browser credentials. Response-size limits apply while streaming, even without a Content-Length header. Only successful raw metadata/geometry responses enter the bounded LRU cache; search results are not cached. Fetches are sequential within point lookup/download. No automatic retry is performed. HttpError exposes status and url; ProtocolError identifies malformed or unsupported response formats; NoDataError identifies absent geometry. Invalid caller input raises RangeError.

Endpoint overrides: rockTreeUrl (default https://kh.google.com/rt/earth/) and searchUrl (default https://www.google.com/earth/rpc/search). Browser CORS, upstream behavior, availability and schema compatibility are independent of the SDK. Live verification used Bun; built ESM imports were also checked under Node. This is an undocumented protocol client, not an official Google SDK or a promise of continued endpoint availability.

Development and verification

bun install --frozen-lockfile
bun run test          # deterministic tests; no network
bun run check         # formatting, lint, TypeScript
bun run build         # Vite+ bundle and declarations
bun run test:live     # real network, writes artifacts/
npm pack --dry-run

Live test: Tokyo Tower search, Japanese label and coordinate checks, RockTree epoch traversal, three level-18 tiles, 14,474 vertices and 9,806 non-degenerate triangles. All twelve requests returned HTTP 200 without credentials in the initial verification. Response epochs and geometry counts can change. The live example validates index bounds, finite Earth-centred coordinates, UV lengths and JPEG signatures; it writes timestamped artifacts/live-verification.json, OBJ/MTL files and textures. These fetched assets are excluded from the npm package.

The deterministic suite covers packed vertex/UV/index decoding, layer boundaries, winding, transforms, malformed protobuf/XML, search parsing and Unicode, label filtering, no-data traversal, cache behavior, HTTP errors, response limits, timeout, cancellation and geographic boundaries.

Protocol evidence

The local Google Earth Pro 7.3.7.1327-r0 binaries were analyzed in Ghidra 12.0.4. Relevant functions include RockNode::GetRequestUrl, PlanetoidMetadataEntry::BuildFullUrl, GoogleSearch and GeocodeSearchQuery::AddCustomQueryParameters. output=xml, prune=earth, ui=earth, and view=teaser produced the verified search response; output=kml did not.

RockTree wire-field numbers and packed-data formats were cross-checked against retroplasma's protocol research, in particular its protocol definitions and decoder description. This SDK contains its own typed implementation rather than the repository's embedded/minified client runtime.

Publishing

The public repository is nakasyou/google-earth-internal-api-client. Run the manual publish.yml GitHub Actions workflow with major, minor, or patch to release from the latest npm version. The npm trusted publisher is configured for GitHub owner nakasyou, repository google-earth-internal-api-client, and workflow publish.yml, with direct npm publish allowed. It uses GitHub-hosted runners and OIDC; no npm publishing token is stored in the repository. The release commit, tag and GitHub Release are pushed only after npm publication succeeds.

Tests live alongside the source in src/*.test.ts. Run bun run test for deterministic offline tests.