1package geojson 2 3import ( 4 "github.com/tidwall/gjson" 5 "github.com/tidwall/tile38/geojson/geohash" 6) 7 8// FeatureCollection is a geojson object with the type "FeatureCollection" 9type FeatureCollection struct { 10 Features []Object 11 BBox *BBox 12 bboxDefined bool 13} 14 15func fillFeatureCollectionMap(json string) (FeatureCollection, error) { 16 var g FeatureCollection 17 res := gjson.Get(json, "features") 18 switch res.Type { 19 default: 20 return g, errInvalidFeaturesMember 21 case gjson.Null: 22 return g, errFeaturesMemberRequired 23 case gjson.JSON: 24 if !resIsArray(res) { 25 return g, errInvalidFeaturesMember 26 } 27 v := res.Array() 28 g.Features = make([]Object, len(v)) 29 for i, res := range v { 30 if res.Type != gjson.JSON { 31 return g, errInvalidFeature 32 } 33 o, err := objectMap(res.Raw, fcoll) 34 if err != nil { 35 return g, err 36 } 37 g.Features[i] = o 38 } 39 } 40 var err error 41 g.BBox, err = fillBBox(json) 42 if err != nil { 43 return g, err 44 } 45 g.bboxDefined = g.BBox != nil 46 if !g.bboxDefined { 47 cbbox := g.CalculatedBBox() 48 g.BBox = &cbbox 49 } 50 return g, err 51} 52 53// Geohash converts the object to a geohash value. 54func (g FeatureCollection) Geohash(precision int) (string, error) { 55 p := g.CalculatedPoint() 56 return geohash.Encode(p.Y, p.X, precision) 57} 58 59// CalculatedPoint is a point representation of the object. 60func (g FeatureCollection) CalculatedPoint() Position { 61 return g.CalculatedBBox().center() 62} 63 64// CalculatedBBox is exterior bbox containing the object. 65func (g FeatureCollection) CalculatedBBox() BBox { 66 if g.BBox != nil { 67 return *g.BBox 68 } 69 var bbox BBox 70 for i, g := range g.Features { 71 if i == 0 { 72 bbox = g.CalculatedBBox() 73 } else { 74 bbox = bbox.union(g.CalculatedBBox()) 75 } 76 } 77 return bbox 78} 79 80// PositionCount return the number of coordinates. 81func (g FeatureCollection) PositionCount() int { 82 var res int 83 for _, g := range g.Features { 84 res += g.PositionCount() 85 } 86 if g.BBox != nil { 87 return 2 + res 88 } 89 return res 90} 91 92// Weight returns the in-memory size of the object. 93func (g FeatureCollection) Weight() int { 94 var res int 95 for _, g := range g.Features { 96 res += g.Weight() 97 } 98 return res 99} 100 101// MarshalJSON allows the object to be encoded in json.Marshal calls. 102func (g FeatureCollection) MarshalJSON() ([]byte, error) { 103 return g.appendJSON(nil), nil 104} 105 106func (g FeatureCollection) appendJSON(json []byte) []byte { 107 json = append(json, `{"type":"FeatureCollection","features":[`...) 108 for i, g := range g.Features { 109 if i != 0 { 110 json = append(json, ',') 111 } 112 json = append(json, g.JSON()...) 113 } 114 json = append(json, ']') 115 if g.bboxDefined { 116 json = appendBBoxJSON(json, g.BBox) 117 } 118 return append(json, '}') 119} 120 121// JSON is the json representation of the object. This might not be exactly the same as the original. 122func (g FeatureCollection) JSON() string { 123 return string(g.appendJSON(nil)) 124} 125 126// String returns a string representation of the object. This might be JSON or something else. 127func (g FeatureCollection) String() string { 128 return g.JSON() 129} 130 131// Bytes is the bytes representation of the object. 132func (g FeatureCollection) Bytes() []byte { 133 return []byte(g.JSON()) 134} 135func (g FeatureCollection) bboxPtr() *BBox { 136 return g.BBox 137} 138func (g FeatureCollection) hasPositions() bool { 139 if g.BBox != nil { 140 return true 141 } 142 for _, g := range g.Features { 143 if g.hasPositions() { 144 return true 145 } 146 } 147 return false 148} 149 150// WithinBBox detects if the object is fully contained inside a bbox. 151func (g FeatureCollection) WithinBBox(bbox BBox) bool { 152 if g.bboxDefined { 153 return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox)) 154 } 155 if len(g.Features) == 0 { 156 return false 157 } 158 for _, g := range g.Features { 159 if !g.WithinBBox(bbox) { 160 return false 161 } 162 } 163 return true 164} 165 166// IntersectsBBox detects if the object intersects a bbox. 167func (g FeatureCollection) IntersectsBBox(bbox BBox) bool { 168 if g.bboxDefined { 169 return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox)) 170 } 171 for _, g := range g.Features { 172 if g.IntersectsBBox(bbox) { 173 return true 174 } 175 } 176 return false 177} 178 179// Within detects if the object is fully contained inside another object. 180func (g FeatureCollection) Within(o Object) bool { 181 return withinObjectShared(g, o, 182 func(v Polygon) bool { 183 if len(g.Features) == 0 { 184 return false 185 } 186 for _, f := range g.Features { 187 if !f.Within(o) { 188 return false 189 } 190 } 191 return true 192 }, 193 ) 194} 195 196// Intersects detects if the object intersects another object. 197func (g FeatureCollection) Intersects(o Object) bool { 198 return intersectsObjectShared(g, o, 199 func(v Polygon) bool { 200 if len(g.Features) == 0 { 201 return false 202 } 203 for _, f := range g.Features { 204 205 if f.Intersects(o) { 206 return true 207 } 208 } 209 return false 210 }, 211 ) 212} 213 214// Nearby detects if the object is nearby a position. 215func (g FeatureCollection) Nearby(center Position, meters float64) bool { 216 return nearbyObjectShared(g, center.X, center.Y, meters) 217} 218 219// IsBBoxDefined returns true if the object has a defined bbox. 220func (g FeatureCollection) IsBBoxDefined() bool { 221 return g.bboxDefined 222} 223 224// IsGeometry return true if the object is a geojson geometry object. false if it something else. 225func (g FeatureCollection) IsGeometry() bool { 226 return true 227} 228