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  }