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