11package main
22
33import (
4+ "bytes"
5+ "encoding/binary"
46 "encoding/json"
57 "fmt"
68 "io"
79 "log"
10+ "math"
811 "net/http"
912 "os"
1013 "slices"
@@ -13,7 +16,6 @@ import (
1316
1417 "github.com/paulmach/orb"
1518 "github.com/paulmach/orb/geojson"
16- "github.com/paulmach/orb/planar"
1719)
1820
1921const activeTravelwaysDownloadURL = `https://hub.arcgis.com/api/download/v1/items/a3631c7664ef4ecb93afb1ea4c12022b/geojson?redirect=false&layers=0&spatialRefId=4326`
@@ -38,13 +40,14 @@ func main() {
3840 }
3941
4042 type priority struct {
43+ Number int
4144 Timeline time.Duration
4245 Deadline time.Time
4346 }
4447 priorities := map [string ]priority {
45- "1" : {12 * time .Hour , endTime .Add (12 * time .Hour )},
46- "2" : {18 * time .Hour , endTime .Add (18 * time .Hour )},
47- "3" : {36 * time .Hour , endTime .Add (36 * time .Hour )},
48+ "1" : {1 , 12 * time .Hour , endTime .Add (12 * time .Hour )},
49+ "2" : {2 , 18 * time .Hour , endTime .Add (18 * time .Hour )},
50+ "3" : {3 , 36 * time .Hour , endTime .Add (36 * time .Hour )},
4851 }
4952
5053 resp , err := http .Get (activeTravelwaysDownloadURL )
@@ -82,13 +85,7 @@ func main() {
8285 }
8386
8487 fc .Features = slices .DeleteFunc (fc .Features , func (f * geojson.Feature ) bool {
85- if f .Properties ["WINT_LOS" ] == nil {
86- return true
87- }
88- if ! isPolygonInsidePolygon (peninsulaPolygon .Geometry ().(orb.Polygon ), f .Geometry ) {
89- return true
90- }
91- return false
88+ return f .Properties ["WINT_LOS" ] == nil
9289 })
9390 for _ , f := range fc .Features {
9491 props := f .Properties
@@ -101,17 +98,16 @@ func main() {
10198 }
10299
103100 f .Properties ["title" ] = props ["LOCATION" ]
104- f .Properties ["priority" ] = priorityNum
101+ f .Properties ["priority" ] = p . Number
105102 f .Properties ["timeline" ] = fmt .Sprintf ("%d hours" , int (p .Timeline .Hours ()))
106103 f .Properties ["deadline" ] = p .Deadline .Format ("Mon 3:04 PM" )
107104 }
108105
109- b , err = json . Marshal ( fc )
110- if err != nil {
106+ var out bytes. Buffer
107+ if err := encodeFeatures ( fc . Features , & out ); err != nil {
111108 log .Fatal (err )
112109 }
113-
114- if err := os .WriteFile ("data.geojson" , b , 0644 ); err != nil {
110+ if err := os .WriteFile ("features.bin" , out .Bytes (), 0644 ); err != nil {
115111 log .Fatal (err )
116112 }
117113
@@ -125,42 +121,87 @@ func main() {
125121 }
126122}
127123
128- func isPolygonInsidePolygon (outer orb.Polygon , inner orb.Geometry ) bool {
129- switch inner := inner .(type ) {
130- case orb.Polygon :
131- // Check if all points of the inner polygon are inside the outer polygon
132- for _ , ring := range inner {
133- for _ , point := range ring {
134- if ! planar .PolygonContains (outer , point ) {
135- return false // If any point is outside, it's not wholly contained
136- }
137- }
124+ func encodeFeatures (features []* geojson.Feature , writer io.Writer ) error {
125+ if len (features ) == 0 {
126+ return fmt .Errorf ("no features" )
127+ }
128+ if len (features ) > math .MaxUint32 {
129+ return fmt .Errorf ("too many features: %d exceeds uint32 capacity" , len (features ))
130+ }
131+
132+ // Write the number of features
133+ if err := binary .Write (writer , binary .LittleEndian , uint32 (len (features ))); err != nil {
134+ return err
135+ }
136+
137+ var baseLon , baseLat float64
138+ first := features [0 ]
139+ switch g := first .Geometry .(type ) {
140+ case orb.LineString :
141+ baseLon , baseLat = g [0 ][0 ], g [0 ][1 ]
142+ case orb.MultiLineString :
143+ baseLon , baseLat = g [0 ][0 ][0 ], g [0 ][0 ][1 ]
144+ default :
145+ return fmt .Errorf ("unknown geometry type: %T" , g )
146+ }
147+
148+ if err := binary .Write (writer , binary .LittleEndian , float64 (baseLon )); err != nil {
149+ return err
150+ }
151+ if err := binary .Write (writer , binary .LittleEndian , float64 (baseLat )); err != nil {
152+ return err
153+ }
154+
155+ for _ , feature := range features {
156+ // Write title length and title
157+ titleBytes := []byte (feature .Properties .MustString ("title" , "" ))
158+ if len (titleBytes ) > 255 {
159+ return fmt .Errorf ("title too long: %s exceeds 255 bytes" , titleBytes )
138160 }
139- case orb.MultiPolygon :
140- // Check if all points of the inner multipolygon are inside the outer polygon
141- for _ , p := range inner {
142- if ! isPolygonInsidePolygon (outer , p ) {
143- return false
144- }
161+ if err := binary .Write (writer , binary .LittleEndian , uint8 (len (titleBytes ))); err != nil {
162+ return err
145163 }
146- case orb.LineString :
147- // Check if all points of the inner line string are inside the outer polygon
148- for _ , point := range inner {
149- if ! planar .PolygonContains (outer , point ) {
150- return false
164+ if _ , err := writer .Write (titleBytes ); err != nil {
165+ return err
166+ }
167+
168+ // Write priority
169+ if err := binary .Write (writer , binary .LittleEndian , uint8 (feature .Properties .MustInt ("priority" , 0 ))); err != nil {
170+ return err
171+ }
172+
173+ var ls orb.LineString
174+ switch g := feature .Geometry .(type ) {
175+ case orb.LineString :
176+ ls = g
177+ case orb.MultiLineString :
178+ for _ , gLS := range g {
179+ ls = append (ls , gLS ... )
151180 }
181+ default :
182+ return fmt .Errorf ("unknown geometry type: %T" , g )
152183 }
153- case orb.MultiLineString :
154- // Check if all points of the inner multi line string are inside the outer polygon
155- for _ , ls := range inner {
156- for _ , point := range ls {
157- if ! planar .PolygonContains (outer , point ) {
158- return false
159- }
184+
185+ if len (ls ) > math .MaxUint16 {
186+ return fmt .Errorf ("too many coordinates: %d exceeds uint16 capacity" , len (ls ))
187+ }
188+
189+ // Write number of coordinates
190+ if err := binary .Write (writer , binary .LittleEndian , uint16 (len (ls ))); err != nil {
191+ return err
192+ }
193+
194+ for _ , coord := range ls {
195+ deltaLon := int32 (math .Round ((coord [0 ] - baseLon ) * 1000000 ))
196+ if err := binary .Write (writer , binary .LittleEndian , deltaLon ); err != nil {
197+ return err
198+ }
199+ deltaLat := int32 (math .Round ((coord [1 ] - baseLat ) * 1000000 ))
200+ if err := binary .Write (writer , binary .LittleEndian , deltaLat ); err != nil {
201+ return err
160202 }
161203 }
162- default :
163- log .Fatalf ("unknown geometry type: %T" , inner )
164204 }
165- return true
205+
206+ return nil
166207}
0 commit comments