clone

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

feat: curate the nation

Dami committed e3e2eaf
The Colorado water viewer turned out to be a branded window onto
federal datasets -- the USGS Watershed Boundary Dataset and National
Hydrography Dataset, credited by name in its own attribution. So the
generic move is a National table beside the city tables: layers every
US city receives by its own bounding box, with no per-city curation.
Four to start, all water -- watersheds and subwatersheds from the WBD
hierarchy, named streams and waterbodies from the NHD -- each one a
zone a reader can click, the wet ones drawing into the basemap under
the streets.

A dataset now carries exactly one identity: a hub item, or a national
MapServer with a where-clause and, for the WBD layers, a feature id
derived from the hydrologic unit code itself, because the code
survives upstream refreshes where object ids are load artifacts.
AllDatasets is the one seam -- the city's own curation followed by
the nation's -- and the soundness test walks it so a private city
from cities_local.go is held to the same rules, slug collisions with
the national layers included.
commit
e3e2eaff782f4873be4f466b9fe4528b54643c5b
parent
31f0bf53334dac0d7a09e5f93bc5c655b53569c6
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 1.8 of 3, Jev's estimate
3 files changed, +138 -5
Minternal/arcgismap/arcgismap.go23++++++-
Minternal/arcgismap/arcgismap_test.go12+++-
Ainternal/arcgismap/national.go108++++++++++++++++++++++++++++++
M internal/arcgismap/arcgismap.go +21 -2
@@ -88,6 +88,17 @@ type Dataset struct {
8888 // than a lane -- and absent means every feature draws alike.8989 Role string9090 Emphasis func(Fields) float6491+92+ // National enrichment. A dataset with Server set is fetched from a93+ // plain ArcGIS MapServer query endpoint by the city's padded window94+ // rather than from the city's hub, Where narrowing the rows, and its95+ // geometry is cut to the window at capture time. The first kept field96+ // names the layer's row identity and orders the pages; IDOf derives97+ // the capture's stable feature identifier from the kept fields when98+ // the layer's own object ids churn between upstream refreshes.99+ Server string100+ Where string101+ IDOf func(Fields) (int64, bool)91102 }92103 93104 // City is one curated hub. The table is the whole authority: an uncurated
@@ -103,6 +114,13 @@ type City struct {
103114 Datasets []Dataset104115 }105116 117+// AllDatasets is the city's own curation followed by the national118+// enrichment every US city receives by bounding box, in that order, so a119+// city's zones keep their places and the nation's sort after them.120+func (c City) AllDatasets() []Dataset {121+ return append(append(make([]Dataset, 0, len(c.Datasets)+len(National)), c.Datasets...), National...)122+}123+106124 // Cities is every city the source knows how to capture. A city that should107125 // stay out of the repository -- your own, say -- can be registered from a108126 // cities_local.go beside this file: that name is ignored by git, and an
@@ -590,8 +608,9 @@ func curatedOrder(city City, capture *Capture) ([]pairing, error) {
590608 captured[dataset.Slug] = dataset591609 }592610 var out []pairing593- for at := range city.Datasets {594- curated := &city.Datasets[at]611+ curatedAll := city.AllDatasets()612+ for at := range curatedAll {613+ curated := &curatedAll[at]595614 if data, taken := captured[curated.Slug]; taken {596615 out = append(out, pairing{curated, data})597616 delete(captured, curated.Slug)
M internal/arcgismap/arcgismap_test.go +9 -3
A internal/arcgismap/national.go +108 -0
@@ -0,0 +1,108 @@
1+package arcgismap2+3+import (4+ "strconv"5+ "strings"6+)7+8+// The national enrichment. A state GIS viewer is usually a branded window9+// onto federal datasets -- Colorado's water viewer credits the USGS10+// Watershed Boundary Dataset and National Hydrography Dataset by name -- so11+// the generic move is to ask the federal services directly, by any city's12+// own bounding box, and every curated city gains the same layers without a13+// line of per-city curation.14+//15+// Both services are plain ArcGIS MapServer query endpoints on16+// hydro.nationalmap.gov. Field spellings differ per layer -- the WBD layers17+// speak lowercase, the NHD waterbody layer uppercase -- and were verified18+// against the live services, the same discipline every city table keeps.19+20+const (21+ wbdServer = "https://hydro.nationalmap.gov/arcgis/rest/services/wbd/MapServer"22+ nhdServer = "https://hydro.nationalmap.gov/arcgis/rest/services/nhd/MapServer"23+24+ // SlugSubwatersheds names the captured dataset the membership join25+ // reads, so the join and the table cannot drift apart on a spelling.26+ SlugSubwatersheds = "subwatersheds"27+)28+29+// National is the enrichment every curated city receives: the watersheds30+// its ground drains through, in two grains of the USGS hydrologic unit31+// hierarchy, and the named streams and waterbodies that do the draining.32+// A padded city window intersects a handful of hydrologic units and tens33+// of named streams -- national extent, city-sized answers.34+var National = []Dataset{35+ {36+ Slug: "watersheds", Title: "Watersheds",37+ Server: wbdServer, Layer: 5,38+ Geometry: "polygon",39+ Keep: []string{"objectid", "huc10", "name"},40+ IDOf: hucID("huc10"),41+ ZoneOf: hucZone("huc10", "Watershed"),42+ },43+ {44+ Slug: SlugSubwatersheds, Title: "Subwatersheds",45+ Server: wbdServer, Layer: 6,46+ Geometry: "polygon",47+ Keep: []string{"objectid", "huc12", "name"},48+ IDOf: hucID("huc12"),49+ ZoneOf: hucZone("huc12", "Subwatershed"),50+ },51+ {52+ Slug: "streams", Title: "Streams",53+ Server: nhdServer, Layer: 6,54+ Geometry: "line",55+ Keep: []string{"OBJECTID", "gnis_name"},56+ // Named flowlines only: the layer also carries every unnamed ditch57+ // and culvert, which is noise to a reader and no loss to the map.58+ Where: "gnis_name IS NOT NULL",59+ ZoneOf: func(f Fields) ZoneKey {60+ name := strings.TrimSpace(f["gnis_name"])61+ if name == "" {62+ return ZoneKey{}63+ }64+ return ZoneKey{Key: slugify(name), Title: name, Subtitle: "Stream"}65+ },66+ StrokeWidth: 10,67+ Role: "water",68+ },69+ {70+ Slug: "waterbodies", Title: "Waterbodies",71+ Server: nhdServer, Layer: 12,72+ Geometry: "polygon",73+ Keep: []string{"OBJECTID", "GNIS_NAME"},74+ // Every pond draws into the basemap; only the named ones earn a75+ // zone anyone could search for.76+ ZoneOf: func(f Fields) ZoneKey {77+ name := strings.TrimSpace(f["GNIS_NAME"])78+ if name == "" {79+ return ZoneKey{}80+ }81+ return ZoneKey{Key: slugify(name), Title: name, Subtitle: "Waterbody"}82+ },83+ Role: "water",84+ },85+}86+87+// hucID reads the hydrologic unit code as the feature's identity: the code88+// is nationally unique and survives upstream refreshes, where the layer's89+// object ids are load artifacts that churn. A leading zero -- regions one90+// through nine -- drops in the number without costing uniqueness.91+func hucID(field string) func(Fields) (int64, bool) {92+ return func(f Fields) (int64, bool) {93+ code, err := strconv.ParseInt(f[field], 10, 64)94+ return code, err == nil && code > 095+ }96+}97+98+// hucZone folds a WBD layer into one zone per hydrologic unit, titled by99+// the unit's own name with the code kept legible beside the grain.100+func hucZone(field, grain string) func(Fields) ZoneKey {101+ return func(f Fields) ZoneKey {102+ code, name := strings.TrimSpace(f[field]), strings.TrimSpace(f["name"])103+ if code == "" || name == "" {104+ return ZoneKey{}105+ }106+ return ZoneKey{Key: code, Title: name, Subtitle: grain + " · HUC " + code}107+ }108+}