1package arcgishub23import "sort"45// The archived capture, as the crawler wrote it. A city's capture is one day of6// one open-data hub: the window the city's ground is pictured in, the basemap7// pyramid rendered beside it from that same vector data, and every curated8// dataset's features under their allowlisted fields. Its fields are declared in9// the order they marshal and normalize sorts its lists, so a hub listing its10// rows differently does not masquerade as a new day.1112type capture struct {13 Source string `json:"source"`14 City string `json:"city"`15 Title string `json:"title"`16 MapSlug string `json:"mapSlug"`17 Window window `json:"window"`18 Basemap mapConfig `json:"basemap"`19 Datasets []capturedDataset `json:"datasets"`20}2122// mapConfig is the basemap pyramid as it was rendered: the deepest level drawn,23// and the extension every tile wears. Nothing here is a promise about what a24// bundle serves -- the deriver decides that -- it is what the renderer was asked25// for, which is the only thing the capture can honestly say.26type mapConfig struct {27 MaxZoom int `json:"maxZoom"`28 Extension string `json:"extension"`29}3031// capturedDataset is one curated layer's rows, joined back to the curation32// table by slug when the capture is read.33type capturedDataset struct {34 Slug string `json:"slug"`35 Features []feature `json:"features"`36}3738// feature is one thing the city published: the row identity the layer numbers39// it by, its kept fields spelled as text, and its ground in true degrees.40type feature struct {41 ID int64 `json:"id"`42 Fields fields `json:"fields,omitempty"`43 Geometry geometry `json:"geometry"`44}4546// fields is one row's allowlisted attributes, every value spelled as text.47// Curation reads a row through this and nothing else, which is what keeps a48// field name a reviewed decision rather than a guess made at translate time.49type fields map[string]string5051// geometry is a feature's ground, normalized at capture to one of three shapes:52// a point, lines in MultiLineString nesting, or rings in MultiPolygon nesting.53// Positions are [longitude, latitude] in WGS84.54type geometry struct {55 Type string `json:"type"`56 Point []float64 `json:"point,omitempty"`57 Lines [][][]float64 `json:"lines,omitempty"`58 Rings [][][][]float64 `json:"rings,omitempty"`59}6061// The three shapes a captured geometry may declare.62const (63 geometryPoint = "point"64 geometryLines = "lines"65 geometryRings = "rings"66)6768// normalize puts a capture into its canonical order. The crawler does this69// before archiving; the reader does it again because an archive holds captures70// older than the habit.71func (c *capture) normalize() {72 sort.Slice(c.Datasets, func(a, b int) bool { return c.Datasets[a].Slug < c.Datasets[b].Slug })73 for _, dataset := range c.Datasets {74 rows := dataset.Features75 sort.Slice(rows, func(a, b int) bool { return rows[a].ID < rows[b].ID })76 }77}