github.com/go-spatial/go-wfs@v0.1.4-0.20190401000911-c9fba2bb5188/data_provider/provider.go (about) 1 /////////////////////////////////////////////////////////////////////////////// 2 // 3 // The MIT License (MIT) 4 // Copyright (c) 2018 Jivan Amara 5 // 6 // Permission is hereby granted, free of charge, to any person obtaining a copy 7 // of this software and associated documentation files (the "Software"), to 8 // deal in the Software without restriction, including without limitation the 9 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 // sell copies of the Software, and to permit persons to whom the Software is 11 // furnished to do so, subject to the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be included in 14 // all copies or substantial portions of the Software. 15 // 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 21 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 22 // USE OR OTHER DEALINGS IN THE SOFTWARE. 23 // 24 /////////////////////////////////////////////////////////////////////////////// 25 26 // jivan project provider.go 27 28 package data_provider 29 30 // Builds upon the tegola Tiler interface to reuse data providers from tegola. 31 // Instantiate by: 32 // p := Provider{Tiler: <my Tiler-based provider>} 33 34 import ( 35 "context" 36 "fmt" 37 "sort" 38 "time" 39 40 "github.com/go-spatial/geom" 41 prv "github.com/go-spatial/tegola/provider" 42 ) 43 44 type BadTimeString struct { 45 msg string 46 } 47 48 func (bts *BadTimeString) Error() string { 49 return bts.msg 50 } 51 52 type EmptyTile struct { 53 extent *geom.Extent 54 srid uint64 55 } 56 57 func (_ EmptyTile) ZXY() (uint, uint, uint) { 58 return 0, 0, 0 59 } 60 61 func (et EmptyTile) Extent() (extent *geom.Extent, srid uint64) { 62 if et.extent == nil { 63 max := 20037508.34 64 et.srid = 3857 65 et.extent = &geom.Extent{-max, -max, max, max} 66 } 67 return et.extent, et.srid 68 } 69 70 func (et EmptyTile) BufferedExtent() (extent *geom.Extent, srid uint64) { 71 if et.extent == nil { 72 max := 20037508.34 73 et.srid = 3857 74 et.extent = &geom.Extent{-max, -max, max, max} 75 } 76 return et.extent, et.srid 77 } 78 79 type ErrDuplicateCollectionName struct { 80 name string 81 } 82 83 func (e ErrDuplicateCollectionName) Error() string { 84 return fmt.Sprintf("collection name '%v' already in use", e.name) 85 } 86 87 type tempCollection struct { 88 lastAccess time.Time 89 featureIds []FeatureId 90 } 91 92 type Provider struct { 93 Tiler prv.Tiler 94 tempCollections map[string]*tempCollection 95 } 96 97 type FeatureId struct { 98 Collection string 99 FeaturePk uint64 100 } 101 102 func parse_time_string(ts string) (t time.Time, err error) { 103 fmtstrings := []string{ 104 "2006-01-02T15:04:05Z-0700", 105 "2006-01-02T15:04:05", 106 "2006-01-02", 107 } 108 109 for _, fmts := range fmtstrings { 110 t, err = time.Parse(fmts, ts) 111 if err == nil { 112 return t, nil 113 } 114 } 115 return time.Time{}, &BadTimeString{msg: fmt.Sprintf("unable to parse time string: '%v'", ts)} 116 } 117 118 // Checks for any intersection of start_time - stop_time period or timestamp value 119 // If the feature has none of these tags, we'll consider it non-intersecting. 120 // If only one of start_time or stop_time is provided, the other will be considered 121 // infitity or negative infinity respectively. 122 func feature_time_intersects_time_filter(f *prv.Feature, start_time_str, stop_time_str, timestamp_str string) (bool, error) { 123 // --- Collect any time parameters from feature's tags 124 // Feature start, feature stop, feature timestamp 125 var fstart_str, fstop_str, fts_str string 126 127 for k, v := range f.Properties { 128 switch k { 129 case "start_time": 130 fstart_str = v.(string) 131 case "stop_time": 132 fstop_str = v.(string) 133 case "timestamp": 134 fts_str = v.(string) 135 } 136 } 137 138 // --- Convert all time strings to time.Time instances 139 var start_time, stop_time, timestamp, fstart, fstop, fts time.Time 140 times := []time.Time{start_time, stop_time, timestamp, fstart, fstop, fts} 141 timestrings := []string{start_time_str, stop_time_str, timestamp_str, fstart_str, fstop_str, fts_str} 142 if len(times) != len(timestrings) { 143 panic("array length mismatch") 144 } 145 var err error 146 for i := 0; i < len(times); i++ { 147 if timestrings[i] == "" { 148 continue 149 } 150 times[i], err = parse_time_string(timestrings[i]) 151 if err != nil { 152 return false, err 153 } 154 } 155 156 // if the feature doesn't have any time data, treat as a match 157 if fstart_str == "" && fstop_str == "" && fts_str == "" { 158 return true, nil 159 } 160 161 // if there's no start_time, but there's a stop_time and feature timestamp before the stop_time 162 if start_time_str == "" && stop_time_str != "" && fts_str != "" && fts.Sub(stop_time) <= 0 { 163 return true, nil 164 } 165 // if there's no start time, but there's a stop time and a feature start and/or stop time 166 if start_time_str == "" && stop_time_str != "" && (fstart_str != "" || fstop_str != "") { 167 if fstart_str != "" && fstart.Sub(stop_time) <= 0 { 168 return true, nil 169 } 170 if fstop_str != "" && fstop.Sub(stop_time) <= 0 { 171 return true, nil 172 } 173 } 174 // If there's no stop_time, but there's a start_time and feature timestamp after the start_time 175 if start_time_str != "" && stop_time_str == "" && fts_str != "" && fts.Sub(start_time) >= 0 { 176 return true, nil 177 } 178 // If there's no stop time, but there's a start time and a feature start and/or stop time 179 if start_time_str != "" && stop_time_str == "" && (fstart_str != "" || fstop_str != "") { 180 if fstart_str != "" && fstart.Sub(start_time) >= 0 { 181 return true, nil 182 } 183 if fstop_str != "" && fstop.Sub(start_time) >= 0 { 184 return true, nil 185 } 186 } 187 // If there's a start_time and stop_time { 188 if start_time_str != "" && stop_time_str != "" { 189 if fts_str != "" && fts.Sub(start_time) >= 0 && fts.Sub(stop_time) <= 0 { 190 return true, nil 191 } 192 if fstart_str != "" && fstart.Sub(start_time) >= 0 && fstart.Sub(stop_time) <= 0 { 193 return true, nil 194 } 195 if fstop_str != "" && fstop.Sub(start_time) >= 0 && fstop.Sub(stop_time) <= 0 { 196 return true, nil 197 } 198 } 199 // If there's a timestamp 200 if timestamp_str != "" { 201 if fts_str != "" && fts == timestamp { 202 return true, nil 203 } 204 if fstart_str != "" && timestamp.Sub(fstart) >= 0 { 205 if fstop_str == "" || timestamp.Sub(fstop) <= 0 { 206 return true, nil 207 } 208 } 209 if fstop_str != "" && timestamp.Sub(fstop) <= 0 { 210 if fstart_str == "" || timestamp.Sub(fstart) >= 0 { 211 return true, nil 212 } 213 } 214 } 215 return false, nil 216 } 217 218 // Filter out features based on params passed 219 // start_time, stop_time, timestamp parameters are specifically used for timestamp filtering 220 // @see check_time_filter(). 221 func (p *Provider) FilterFeatures(extent *geom.Extent, collections []string, properties map[string]string) ([]FeatureId, error) { 222 if len(collections) < 1 { 223 var err error 224 collections, err = p.CollectionNames() 225 if err != nil { 226 return nil, err 227 } 228 } 229 // To maintain a consistent order for paging & testing 230 sort.Strings(collections) 231 232 fids := make([]FeatureId, 0, 100) 233 for _, col := range collections { 234 fs, err := p.CollectionFeatures(col, properties, extent) 235 if err != nil { 236 return nil, err 237 } 238 for _, f := range fs { 239 fids = append(fids, FeatureId{Collection: col, FeaturePk: f.ID}) 240 } 241 } 242 243 return fids, nil 244 } 245 246 // Create a new collection given collection/pk pairs to populate it 247 func (p *Provider) MakeCollection(name string, featureIds []FeatureId) (string, error) { 248 collectionIds, err := p.CollectionNames() 249 if err != nil { 250 return "", err 251 } 252 for _, cid := range collectionIds { 253 if name == cid { 254 e := ErrDuplicateCollectionName{name: name} 255 return "", e 256 } 257 } 258 259 if p.tempCollections == nil { 260 p.tempCollections = make(map[string]*tempCollection) 261 } 262 263 p.tempCollections[name] = &tempCollection{lastAccess: time.Now(), featureIds: featureIds} 264 return name, nil 265 } 266 267 // Returns f if items from properties match the properties of f. Otherwise returns nil. 268 func property_filter(f *prv.Feature, properties map[string]string) (*prv.Feature, error) { 269 starttime := "" 270 stoptime := "" 271 timestamp := "" 272 for k, v := range properties { 273 // --- grab any time-related properties for intersection processing instead of equality testing. 274 switch k { 275 case "start_time": 276 starttime = v 277 continue 278 case "stop_time": 279 stoptime = v 280 continue 281 case "timestamp": 282 timestamp = v 283 continue 284 } 285 286 if v != f.Properties[k] { 287 return nil, nil 288 } 289 } 290 in_time_filter, err := feature_time_intersects_time_filter(f, starttime, stoptime, timestamp) 291 if err != nil { 292 return nil, err 293 } 294 if in_time_filter { 295 return f, nil 296 } else { 297 return nil, nil 298 } 299 } 300 301 // Get all features for a particular collection 302 func (p *Provider) CollectionFeatures(collectionName string, properties map[string]string, extent *geom.Extent) ([]*prv.Feature, error) { 303 // return a temp collection with this name if there is one 304 for tcn := range p.tempCollections { 305 if collectionName == tcn { 306 p.tempCollections[collectionName].lastAccess = time.Now() 307 return p.GetFeatures(p.tempCollections[collectionName].featureIds) 308 } 309 } 310 311 // otherwise hit the Tiler provider to get features for this collectionName 312 pFs := make([]*prv.Feature, 0, 100) 313 314 var err error 315 getFeatures := func(f *prv.Feature) error { 316 if properties != nil { 317 f, err = property_filter(f, properties) 318 if err != nil { 319 return err 320 } 321 if f == nil { 322 return nil 323 } 324 } 325 pFs = append(pFs, f) 326 return nil 327 } 328 329 t := EmptyTile{extent: extent, srid: 4326} 330 err = p.Tiler.TileFeatures(context.TODO(), collectionName, t, getFeatures) 331 if err != nil { 332 return nil, err 333 } 334 335 return pFs, nil 336 } 337 338 // Get features given collection/pk pairs 339 func (p *Provider) GetFeatures(featureIds []FeatureId) ([]*prv.Feature, error) { 340 // Feature pks grouped by collection 341 cf := make(map[string][]uint64) 342 fcount := 0 343 for _, fid := range featureIds { 344 if _, ok := cf[fid.Collection]; !ok { 345 cf[fid.Collection] = make([]uint64, 0, 100) 346 } 347 cf[fid.Collection] = append(cf[fid.Collection], fid.FeaturePk) 348 fcount += 1 349 } 350 351 // Desired features 352 fs := make([]*prv.Feature, 0, fcount) 353 for col, fpks := range cf { 354 colFs, err := p.CollectionFeatures(col, nil, nil) 355 if err != nil { 356 return nil, err 357 } 358 359 for _, colF := range colFs { 360 for _, fpk := range fpks { 361 if colF.ID == fpk { 362 fs = append(fs, colF) 363 break 364 } 365 } 366 } 367 } 368 369 return fs, nil 370 } 371 372 // Fetch a list of all collection names from provider 373 func (p *Provider) CollectionNames() ([]string, error) { 374 featureTableInfo, err := p.Tiler.Layers() 375 if err != nil { 376 return nil, err 377 } 378 379 ftNames := make([]string, len(featureTableInfo)) 380 for i, fti := range featureTableInfo { 381 ftNames[i] = fti.Name() 382 } 383 sort.Strings(ftNames) 384 385 return ftNames, err 386 }