Skip to contents

A link is an analysis input: master-plan D8 puts places in the hash with a versioned binary codec, and every analysis runs on the decoded geometry, so a link reproduces its numbers rather than approximating them. R needs to read those links, which is why this is the twin of the app's placeCodec.ts rather than a second design.

Usage

place_encode(x, unwrap = TRUE)

place_encode_strict(x)

place_decode(hash)

Arguments

x

a place, or a list of places. A place is a list with kind: "geom" (name, geometry, optional precision), "zone" (set, keys) or "upload" (name, digest).

unwrap

normalize each geometry with unwrap_polygon() before encoding (TRUE, the default). FALSE is the strict byte-level behaviour.

hash

the hash value, with or without a leading #

Value

place_encode() a hash string; place_decode() a list of places, each with kind and, for "geom", geometry as an sfc plus precision

Details

place_encode_strict() is the BYTE-LEVEL encoder, the twin of the app's encodeGeometry(). It refuses a ring that still has a consecutive-vertex step of more than 180 degrees, with code wrapped — because such a ring is ambiguous, and a codec that quietly repaired it would make the byte stream depend on which language wrote it. place_encode() is the high-level entry: it runs unwrap_polygon() first and then calls this, exactly as a TypeScript caller goes normalizeForAnalysis() then encodeGeometry(). So the same high-level call yields the same token on both sides, and the same low-level call yields the same error.

place_encode(unwrap = FALSE) is the strict path for a whole hash.

The format

#pl = place ("~" place)*, each place one of

g1.name.b64url(bytes)

a drawn or uploaded geometry

z.set.key,key

zone keys, set in pa|pl|er|sr — the vintage comes from the RELEASE, never from the token

u.name.sha256_8

an upload too large to carry, naming what to re-upload

name is percent-encoded UTF-8 over A-Za-z0-9_- (so ., ~, , and %, all structural here, cannot survive raw), at most 60 encoded characters.

The bytes are 0x10, precision:u8, npoly:varint, then per polygon nring:varint and per ring npt:varint followed by its vertices as zigzag-varint dlon, dlat in units of 10^-precision. The closing vertex is omitted. The delta cursor starts at (0,0) and runs ACROSS rings and polygons — resetting it per ring is a bug that still decodes, into a different place. Longitudes are stored unwrapped (a Bering polygon runs 170..190), so a decoder needs no antimeridian guess; unwrap_ring() is what puts them that way, and place_encode() applies it.

Precision

3 by default, 4 when the bounding box is under 0.5 degrees, so the deviation is at most 0.5 * 10^-precision — about 55 m at precision 3 and 5.5 m at precision 4.

Two entries, one byte stream

place_encode() normalizes then encodes; place_encode_strict() only encodes, and rejects a ring that is still wrapped (code wrapped). See the details below — the split is master-plan D8 addendum ruling 2, after the two languages were found to disagree about the same geometry.

Examples

p <- list(kind = "geom", name = "Gulf box", geometry = sf::st_sfc(sf::st_polygon(
  list(cbind(c(-90, -89, -89, -90, -90), c(27.5, 27.5, 28.5, 28.5, 27.5)))),
  crs = 4326))
(h <- place_encode(p))
#> [1] "g1.Gulf%20box.EAMBAQSf_grYrQPQDwAA0A_PDwA"
place_decode(h)[[1]]$name
#> [1] "Gulf box"