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 / generate / sources / piggyback / capture.go
capture.go 3.8K
1package piggyback23import "sort"45// The archived capture, as the crawler wrote it: the map's settings, the6// category tree, every pin, and the tile windows the crawler observed. Piggyback7// publishes no bounds of its own, so the crawler's survey is the only account of8// where the pyramid is drawn. Fields marshal in declaration order and every list9// is sorted by normalize, so unchanged data always hashes to the capture already10// archived.1112type capture struct {13 Source string `json:"source"`14 GameSlug string `json:"gameSlug"`15 MapSlug string `json:"mapSlug"`16 GameTitle string `json:"gameTitle"`17 MapTitle string `json:"mapTitle"`18 Map sheet `json:"map"`19 Labels labels `json:"labels"`20 Categories []category `json:"categories"`21 Pins []pin `json:"pins"`22 Levels []level `json:"levels"`23}2425// sheet keeps the settings the page ships and the transformation the map's own26// scripts project through. MaxZoom is the deepest level actually captured; the27// premium ceiling rides along as a matter of record.28type sheet struct {29 TileServer string `json:"tileServer"`30 MinZoom int `json:"minZoom"`31 MaxZoom int `json:"maxZoom"`32 PremiumMaxZoom int `json:"premiumMaxZoom,omitempty"`33 Transform Transform `json:"transform"`34}3536// Transform is Leaflet's affine pair: a point (x, y) in game coordinates draws37// at (a*x + b, c*y + d) on the unit tile at zoom zero. It is exported because38// the verified-transform gate is a table of these and a reader of that table39// needs to see what it is comparing.40type Transform struct {41 A float64 `json:"a"`42 B float64 `json:"b"`43 C float64 `json:"c"`44 D float64 `json:"d"`45}4647// labels carries the display names the page's language bundle gives the category48// and type keys, which the data API never repeats.49type labels struct {50 Categories []label `json:"categories"`51 Types []label `json:"types"`52}5354type label struct {55 Key string `json:"key"`56 Label string `json:"label"`57}5859type category struct {60 ID string `json:"id"`61 Key string `json:"key"`62 Position float64 `json:"position"`63 Types []kind `json:"types"`64}6566type kind struct {67 ID string `json:"id"`68 Key string `json:"key"`69 Position float64 `json:"position"`70}7172// pin is one pin as Piggyback serves it, coordinates included as the strings73// they arrive in.74type pin struct {75 ID string `json:"id"`76 X string `json:"x"`77 Y string `json:"y"`78 CategoryKey string `json:"categoryKey"`79 TypeKey string `json:"typeKey"`80 Name string `json:"name"`81 Description string `json:"description,omitempty"`82}8384// level is one observed tile window: at Zoom, tiles exist from MinX,MinY through85// MaxX,MaxY inclusive.86type level struct {87 Zoom int `json:"zoom"`88 MinX int `json:"minX"`89 MinY int `json:"minY"`90 MaxX int `json:"maxX"`91 MaxY int `json:"maxY"`92}9394func (c *capture) normalize() {95 sort.Slice(c.Labels.Categories, func(a, b int) bool {96 return c.Labels.Categories[a].Key < c.Labels.Categories[b].Key97 })98 sort.Slice(c.Labels.Types, func(a, b int) bool {99 return c.Labels.Types[a].Key < c.Labels.Types[b].Key100 })101 sort.SliceStable(c.Categories, func(a, b int) bool {102 if c.Categories[a].Position != c.Categories[b].Position {103 return c.Categories[a].Position < c.Categories[b].Position104 }105 return c.Categories[a].Key < c.Categories[b].Key106 })107 for index := range c.Categories {108 types := c.Categories[index].Types109 sort.SliceStable(types, func(a, b int) bool {110 if types[a].Position != types[b].Position {111 return types[a].Position < types[b].Position112 }113 return types[a].Key < types[b].Key114 })115 }116 sort.Slice(c.Pins, func(a, b int) bool { return c.Pins[a].ID < c.Pins[b].ID })117 sort.Slice(c.Levels, func(a, b int) bool { return c.Levels[a].Zoom < c.Levels[b].Zoom })118}