@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-clientSearch, 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-majormatrixGlobeFromMesh(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: 1to explicitly request JPEG. CRN texture decompression and stored normal-vector decoding are not implemented; renderers can compute vertex normals from geometry. toObjexports 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.leveldefaults 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.maxNodesdefaults 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 cancellationRequests 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-runLive 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.
