clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / app / cells / carry.go
carry.go 6.7K
1package cells23import "math"45// Carrying a place across systems.6//7// The two hierarchies share no boundaries -- a geohash rectangle and an S28// quadrilateral agree nowhere except by accident -- so nothing about an address9// survives the crossing. What survives is the *place*: the point the reader was10// looking at, and how closely they were looking at it. [Equivalent] is that11// sentence as arithmetic, and it is the seam's `equivalentCell`12// (analysis/cellsystems/registry.ts) rather than a second idea of the same13// thing: the shared carry vectors in analysis/testdata/cells hold this14// function to the answers the TypeScript lane gives.15//16// WHY THE SERVER CARRIES AT ALL. Cycling the system is a session write like any17// other, and the record it writes has to name a cell the *server* can narrow by18// -- the count above the panel is the count of what the map is drawing19// (docs/render-seam.md §4.2), and a cell carried by the seam alone would be a20// cell the server never heard of. The seam draws the same crossing from the21// same recorded oracle, which is what keeps the two answers one answer.2223// bound is one system standing on one ground: the four questions the carry24// asks of a hierarchy, and the floor it stops at. It exists because the carry25// is written once and walks both directions -- the alternative is the same loop26// twice with the two systems' names swapped through it.27type bound interface {28 // level is the depth of an address, the root counting 0.29 level(id string) int30 // floor is how deep this system's telescope goes on this ground.31 floor() int32 // descend is the child of an address holding a point, or nothing at the33 // floor.34 descend(id string, x, y float64) string35 // extent is an address's rectangle in world pixels.36 extent(id string) Extent37 // center is a point inside an address, in world pixels.38 center(id string) (x, y float64)39}4041// Equivalent carries a held cell from one system to another: the cell of the42// new hierarchy holding the old cell's centre, taken to the depth whose cells43// cover closest to the same ground.44//45// Area is compared on a LOG scale, because levels grow geometrically and46// "closest" should mean closest in kind rather than closest in pixels: a level47// four times too coarse and a level four times too fine are equally wrong, and48// on a linear scale the coarse one would win every time.49//50// The root carries to the root, which is the only address the two hierarchies51// spell the same way. An empty answer is a carry that could not be made -- a52// system this world does not divide, or a centre off the surface -- and the53// caller's own answer to that is to hold no cell rather than to hold a wrong54// one.55func Equivalent(ground Extent, attrs map[string]string, from, to, id string) string {56 if id == "" {57 return ""58 }59 source, standing := boundTo(from, ground, attrs)60 target, divides := boundTo(to, ground, attrs)61 if !standing || !divides {62 return ""63 }64 x, y := source.center(id)65 area := areaOf(source.extent(id))66 held, best := "", ""67 fit := math.Inf(1)68 for target.level(held) < target.floor() {69 next := target.descend(held, x, y)70 if next == "" {71 break72 }73 nextArea := areaOf(target.extent(next))74 nextFit := math.Abs(math.Log(nextArea / area))75 if nextFit < fit {76 best, fit = next, nextFit77 }78 // Areas only shrink on the way down: past the target, deeper is worse.79 if nextArea <= area {80 break81 }82 held = next83 }84 return best85}8687// boundTo stands a system on a ground, or refuses one that does not divide it.88//89// The geohash the carry walks is the ground's own mode: real on an90// earth-anchored plane, the halving of the rectangle it was handed91// everywhere else. The rectangle is the caller's reading of the world —92// today every carry the application can make is on a sphere, where the93// surface ladder is the geohash ground too — and the anchored check is here94// so the day a second system divides a plane, the carry does not quietly95// halve a picture whose addresses are the planet's.96func boundTo(system string, ground Extent, attrs map[string]string) (bound, bool) {97 switch system {98 case SystemGeohash:99 if mapping, anchored := EarthMercator(attrs); anchored {100 return realGeohashOn{mapping: mapping, base: GeohashBaseDepth(mapping)}, true101 }102 return geohashOn{ground: ground}, true103 case SystemS2:104 mapping, mapped := MappingOf(attrs)105 if !mapped || !Applicable(attrs, SystemS2) {106 return nil, false107 }108 return s2On{ground: ground, mapping: mapping}, true109 }110 return nil, false111}112113// areaOf is a rectangle's ground in world pixels squared. Both hierarchies are114// measured this way and only this way; see [S2Extent] for why a spherical area115// would not do.116func areaOf(e Extent) float64 {117 return (e.MaxX - e.MinX) * (e.MaxY - e.MinY)118}119120type geohashOn struct{ ground Extent }121122func (g geohashOn) level(id string) int { return len(id) }123124func (g geohashOn) floor() int { return GeohashMaxDepth }125126func (g geohashOn) descend(id string, x, y float64) string {127 return GeohashCellAt(g.ground, x, y, len(id)+1)128}129130func (g geohashOn) extent(id string) Extent { return GeohashExtent(g.ground, id) }131132func (g geohashOn) center(id string) (x, y float64) {133 held := GeohashExtent(g.ground, id)134 return (held.MinX + held.MaxX) / 2, (held.MinY + held.MaxY) / 2135}136137// realGeohashOn is geohash on an earth-anchored plane: level counts LAYERS —138// the root's children are base-depth cells — and the floor is still three of139// them, exactly as the seam's real mode counts (analysis/cellsystems/140// geohash.ts, `realGrounded`).141type realGeohashOn struct {142 mapping Mercator143 base int144}145146func (g realGeohashOn) level(id string) int {147 if id == "" {148 return 0149 }150 return len(id) - g.base + 1151}152153func (g realGeohashOn) floor() int { return 3 }154155func (g realGeohashOn) descend(id string, x, y float64) string {156 depth := g.base157 if id != "" {158 depth = len(id) + 1159 }160 return GeohashRealCellAt(g.mapping, x, y, depth)161}162163func (g realGeohashOn) extent(id string) Extent {164 return GeohashRealExtent(g.mapping, id)165}166167func (g realGeohashOn) center(id string) (x, y float64) {168 held := GeohashRealExtent(g.mapping, id)169 return (held.MinX + held.MaxX) / 2, (held.MinY + held.MaxY) / 2170}171172type s2On struct {173 ground Extent174 mapping Mapping175}176177func (s s2On) level(id string) int { return S2Level(id) }178179// The floor is the port's, not the library's: the deepest cell counted in a180// bookkeeping where the root is a place (see [S2Level]).181func (s s2On) floor() int { return deepestS2Level + 1 }182183func (s s2On) descend(id string, x, y float64) string {184 return S2Descend(s.mapping, id, x, y)185}186187func (s s2On) extent(id string) Extent {188 if id == "" {189 return s.ground190 }191 return S2Extent(s.mapping, s.ground, id)192}193194func (s s2On) center(id string) (x, y float64) { return S2Center(s.mapping, s.ground, id) }