diff --git a/README.md b/README.md index 20b4ee1..30dcda3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ A a GeoJSON implementation with a Geopackage as a data provider. The specification is a preliminary one, with `go generate` the routing based on api spec, provider interfaces en types structs and convenient parameter extractions are generated to stay easily up to date. -* FeatureCollectionGeoJSON is overridden in the GeoPackage provider to use the [GeoJSON](https://github.com/go-spatial/geom/tree/master/encoding/geojson) equivalent for decoding blobs +* FeatureCollection is overridden in the GeoPackage provider to use the [GeoJSON](https://github.com/go-spatial/geom/tree/master/encoding/geojson) equivalent for decoding blobs * ## Build diff --git a/core/api.go b/core/api.go new file mode 100644 index 0000000..d932007 --- /dev/null +++ b/core/api.go @@ -0,0 +1,53 @@ +package core + +import ( + "net/http" + "oaf-server/codegen" + + "github.com/getkin/kin-openapi/openapi3" +) + +// GetApiProvider is returned by the NewGetApiProvider +// containing the data and contenttype for the response +type GetApiProvider struct { + data *openapi3.T + contenttype string +} + +// NewGetApiProvider handles the request and return the GetApiProvider +func NewGetApiProvider(api *openapi3.T) func(r *http.Request) (codegen.Provider, error) { + + return func(r *http.Request) (codegen.Provider, error) { + p := &GetApiProvider{} + + ct, err := GetContentType(r, p.String()) + if err != nil { + return nil, err + } + + p.contenttype = ct + p.data = api + + return p, nil + } +} + +// Provide provides the data +func (gap *GetApiProvider) Provide() (interface{}, error) { + return gap.data, nil +} + +// ContentType returns the ContentType +func (gap *GetApiProvider) ContentType() string { + return gap.contenttype +} + +// String returns the provider name +func (gap *GetApiProvider) String() string { + return "api" +} + +// SrsId returns the srsid +func (gap *GetApiProvider) SrsId() string { + return "n.a" +} diff --git a/core/collection.go b/core/collection.go new file mode 100644 index 0000000..7294b2f --- /dev/null +++ b/core/collection.go @@ -0,0 +1,90 @@ +package core + +import ( + "fmt" + "net/http" + "oaf-server/codegen" +) + +// DescribeCollectionProvider is returned by the NewDescribeCollectionProvider +// containing the data and contenttype for the response +type DescribeCollectionProvider struct { + data codegen.Collection + contenttype string +} + +// NewDescribeCollectionProvider handles the request and return the DescribeCollectionProvider +func NewDescribeCollectionProvider(config Config) func(r *http.Request) (codegen.Provider, error) { + + return func(r *http.Request) (codegen.Provider, error) { + path := r.URL.Path // collections/{{collectionId}} + + collectionId, _ := codegen.ParametersForDescribeCollection(r) + + p := &DescribeCollectionProvider{} + + ct, err := GetContentType(r, p.String()) + if err != nil { + return nil, err + } + p.contenttype = ct + + for _, cn := range config.Datasource.Collections { + // maybe convert to map, but not thread safe! + if cn.Identifier != collectionId { + continue + } + + cInfo := codegen.Collection{ + Id: cn.Identifier, + Title: cn.Identifier, + Description: cn.Description, + Crs: []string{config.Crs[fmt.Sprintf("%d", cn.Srid)]}, + Links: []codegen.Link{}, + } + + // create links + hrefBase := fmt.Sprintf("%s%s", config.Service.Url, path) // /collections + links, _ := CreateLinks(collectionId, p.String(), hrefBase, "self", ct) + + cihrefBase := fmt.Sprintf("%s/items", hrefBase) + ilinks, _ := CreateLinks("items of "+collectionId, p.String(), cihrefBase, "item", ct) + cInfo.Links = append(links, ilinks...) + + for _, c := range config.Datasource.Collections { + if c.Identifier == cn.Identifier { + if len(c.Links) != 0 { + cInfo.Links = append(cInfo.Links, c.Links...) + } + break + } + } + + p.data = cInfo + break + } + + return p, nil + } + +} + +// Provide returns the srsid +func (dcp *DescribeCollectionProvider) Provide() (interface{}, error) { + return dcp.data, nil +} + +// ContentType returns the srsid +func (dcp *DescribeCollectionProvider) ContentType() string { + return dcp.contenttype +} + +// SrsStringId returns the provider name +func (dcp *DescribeCollectionProvider) String() string { + return "collection" +} + +// SrsId returns the srsid +func (dcp *DescribeCollectionProvider) SrsId() string { + return "n.a." +} diff --git a/core/collections.go b/core/collections.go new file mode 100644 index 0000000..9136afa --- /dev/null +++ b/core/collections.go @@ -0,0 +1,96 @@ +package core + +import ( + "fmt" + "net/http" + "oaf-server/codegen" +) + +// GetCollectionsProvider is returned by the NewGetCollectionsProvider +// containing the data and contenttype for the response +type GetCollectionsProvider struct { + data codegen.Collections + contenttype string +} + +// NewGetCollectionsProvider handles the request and return the GetCollectionsProvider +func NewGetCollectionsProvider(config Config) func(r *http.Request) (codegen.Provider, error) { + + return func(r *http.Request) (codegen.Provider, error) { + path := r.URL.Path // collections + + p := &GetCollectionsProvider{} + + ct, err := GetContentType(r, p.String()) + if err != nil { + return nil, err + } + + p.contenttype = ct + + csInfo := codegen.Collections{Links: []codegen.Link{}, Collections: []codegen.Collection{}} + // create Links + hrefBase := fmt.Sprintf("%s%s", config.Service.Url, path) // /collections + links, _ := CreateLinks("collections ", p.String(), hrefBase, "self", ct) + csInfo.Links = append(csInfo.Links, links...) + for _, cn := range config.Datasource.Collections { + clinks, _ := CreateLinks("collection "+cn.Identifier, p.String(), fmt.Sprintf("%s/%s", hrefBase, cn.Identifier), "item", ct) + csInfo.Links = append(csInfo.Links, clinks...) + } + + for _, cn := range config.Datasource.Collections { + + cInfo := codegen.Collection{ + Id: cn.Identifier, + Title: cn.Identifier, + Description: cn.Description, + Crs: []string{config.Crs[fmt.Sprintf("%d", cn.Srid)]}, + Links: []codegen.Link{}, + } + + chrefBase := fmt.Sprintf("%s/%s", hrefBase, cn.Identifier) + + clinks, _ := CreateLinks("collection "+cn.Identifier, p.String(), chrefBase, "self", ct) + cInfo.Links = append(cInfo.Links, clinks...) + + cihrefBase := fmt.Sprintf("%s/items", chrefBase) + ilinks, _ := CreateLinks("items "+cn.Identifier, p.String(), cihrefBase, "item", ct) + cInfo.Links = append(cInfo.Links, ilinks...) + + for _, c := range config.Datasource.Collections { + if c.Identifier == cn.Identifier { + if len(c.Links) != 0 { + cInfo.Links = append(cInfo.Links, c.Links...) + } + break + } + } + + csInfo.Collections = append(csInfo.Collections, cInfo) + } + + p.data = csInfo + + return p, nil + } +} + +// Provide provides the data +func (gcp *GetCollectionsProvider) Provide() (interface{}, error) { + return gcp.data, nil +} + +// ContentType returns the ContentType +func (gcp *GetCollectionsProvider) ContentType() string { + return gcp.contenttype +} + +// String returns the provider name +func (gcp *GetCollectionsProvider) String() string { + return "collections" +} + +// SrsId returns the srsid +func (gcp *GetCollectionsProvider) SrsId() string { + return "n.a." +} diff --git a/provider/config.go b/core/config.go similarity index 81% rename from provider/config.go rename to core/config.go index c2bd591..896fd34 100644 --- a/provider/config.go +++ b/core/config.go @@ -1,24 +1,27 @@ -package provider +package core import ( "io/ioutil" "log" "oaf-server/codegen" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) +// Config wrappes all the available configuration type Config struct { ApplicationId string `yaml:"applicationid,omitempty"` UserVersion string `yaml:"userversion,omitempty"` - Openapi string `yaml:"openapi"` - DefaultFeatureLimit int `yaml:"defaultfeaturelimit"` - MaxFeatureLimit int `yaml:"maxfeaturelimit"` + Openapi string `yaml:"openapi"` + DefaultFeatureLimit int `yaml:"defaultfeaturelimit"` + MaxFeatureLimit int `yaml:"maxfeaturelimit"` + Crs map[string]string `yaml:"crs"` Datasource Datasource Service Service `yaml:"service" json:"service"` } +// ContactPoint needed for the ld+json type ContactPoint struct { Type string `yaml:"@type" json:"@type,omitempty"` Email string `yaml:"email" json:"email,omitempty"` @@ -27,6 +30,8 @@ type ContactPoint struct { ContactType string `yaml:"contactType" json:"contactType,omitempty"` Description string `yaml:"description" json:"description,omitempty"` } + +// Address needed for the ld+json type Address struct { Type string `yaml:"@type" json:"@type,omitempty"` StreetAddress string `yaml:"streetAddress" json:"streetAddress,omitempty"` @@ -36,6 +41,7 @@ type Address struct { AddressCountry string `yaml:"addressCountry" json:"addressCountry,omitempty"` } +// Provider needed for the ld+json type Provider struct { Type string `yaml:"@type" json:"@type"` Name string `yaml:"name" json:"name"` @@ -44,6 +50,7 @@ type Provider struct { ContactPoint *ContactPoint `yaml:"contactPoint" json:"contactPoint,omitempty"` // pointer, omitting when empty } +// Service contains the necessary information for building the right ld+json objects type Service struct { Context string `yaml:"@context" json:"@context"` Type string `yaml:"@type" json:"@type"` @@ -57,6 +64,7 @@ type Service struct { Provider Provider `yaml:"provider" json:"provider"` } +// Datasource wrappes the datasources, collections, dataset boundingbox and SRID type Datasource struct { Geopackage *Geopackage `yaml:"gpkg"` PostGIS *PostGIS `yaml:"postgis"` @@ -65,15 +73,18 @@ type Datasource struct { Srid int `yaml:"srid"` } +// Geopackage contains the Geopackage file locations and a alternative Fid column type Geopackage struct { File string `yaml:"file"` Fid string `yaml:"fid"` } +// PostGIS contains the PostGIS connection string type PostGIS struct { Connection string `yaml:"connection"` } +// Collection contains all the needed information for a collections type Collection struct { Schemaname string `yaml:"schemaname"` Tablename string `yaml:"tablename"` @@ -92,6 +103,7 @@ type Collection struct { Links []codegen.Link `yaml:"links"` } +// Columns stores the Fid, Offset, BoundingBox and Geometry columns from the datasources type Columns struct { Fid string `yaml:"fid"` Offset string `yaml:"offset"` @@ -99,6 +111,7 @@ type Columns struct { Geometry string `yaml:"geometry"` } +// NewService initializes a Service func NewService() Service { address := Address{ Type: "PostalAddress", @@ -119,6 +132,7 @@ func NewService() Service { return service } +// ReadConfig reads the from the given path the configuration func (c *Config) ReadConfig(path string) { bytes, err := ioutil.ReadFile(path) if err != nil { @@ -153,6 +167,11 @@ func (c *Config) ReadConfig(path string) { c.MaxFeatureLimit = 500 } + if len(c.Crs) == 0 { + crs := map[string]string{`4326`: `http://www.opengis.net/def/crs/EPSG/0/4326`} + c.Crs = crs + } + if c.Openapi == "" { c.Openapi = "spec/oaf.json" } diff --git a/core/conformance.go b/core/conformance.go new file mode 100644 index 0000000..7e51eb5 --- /dev/null +++ b/core/conformance.go @@ -0,0 +1,76 @@ +package core + +import ( + "errors" + "net/http" + "oaf-server/codegen" + + "github.com/getkin/kin-openapi/openapi3" +) + +const ( + core = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core" + oas30 = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30" + html = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/html" + gjson = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" +) + +// GetConformanceDeclarationProvider is returned by the NewGetConformanceDeclarationProvider +// containing the data and contenttype for the response +type GetConformanceDeclarationProvider struct { + data map[string][]string + contenttype string +} + +// NewGetConformanceDeclarationProvider handles the request and return the GetConformanceDeclarationProvider +func NewGetConformanceDeclarationProvider(api *openapi3.T) func(r *http.Request) (codegen.Provider, error) { + return func(r *http.Request) (codegen.Provider, error) { + p := &GetConformanceDeclarationProvider{} + + ct, err := GetContentType(r, p.String()) + + if err != nil { + return nil, err + } + + p.contenttype = ct + path := r.URL.Path + pathItem := api.Paths.Find(path) + if pathItem == nil { + return p, errors.New("Invalid path :" + path) + } + + for k := range r.URL.Query() { + if notfound := pathItem.Get.Parameters.GetByInAndName("query", k) == nil; notfound { + return p, errors.New("Invalid query parameter :" + k) + } + } + + d := make(map[string][]string) + d["conformsTo"] = []string{core, oas30, html, gjson} + + p.data = d + return p, nil + } + +} + +// Provide provides the data +func (gcdp *GetConformanceDeclarationProvider) Provide() (interface{}, error) { + return gcdp.data, nil +} + +// ContentType returns the ContentType +func (gcdp *GetConformanceDeclarationProvider) ContentType() string { + return gcdp.contenttype +} + +// String returns the provider name +func (gcdp *GetConformanceDeclarationProvider) String() string { + return "conformance" +} + +// SrsId returns the srsid +func (gcdp *GetConformanceDeclarationProvider) SrsId() string { + return "n.a." +} diff --git a/gpkg/extra_types.go b/core/geojson.go similarity index 60% rename from gpkg/extra_types.go rename to core/geojson.go index f5be6bd..a044c2f 100644 --- a/gpkg/extra_types.go +++ b/core/geojson.go @@ -1,4 +1,4 @@ -package gpkg +package core import ( "oaf-server/codegen" @@ -6,10 +6,7 @@ import ( "github.com/go-spatial/geom/encoding/geojson" ) -// this code is generated by go generate -// Not anymore geojson.FeatureType included - -type FeatureCollectionGeoJSON struct { +type FeatureCollection struct { NumberReturned int64 `json:"numberReturned,omitempty"` TimeStamp string `json:"timeStamp,omitempty"` Type string `json:"type"` @@ -17,15 +14,13 @@ type FeatureCollectionGeoJSON struct { Links []codegen.Link `json:"links,omitempty"` NumberMatched int64 `json:"numberMatched,omitempty"` Crs string `json:"crs,omitempty"` + Offset int64 `json:"-"` } type Feature struct { - Type string `json:"type"` - ID interface{} `json:"id,omitempty"` - // can be null - Geometry geojson.Geometry `json:"geometry"` - // can be null - Properties map[string]interface{} `json:"properties"` - // can be null + // overwrite ID in geojson.Feature so strings are also allowed as id + ID interface{} `json:"id,omitempty"` + geojson.Feature + // Added Links in de document Links []codegen.Link `json:"links,omitempty"` } diff --git a/provider/getlandingpageprovider.go b/core/landingpage.go similarity index 84% rename from provider/getlandingpageprovider.go rename to core/landingpage.go index c08c096..225760b 100644 --- a/provider/getlandingpageprovider.go +++ b/core/landingpage.go @@ -1,4 +1,4 @@ -package provider +package core import ( "fmt" @@ -6,6 +6,8 @@ import ( "oaf-server/codegen" ) +// GetLandingPageProvider is returned by the NewGetLandingPageProvider +// containing the data and contenttype for the response type GetLandingPageProvider struct { Links []codegen.Link `json:"links,omitempty"` contenttype string @@ -14,6 +16,7 @@ type GetLandingPageProvider struct { *Service } +// NewGetLandingPageProvider handles the request and return the GetLandingPageProvider func NewGetLandingPageProvider(serviceConfig Service) func(r *http.Request) (codegen.Provider, error) { return func(r *http.Request) (codegen.Provider, error) { @@ -48,18 +51,22 @@ func NewGetLandingPageProvider(serviceConfig Service) func(r *http.Request) (cod } } +// Provide provides the data func (glp *GetLandingPageProvider) Provide() (interface{}, error) { return glp, nil } +// ContentType returns the ContentType func (glp *GetLandingPageProvider) ContentType() string { return glp.contenttype } +// String returns the provider name func (glp *GetLandingPageProvider) String() string { return "landingpage" } +// SrsId returns the srsid func (glp *GetLandingPageProvider) SrsId() string { return "n.a" } diff --git a/provider/getlandingpageprovider_test.go b/core/landingpage_test.go similarity index 82% rename from provider/getlandingpageprovider_test.go rename to core/landingpage_test.go index 6611020..b10245d 100644 --- a/provider/getlandingpageprovider_test.go +++ b/core/landingpage_test.go @@ -1,7 +1,8 @@ -package provider +package core import ( "net/http" + "net/url" "reflect" "testing" ) @@ -10,7 +11,7 @@ func TestNewGetLandingPageProvider(t *testing.T) { service := Service{} - provider, err := NewGetLandingPageProvider(service)(&http.Request{}) + provider, err := NewGetLandingPageProvider(service)(&http.Request{URL: &url.URL{RawQuery: ""}, Header: map[string][]string{}}) if err != nil { t.Errorf("NewGetLandingPageProvider(serviceEndpoint) = %v, want %v", err, nil) } @@ -26,7 +27,7 @@ func TestNewGetLandingPageProvider(t *testing.T) { links := provided.(*GetLandingPageProvider).Links - if len(links) != 12 { - t.Errorf("GetLandingPageProvider.Provide() has incorrect number of links = %v, want %v", len(links), 12) + if len(links) != 11 { + t.Errorf("GetLandingPageProvider.Provide() has incorrect number of links = %v, want %v", len(links), 11) } } diff --git a/provider/util.go b/core/util.go similarity index 96% rename from provider/util.go rename to core/util.go index 49dc90e..c5dad68 100644 --- a/provider/util.go +++ b/core/util.go @@ -1,4 +1,4 @@ -package provider +package core import ( "fmt" @@ -25,13 +25,13 @@ const ( ) var providerNameMap = map[string]string{ - "describecollection": CapabilitesProvider, - "getcollections": CapabilitesProvider, - "getconformancedeclaration": CapabilitesProvider, - "getfeature": DataProvider, - "getfeatures": DataProvider, - "api": OASProvider, - "landingpage": CapabilitesProvider, + "collection": CapabilitesProvider, + "collections": CapabilitesProvider, + "conformance": CapabilitesProvider, + "feature": DataProvider, + "features": DataProvider, + "api": OASProvider, + "landingpage": CapabilitesProvider, } type InvalidContentTypeError struct { diff --git a/example/config-addresses-gpkg.yaml b/example/config-addresses-gpkg.yaml index f14ff1c..3fb21c9 100644 --- a/example/config-addresses-gpkg.yaml +++ b/example/config-addresses-gpkg.yaml @@ -26,6 +26,9 @@ service: openapi: spec/oaf.json defaultfeaturelimit: 100 maxfeaturelimit: 500 +crs: + CRS84: http://www.opengis.net/def/crs/OGC/1.3/CRS84 + "4326": http://www.opengis.net/def/crs/EPSG/0/4326 datasource: gpkg: file: example/addresses.gpkg diff --git a/example/epsg.example.yaml b/example/epsg.example.yaml deleted file mode 100644 index 6d73917..0000000 --- a/example/epsg.example.yaml +++ /dev/null @@ -1,2 +0,0 @@ -"28992": "http://www.opengis.net/def/crs/EPSG/0/28992" -"4326": "http://www.opengis.net/def/crs/OGC/1.3/CRS84" \ No newline at end of file diff --git a/gpkg/README.md b/geopackage/README.md similarity index 100% rename from gpkg/README.md rename to geopackage/README.md diff --git a/geopackage/core.go b/geopackage/core.go new file mode 100644 index 0000000..c753726 --- /dev/null +++ b/geopackage/core.go @@ -0,0 +1,32 @@ +package geopackage + +import ( + "net/http" + "oaf-server/codegen" + "oaf-server/core" +) + +// NewGetLandingPageProvider passes the request to the Core NewGetLandingPageProvider with the GeoPackage Config +func (gp *GeoPackageProvider) NewGetLandingPageProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetLandingPageProvider(gp.Config.Service)(r) +} + +// NewGetApiProvider passes the request to the Core NewGetApiProvider with the GeoPackage Config +func (gp *GeoPackageProvider) NewGetApiProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetApiProvider(gp.ApiProcessed)(r) +} + +// NewGetConformanceDeclarationProvider passes the request to the Core NewGetConformanceDeclarationProvider with the GeoPackage Config +func (gp *GeoPackageProvider) NewGetConformanceDeclarationProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetConformanceDeclarationProvider(gp.ApiProcessed)(r) +} + +// NewGetCollectionsProvider passes the request to the Core NewGetCollectionsProvider with the GeoPackage Config +func (gp *GeoPackageProvider) NewGetCollectionsProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetCollectionsProvider(gp.Config)(r) +} + +// NewDescribeCollectionProvider passes the request to the Core NewDescribeCollectionProvider with the GeoPackage Config +func (gp *GeoPackageProvider) NewDescribeCollectionProvider(r *http.Request) (codegen.Provider, error) { + return core.NewDescribeCollectionProvider(gp.Config)(r) +} diff --git a/gpkg/getfeatureprovider.go b/geopackage/feature.go similarity index 71% rename from gpkg/getfeatureprovider.go rename to geopackage/feature.go index 2b1b0d4..44ec563 100644 --- a/gpkg/getfeatureprovider.go +++ b/geopackage/feature.go @@ -1,18 +1,21 @@ -package gpkg +package geopackage import ( "fmt" "net/http" "oaf-server/codegen" - "oaf-server/provider" + "oaf-server/core" ) +// GetFeatureProvider is returned by the NewGetFeatureProvider +// containing the data, srsid and contenttype for the response type GetFeatureProvider struct { - data *Feature + data *core.Feature srsid string contenttype string } +// NewGetFeatureProvider handles the request and return the GetFeatureProvider func (gp *GeoPackageProvider) NewGetFeatureProvider(r *http.Request) (codegen.Provider, error) { collectionId, featureId, _ := codegen.ParametersForGetFeature(r) @@ -24,7 +27,7 @@ func (gp *GeoPackageProvider) NewGetFeatureProvider(r *http.Request) (codegen.Pr path := r.URL.Path - ct, err := provider.GetContentType(r, p.String()) + ct, err := core.GetContentType(r, p.String()) if err != nil { return nil, err } @@ -48,7 +51,7 @@ func (gp *GeoPackageProvider) NewGetFeatureProvider(r *http.Request) (codegen.Pr feature := fcGeoJSON.Features[0] hrefBase := fmt.Sprintf("%s%s", gp.Config.Service.Url, path) // /collections - links, _ := provider.CreateFeatureLinks("feature", hrefBase, "self", ct) + links, _ := core.CreateFeatureLinks("feature", hrefBase, "self", ct) feature.Links = links p.data = feature @@ -60,18 +63,22 @@ func (gp *GeoPackageProvider) NewGetFeatureProvider(r *http.Request) (codegen.Pr return p, nil } +// Provide provides the data func (gfp *GetFeatureProvider) Provide() (interface{}, error) { return gfp.data, nil } +// ContentType returns the ContentType func (gfp *GetFeatureProvider) ContentType() string { return gfp.contenttype } +// String returns the provider name func (gfp *GetFeatureProvider) String() string { - return "getfeature" + return "feature" } +// SrsId returns the srsid func (gfp *GetFeatureProvider) SrsId() string { return gfp.srsid } diff --git a/gpkg/getfeaturesprovider.go b/geopackage/features.go similarity index 66% rename from gpkg/getfeaturesprovider.go rename to geopackage/features.go index e5c4707..a961a0f 100644 --- a/gpkg/getfeaturesprovider.go +++ b/geopackage/features.go @@ -1,25 +1,28 @@ -package gpkg +package geopackage import ( "fmt" "log" "net/http" "oaf-server/codegen" - "oaf-server/provider" + "oaf-server/core" ) +// GetFeaturesProvider is returned by the NewGetFeaturesProvider +// containing the data, srsid and contenttype for the response type GetFeaturesProvider struct { - data FeatureCollectionGeoJSON + data core.FeatureCollection srsid string contenttype string } +// NewGetFeaturesProvider handles the request and return the GetFeaturesProvider func (gp *GeoPackageProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Provider, error) { collectionId, limit, offset, _, bbox, time := codegen.ParametersForGetFeatures(r) - limitParam := provider.ParseLimit(limit, gp.CommonProvider.DefaultReturnLimit, gp.CommonProvider.MaxReturnLimit) - offsetParam := provider.ParseUint(offset, 0) - bboxParam := provider.ParseBBox(bbox, gp.GeoPackage.DefaultBBox) + limitParam := core.ParseLimit(limit, uint64(gp.Config.DefaultFeatureLimit), uint64(gp.Config.MaxFeatureLimit)) + offsetParam := core.ParseUint(offset, 0) + bboxParam := core.ParseBBox(bbox, gp.GeoPackage.DefaultBBox) if time != "" { log.Println("Time selection currently not implemented") @@ -28,7 +31,7 @@ func (gp *GeoPackageProvider) NewGetFeaturesProvider(r *http.Request) (codegen.P path := r.URL.Path // collections/{{collectionId}}/items p := &GetFeaturesProvider{srsid: fmt.Sprintf("EPSG:%d", gp.GeoPackage.Srid)} - ct, err := provider.GetContentType(r, p.String()) + ct, err := core.GetContentType(r, p.String()) if err != nil { return nil, err } @@ -49,7 +52,7 @@ func (gp *GeoPackageProvider) NewGetFeaturesProvider(r *http.Request) (codegen.P for _, feature := range fcGeoJSON.Features { hrefBase := fmt.Sprintf("%s%s/%v", gp.Config.Service.Url, path, feature.ID) // /collections - links, _ := provider.CreateFeatureLinks("feature", hrefBase, "self", ct) + links, _ := core.CreateFeatureLinks("feature", hrefBase, "self", ct) feature.Links = links } @@ -68,26 +71,20 @@ func (gp *GeoPackageProvider) NewGetFeaturesProvider(r *http.Request) (codegen.P // create links hrefBase := fmt.Sprintf("%s%s", gp.Config.Service.Url, path) // /collections - links, _ := provider.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "self", ct) - _ = provider.ProcesLinksForParams(links, requestParams) + links, _ := core.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "self", ct) + _ = core.ProcesLinksForParams(links, requestParams) // next => offsetParam + limitParam < numbersMatched if (int64(limitParam)) == fcGeoJSON.NumberReturned { - ilinks, _ := provider.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "next", ct) + ilinks, _ := core.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "next", ct) requestParams.Set("offset", fmt.Sprintf("%d", int64(offsetParam)+int64(limitParam))) - _ = provider.ProcesLinksForParams(ilinks, requestParams) + _ = core.ProcesLinksForParams(ilinks, requestParams) links = append(links, ilinks...) } fcGeoJSON.Links = links - - crsUri, ok := gp.CrsMap[fmt.Sprintf("%d", cn.Srid)] - if !ok { - log.Printf("SRS ID: %s, not found", fmt.Sprintf("%d", cn.Srid)) - crsUri = "" - } - fcGeoJSON.Crs = crsUri + fcGeoJSON.Crs = gp.Config.Crs[fmt.Sprintf("%d", cn.Srid)] p.data = *fcGeoJSON break @@ -96,18 +93,22 @@ func (gp *GeoPackageProvider) NewGetFeaturesProvider(r *http.Request) (codegen.P return p, nil } +// Provide provides the data func (gfp *GetFeaturesProvider) Provide() (interface{}, error) { return gfp.data, nil } +// ContentType returns the ContentType func (gfp *GetFeaturesProvider) ContentType() string { return gfp.contenttype } +// String returns the provider name func (gfp *GetFeaturesProvider) String() string { - return "getfeatures" + return "features" } +// SrsId returns the srsid func (gfp *GetFeaturesProvider) SrsId() string { return gfp.srsid } diff --git a/gpkg/gpkg.go b/geopackage/geopackage.go similarity index 87% rename from gpkg/gpkg.go rename to geopackage/geopackage.go index d235096..159e97a 100644 --- a/gpkg/gpkg.go +++ b/geopackage/geopackage.go @@ -1,4 +1,4 @@ -package gpkg +package geopackage import ( "context" @@ -6,15 +6,19 @@ import ( "errors" "fmt" "log" - "oaf-server/provider" + "oaf-server/core" "os" "regexp" "time" "github.com/go-spatial/geom/encoding/geojson" "github.com/jmoiron/sqlx" + + "github.com/go-spatial/geom/encoding/gpkg" ) +// GeoPackageLayer used for reading the given GeoPackage +// This will later be translated to Collections type GeoPackageLayer struct { TableName string `db:"table_name"` DataType string `db:"data_type"` @@ -33,12 +37,13 @@ type GeoPackageLayer struct { Features []string // first table, second PK, rest features } +// Geopackage configuration type GeoPackage struct { ApplicationId string UserVersion int64 DB *sqlx.DB FeatureIdKey string - Collections []provider.Collection + Collections []core.Collection DefaultBBox [4]float64 Srid int64 } @@ -87,7 +92,7 @@ func NewGeoPackage(filepath string, featureIdKey string) (GeoPackage, error) { } } log.Printf("| \n") - log.Printf("| BBOX: [%f,%f,%f,%f], SRS_ID:%d", gpkg.DefaultBBox[0], gpkg.DefaultBBox[1], gpkg.DefaultBBox[2], gpkg.DefaultBBox[3], gpkg.Srid) + log.Printf("| BBOX: [%f,%f,%f,%f], SRID: %d", gpkg.DefaultBBox[0], gpkg.DefaultBBox[1], gpkg.DefaultBBox[2], gpkg.DefaultBBox[3], gpkg.Srid) return *gpkg, nil } @@ -96,7 +101,7 @@ func (gpkg *GeoPackage) Close() error { return gpkg.DB.Close() } -func (gpkg *GeoPackage) GetCollections(ctx context.Context, db *sqlx.DB) (result []provider.Collection, err error) { +func (gpkg *GeoPackage) GetCollections(ctx context.Context, db *sqlx.DB) (result []core.Collection, err error) { if gpkg.Collections != nil { result = gpkg.Collections @@ -120,7 +125,7 @@ func (gpkg *GeoPackage) GetCollections(ctx context.Context, db *sqlx.DB) (result } defer rowsClose(query, rows) - gpkg.Collections = make([]provider.Collection, 0) + gpkg.Collections = make([]core.Collection, 0) for rows.Next() { if err = ctx.Err(); err != nil { @@ -138,11 +143,11 @@ func (gpkg *GeoPackage) GetCollections(ctx context.Context, db *sqlx.DB) (result row.Features = append(row.Features, match[1]) } - collection := provider.Collection{ + collection := core.Collection{ Tablename: row.TableName, Identifier: row.Identifier, Description: row.Description, - Columns: &provider.Columns{Geometry: row.ColumnName}, + Columns: &core.Columns{Geometry: row.ColumnName}, Geometrytype: row.GeometryType, BBox: [4]float64{row.MinX, row.MinY, row.MaxX, row.MaxY}, Srid: int(row.SrsId), @@ -157,9 +162,10 @@ func (gpkg *GeoPackage) GetCollections(ctx context.Context, db *sqlx.DB) (result return } -func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection provider.Collection, collectionId string, offset uint64, limit uint64, featureId interface{}, bbox [4]float64) (result *FeatureCollectionGeoJSON, err error) { +// GetFeatures return the FeatureCollection +func (geopackage GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection core.Collection, collectionId string, offset uint64, limit uint64, featureId interface{}, bbox [4]float64) (result *core.FeatureCollection, err error) { // Features bit of a hack // layer.Features => tablename, PK, ...FEATURES, assuming create table in sql statement first is PK - result = &FeatureCollectionGeoJSON{} + result = &core.FeatureCollection{} if len(bbox) > 4 { err = errors.New("bbox with 6 elements not supported") return @@ -167,10 +173,10 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection var featureIdKey string - if gpkg.FeatureIdKey == "" { + if geopackage.FeatureIdKey == "" { featureIdKey = collection.Properties[1] } else { - featureIdKey = gpkg.FeatureIdKey + featureIdKey = geopackage.FeatureIdKey } rtreeTablenName := fmt.Sprintf("rtree_%s_%s", collection.Tablename, collection.Columns.Geometry) @@ -213,7 +219,7 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection result.NumberReturned = 0 result.Type = "FeatureCollection" - result.Features = make([]*Feature, 0) + result.Features = make([]*core.Feature, 0) for rows.Next() { if err = ctx.Err(); err != nil { @@ -233,7 +239,7 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection return } - feature := &Feature{Type: "Feature", Properties: make(map[string]interface{})} + feature := &core.Feature{Feature: geojson.Feature{Properties: make(map[string]interface{})}} for i, colName := range cols { // check if the context cancelled or timed out @@ -248,7 +254,7 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection switch colName { case featureIdKey: - ID, err := provider.ConvertFeatureID(vals[i]) + ID, err := core.ConvertFeatureID(vals[i]) if err != nil { return result, err } @@ -267,11 +273,11 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection return result, errors.New("unexpected column type for geom field. expected blob") } - _, geo, err := DecodeGeometry(geomData) + geo, err := gpkg.DecodeGeometry(geomData) if err != nil { return result, err } - feature.Geometry = geojson.Geometry{Geometry: geo} + feature.Geometry = geojson.Geometry{Geometry: geo.Geometry} case "minx", "miny", "maxx", "maxy", "min_zoom", "max_zoom": // Skip these columns used for bounding box and zoom filtering @@ -307,6 +313,7 @@ func (gpkg GeoPackage) GetFeatures(ctx context.Context, db *sqlx.DB, collection return } +// GetApplicationID returns a string containing the GeoPackage application_id func (gpkg *GeoPackage) GetApplicationID(ctx context.Context, db *sqlx.DB) (string, error) { if gpkg.ApplicationId != "" { @@ -337,6 +344,7 @@ func (gpkg *GeoPackage) GetApplicationID(ctx context.Context, db *sqlx.DB) (stri } +// GetVersion returns a string containing the GeoPackage version func (gpkg *GeoPackage) GetVersion(ctx context.Context, db *sqlx.DB) (int64, error) { if gpkg.UserVersion != 0 { diff --git a/geopackage/initprovider.go b/geopackage/initprovider.go new file mode 100644 index 0000000..c85bf49 --- /dev/null +++ b/geopackage/initprovider.go @@ -0,0 +1,53 @@ +package geopackage + +import ( + "log" + "oaf-server/core" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/imdario/mergo" +) + +// GeoPackageProvider +type GeoPackageProvider struct { + GeoPackage GeoPackage + Config core.Config + Api *openapi3.T + ApiProcessed *openapi3.T +} + +// NewGeopackageWithCommonProvider returns a new GeoPackageProvider set with the +// given config and OAS3 spec +func NewGeopackageWithCommonProvider(api *openapi3.T, config core.Config) *GeoPackageProvider { + return &GeoPackageProvider{ + Api: api, + Config: config, + } +} + +// Init initialize the *GeoPackag +// and processed the OAS3 spec with the available collections +func (gp *GeoPackageProvider) Init() (err error) { + gp.GeoPackage, err = NewGeoPackage(gp.Config.Datasource.Geopackage.File, gp.Config.Datasource.Geopackage.Fid) + + collections := gp.Config.Datasource.Collections + + if len(collections) != 0 { + for _, gpkgc := range gp.GeoPackage.Collections { + for _, configc := range collections { + if gpkgc.Identifier == configc.Identifier { + err = mergo.Merge(&configc, gpkgc) + if err != nil { + log.Fatalln(err) + } + } + } + } + gp.Config.Datasource.Collections = collections + } else { + gp.Config.Datasource.Collections = gp.GeoPackage.Collections + } + + gp.ApiProcessed = core.CreateProvidesSpecificParameters(gp.Api, &gp.Config.Datasource.Collections) + return +} diff --git a/go.mod b/go.mod index 01b7f37..9db3451 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.14.7 github.com/rs/cors v1.7.0 + github.com/urfave/cli/v2 v2.3.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) go 1.16 diff --git a/go.sum b/go.sum index e5235f2..2251985 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,12 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/arolek/p v0.0.0-20191103215535-df3c295ed582/go.mod h1:JPNItmi3yb44Q5QWM+Kh5n9oeRhfcJzPNS90mbLo25U= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gdey/errors v0.0.0-20190426172550-8ebd5bc891fb h1:FYO+lZtAUnakgSW9xYs7QvgawjCDM5wgHaXoDhYHNH4= github.com/gdey/errors v0.0.0-20190426172550-8ebd5bc891fb/go.mod h1:PFaV7MgSRe92Wo9O2H2i1CIm7urUk10AgdSHKyBfjmQ= github.com/getkin/kin-openapi v0.63.0 h1:27zYoKAuHDSquDYRpfmgsDu9TKC5z5G4vlu/XmIdsa8= github.com/getkin/kin-openapi v0.63.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -53,12 +57,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -72,6 +82,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/gpkg/decode.go b/gpkg/decode.go deleted file mode 100644 index 14d8e02..0000000 --- a/gpkg/decode.go +++ /dev/null @@ -1,267 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// The MIT License (MIT) -// Copyright (c) 2018 Tom Kralidis -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. -// -/////////////////////////////////////////////////////////////////////////////// - -package gpkg - -/* - * - * copied from https://github.com/go-spatial/jivan - * - */ - -import ( - "encoding/binary" - "errors" - "fmt" - "log" - "math" - - "github.com/go-spatial/geom" - "github.com/go-spatial/geom/encoding/wkb" -) - -type envelopeType uint8 - -// Magic is the magic number encode in the header. It should be 0x4750 -var Magic = [2]byte{0x47, 0x50} - -const ( - EnvelopeTypeNone = envelopeType(0) - EnvelopeTypeXY = envelopeType(1) - EnvelopeTypeXYZ = envelopeType(2) - EnvelopeTypeXYM = envelopeType(3) - EnvelopeTypeXYZM = envelopeType(4) - EnvelopeTypeInvalid = envelopeType(5) -) - -// NumberOfElements that the particular Evnelope Type will have. -func (et envelopeType) NumberOfElements() int { - switch et { - case EnvelopeTypeNone: - return 0 - case EnvelopeTypeXY: - return 4 - case EnvelopeTypeXYZ: - return 6 - case EnvelopeTypeXYM: - return 6 - case EnvelopeTypeXYZM: - return 8 - default: - return -1 - } -} - -func (et envelopeType) String() string { - str := "NONEXYZMXYMINVALID" - switch et { - case EnvelopeTypeNone: - return str[0:4] - case EnvelopeTypeXY: - return str[4 : 4+2] - case EnvelopeTypeXYZ: - return str[4 : 4+3] - case EnvelopeTypeXYM: - return str[8 : 8+3] - case EnvelopeTypeXYZM: - return str[4 : 4+4] - default: - return str[11:] - } -} - -// HEADER FLAG LAYOUT -// 7 6 5 4 3 2 1 0 -// R R X Y E E E B -// R Reserved for future use. (should be set to 0) -// X GeoPackageBinary type -// Y empty geometry -// E Envelope type -// B ByteOrder -const ( - maskByteOrder = 1 << 0 - maskEnvelopeType = 1<<3 | 1<<2 | 1<<1 - maskEmptyGeometry = 1 << 4 - maskGeoPackageBinary = 1 << 5 -) - -type headerFlags byte - -func (hf headerFlags) String() string { return fmt.Sprintf("0x%02x", uint8(hf)) } - -// Endian will return the encoded Endianess -func (hf headerFlags) Endian() binary.ByteOrder { - if hf&maskByteOrder == 0 { - return binary.BigEndian - } - return binary.LittleEndian -} - -// EnvelopeType returns the type of the envelope. -func (hf headerFlags) Envelope() envelopeType { - et := uint8((hf & maskEnvelopeType) >> 1) - if et >= uint8(EnvelopeTypeInvalid) { - return EnvelopeTypeInvalid - } - return envelopeType(et) -} - -// IsEmpty returns whether or not the geometry is empty. -func (hf headerFlags) IsEmpty() bool { return ((hf & maskEmptyGeometry) >> 4) == 1 } - -// IsStandard returns weather or not the geometry is a standard GeoPackage geometry type. -func (hf headerFlags) IsStandard() bool { return ((hf & maskGeoPackageBinary) >> 5) == 0 } - -// BinaryHeader is the gpkg header that accompainies every feature. -type BinaryHeader struct { - // See: http://www.geopackage.org/spec/ - magic [2]byte // should be 0x47 0x50 (GP in ASCII) - version uint8 - flags headerFlags - srsid int32 - envelope []float64 - headerSize int // total bytes in header -} - -// NewBinaryHeader decodes the data into the BinaryHeader -func NewBinaryHeader(data []byte) (*BinaryHeader, error) { - if len(data) < 8 { - return nil, errors.New("not enough bytes to decode header") - } - - var bh BinaryHeader - bh.magic[0] = data[0] - bh.magic[1] = data[1] - bh.version = data[2] - bh.flags = headerFlags(data[3]) - en := bh.flags.Endian() - bh.srsid = int32(en.Uint32(data[4 : 4+4])) - - bytes := data[8:] - et := bh.flags.Envelope() - if et == EnvelopeTypeInvalid { - return nil, errors.New("invalid envelope type") - } - if et == EnvelopeTypeNone { - return &bh, nil - } - num := et.NumberOfElements() - // there are 8 bytes per float64 value and we need num of them. - if len(bytes) < (num * 8) { - return nil, errors.New("not enough bytes to decode header") - } - - bh.envelope = make([]float64, 0, num) - for i := 0; i < num; i++ { - bits := en.Uint64(bytes[i*8 : (i*8)+8]) - bh.envelope = append(bh.envelope, math.Float64frombits(bits)) - } - if bh.magic[0] != Magic[0] || bh.magic[1] != Magic[1] { - return &bh, errors.New("invalid magic number") - } - return &bh, nil - -} - -// Magic is the magic number encode in the header. It should be 0x4750 -func (h *BinaryHeader) Magic() [2]byte { - if h == nil { - return Magic - } - return h.magic -} - -// Version is the version number encode in the header. -func (h *BinaryHeader) Version() uint8 { - if h == nil { - return 0 - } - return h.version -} - -// EnvelopeType is the type of the envelope that is provided. -func (h *BinaryHeader) EnvelopeType() envelopeType { - if h == nil { - return EnvelopeTypeInvalid - } - return h.flags.Envelope() -} - -// SRSId is the SRS id of the feature. -func (h *BinaryHeader) SRSId() int32 { - if h == nil { - return 0 - } - return h.srsid -} - -// Envelope is the bounding box of the feature, used for searching. If the EnvelopeType is EvelopeTypeNone, then there isn't a envelope encoded -// and a search without an index will need to be preformed. This is to save space. -func (h *BinaryHeader) Envelope() []float64 { - if h == nil { - return nil - } - return h.envelope -} - -// IsGeometryEmpty tells us if the geometry should be considered empty. -func (h *BinaryHeader) IsGeometryEmpty() bool { - if h == nil { - return true - } - return h.flags.IsEmpty() -} - -// IsStandardGeometery is the geometry a core/extended geometry type, or a user defined geometry type. -func (h *BinaryHeader) IsStandardGeometry() bool { - if h == nil { - return true - } - return h.flags.IsStandard() -} - -// Size is the size of the header in bytes. -func (h *BinaryHeader) Size() int { - if h == nil { - return 0 - } - return (len(h.envelope) * 8) + 8 -} - -func DecodeGeometry(bytes []byte) (*BinaryHeader, geom.Geometry, error) { - h, err := NewBinaryHeader(bytes) - if err != nil { - log.Printf("error decoding geometry header: %v", err) - return h, nil, err - } - - geo, err := wkb.DecodeBytes(bytes[h.Size():]) - if err != nil { - log.Printf("error decoding geometry: %v", err) - return h, nil, err - } - - return h, geo, nil -} diff --git a/gpkg/describecollectionprovider.go b/gpkg/describecollectionprovider.go deleted file mode 100644 index d16994f..0000000 --- a/gpkg/describecollectionprovider.go +++ /dev/null @@ -1,85 +0,0 @@ -package gpkg - -import ( - "fmt" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -type DescribeCollectionProvider struct { - data codegen.Collection - contenttype string -} - -func (gp *GeoPackageProvider) NewDescribeCollectionProvider(r *http.Request) (codegen.Provider, error) { - path := r.URL.Path // collections/{{collectionId}} - - collectionId, _ := codegen.ParametersForDescribeCollection(r) - - p := &DescribeCollectionProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - p.contenttype = ct - - for _, cn := range gp.GeoPackage.Collections { - // maybe convert to map, but not thread safe! - if cn.Identifier != collectionId { - continue - } - - crss := make([]string, 0) - for _, v := range gp.CrsMap { - crss = append(crss, v) - } - - cInfo := codegen.Collection{ - Id: cn.Identifier, - Title: cn.Identifier, - Description: cn.Description, - Crs: crss, - Links: []codegen.Link{}, - } - - // create links - hrefBase := fmt.Sprintf("%s%s", gp.Config.Service.Url, path) // /collections - links, _ := provider.CreateLinks("collection "+cn.Identifier, p.String(), hrefBase, "self", ct) - - cihrefBase := fmt.Sprintf("%s/items", hrefBase) - ilinks, _ := provider.CreateLinks("items "+cn.Identifier, p.String(), cihrefBase, "item", ct) - cInfo.Links = append(links, ilinks...) - - for _, c := range gp.Config.Datasource.Collections { - if c.Identifier == cn.Identifier { - if len(c.Links) != 0 { - cInfo.Links = append(cInfo.Links, c.Links...) - } - break - } - } - - p.data = cInfo - break - } - - return p, nil -} - -func (dcp *DescribeCollectionProvider) Provide() (interface{}, error) { - return dcp.data, nil -} - -func (dcp *DescribeCollectionProvider) ContentType() string { - return dcp.contenttype -} - -func (dcp *DescribeCollectionProvider) String() string { - return "describecollection" -} - -func (dcp *DescribeCollectionProvider) SrsId() string { - return "n.a" -} diff --git a/gpkg/getapiprovider.go b/gpkg/getapiprovider.go deleted file mode 100644 index 3a08db3..0000000 --- a/gpkg/getapiprovider.go +++ /dev/null @@ -1,49 +0,0 @@ -package gpkg - -import ( - "log" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" - - "github.com/getkin/kin-openapi/openapi3" -) - -type GetApiProvider struct { - data *openapi3.T - contenttype string -} - -func (gp *GeoPackageProvider) NewGetApiProvider(r *http.Request) (codegen.Provider, error) { - p := &GetApiProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - p.contenttype = ct - - if gp.Api == nil { - log.Printf("Could not get Swagger Specification") - return p, err - } - - p.data = gp.ApiProcessed - return p, nil -} - -func (gap *GetApiProvider) Provide() (interface{}, error) { - return gap.data, nil -} - -func (gap *GetApiProvider) ContentType() string { - return gap.contenttype -} - -func (gap *GetApiProvider) String() string { - return "api" -} - -func (gap *GetApiProvider) SrsId() string { - return "n.a" -} diff --git a/gpkg/getcollectionsprovider.go b/gpkg/getcollectionsprovider.go deleted file mode 100644 index 1c5eb57..0000000 --- a/gpkg/getcollectionsprovider.go +++ /dev/null @@ -1,92 +0,0 @@ -package gpkg - -import ( - "fmt" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -type GetCollectionsProvider struct { - data codegen.Collections - contenttype string -} - -func (gp *GeoPackageProvider) NewGetCollectionsProvider(r *http.Request) (codegen.Provider, error) { - - path := r.URL.Path // collections - p := &GetCollectionsProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - - p.contenttype = ct - - csInfo := codegen.Collections{Links: []codegen.Link{}, Collections: []codegen.Collection{}} - // create Links - hrefBase := fmt.Sprintf("%s%s", gp.Config.Service.Url, path) // /collections - links, _ := provider.CreateLinks("collections", p.String(), hrefBase, "self", ct) - csInfo.Links = append(csInfo.Links, links...) - for _, cn := range gp.GeoPackage.Collections { - clinks, _ := provider.CreateLinks("collection "+cn.Identifier, p.String(), fmt.Sprintf("%s/%s", hrefBase, cn.Identifier), "item", ct) - csInfo.Links = append(csInfo.Links, clinks...) - } - - for _, cn := range gp.GeoPackage.Collections { - - crss := make([]string, 0) - for _, v := range gp.CrsMap { - crss = append(crss, v) - } - - cInfo := codegen.Collection{ - Id: cn.Identifier, - Title: cn.Identifier, - Description: cn.Description, - Crs: crss, - Links: []codegen.Link{}, - } - - chrefBase := fmt.Sprintf("%s/%s", hrefBase, cn.Identifier) - - clinks, _ := provider.CreateLinks("collection "+cn.Identifier, p.String(), chrefBase, "self", ct) - cInfo.Links = append(cInfo.Links, clinks...) - - cihrefBase := fmt.Sprintf("%s/items", chrefBase) - ilinks, _ := provider.CreateLinks("items "+cn.Identifier, p.String(), cihrefBase, "item", ct) - cInfo.Links = append(cInfo.Links, ilinks...) - - for _, c := range gp.Config.Datasource.Collections { - if c.Identifier == cn.Identifier { - if len(c.Links) != 0 { - cInfo.Links = append(cInfo.Links, c.Links...) - } - break - } - } - - csInfo.Collections = append(csInfo.Collections, cInfo) - } - - p.data = csInfo - - return p, nil -} - -func (gp *GetCollectionsProvider) Provide() (interface{}, error) { - return gp.data, nil -} - -func (gp *GetCollectionsProvider) ContentType() string { - return gp.contenttype -} - -func (gp *GetCollectionsProvider) String() string { - return "getcollections" -} - -func (gp *GetCollectionsProvider) SrsId() string { - return "n.a" -} diff --git a/gpkg/getconformancedeclarationprovider.go b/gpkg/getconformancedeclarationprovider.go deleted file mode 100644 index c96192c..0000000 --- a/gpkg/getconformancedeclarationprovider.go +++ /dev/null @@ -1,54 +0,0 @@ -package gpkg - -import ( - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -const ( - core = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core" - oas30 = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30" - html = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/html" - gjson = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" -) - -type GetConformanceDeclarationProvider struct { - data map[string][]string - contenttype string -} - -func (gp *GeoPackageProvider) NewGetConformanceDeclarationProvider(r *http.Request) (codegen.Provider, error) { - - p := &GetConformanceDeclarationProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - - p.contenttype = ct - - d := make(map[string][]string) - d["conformsTo"] = []string{core, oas30, html, gjson} - - p.data = d - - return p, nil -} - -func (gcdp *GetConformanceDeclarationProvider) Provide() (interface{}, error) { - return gcdp.data, nil -} - -func (gcdp *GetConformanceDeclarationProvider) ContentType() string { - return gcdp.contenttype -} - -func (gcdp *GetConformanceDeclarationProvider) String() string { - return "getconformancedeclaration" -} - -func (gcdp *GetConformanceDeclarationProvider) SrsId() string { - return "n.a" -} diff --git a/gpkg/getlandingpageprovider.go b/gpkg/getlandingpageprovider.go deleted file mode 100644 index 37e44ce..0000000 --- a/gpkg/getlandingpageprovider.go +++ /dev/null @@ -1,11 +0,0 @@ -package gpkg - -import ( - "net/http" - "oaf-server/codegen" - pc "oaf-server/provider" -) - -func (gp *GeoPackageProvider) NewGetLandingPageProvider(r *http.Request) (codegen.Provider, error) { - return pc.NewGetLandingPageProvider(gp.Config.Service)(r) -} diff --git a/gpkg/initprovider.go b/gpkg/initprovider.go deleted file mode 100644 index 5990279..0000000 --- a/gpkg/initprovider.go +++ /dev/null @@ -1,51 +0,0 @@ -package gpkg - -import ( - "log" - "oaf-server/provider" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/imdario/mergo" -) - -type GeoPackageProvider struct { - CommonProvider provider.CommonProvider - GeoPackage GeoPackage - CrsMap map[string]string - Config provider.Config - Api *openapi3.T - ApiProcessed *openapi3.T -} - -func NewGeopackageWithCommonProvider(api *openapi3.T, commonProvider provider.CommonProvider, crsMap map[string]string, config provider.Config) *GeoPackageProvider { - return &GeoPackageProvider{ - CommonProvider: commonProvider, - CrsMap: crsMap, - Api: api, - Config: config, - } -} - -func (gp *GeoPackageProvider) Init() (err error) { - gp.GeoPackage, err = NewGeoPackage(gp.Config.Datasource.Geopackage.File, gp.Config.Datasource.Geopackage.Fid) - - collections := gp.Config.Datasource.Collections - - if len(collections) != 0 { - for _, gpkgc := range gp.GeoPackage.Collections { - for _, configc := range collections { - if gpkgc.Identifier == configc.Identifier { - err = mergo.Merge(&configc, gpkgc) - if err != nil { - log.Fatalln(err) - } - } - } - } - } else { - collections = gp.GeoPackage.Collections - } - - gp.ApiProcessed = provider.CreateProvidesSpecificParameters(gp.Api, &collections) - return -} diff --git a/postgis/core.go b/postgis/core.go new file mode 100644 index 0000000..a803425 --- /dev/null +++ b/postgis/core.go @@ -0,0 +1,32 @@ +package postgis + +import ( + "net/http" + "oaf-server/codegen" + "oaf-server/core" +) + +// NewGetLandingPageProvider passes the request to the Core NewGetLandingPageProvider with the Postgis Config +func (pp *PostgisProvider) NewGetLandingPageProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetLandingPageProvider(pp.Config.Service)(r) +} + +// NewGetApiProvider passes the request to the Core NewGetApiProvider with the Postgis Config +func (pp *PostgisProvider) NewGetApiProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetApiProvider(pp.ApiProcessed)(r) +} + +// NewGetConformanceDeclarationProvider passes the request to the Core NewGetConformanceDeclarationProvider with the Postgis Config +func (pp *PostgisProvider) NewGetConformanceDeclarationProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetConformanceDeclarationProvider(pp.ApiProcessed)(r) +} + +// NewGetCollectionsProvider passes the request to the Core NewGetCollectionsProvider with the Postgis Config +func (pp *PostgisProvider) NewGetCollectionsProvider(r *http.Request) (codegen.Provider, error) { + return core.NewGetCollectionsProvider(pp.Config)(r) +} + +// NewDescribeCollectionProvider passes the request to the Core NewDescribeCollectionProvider with the Postgis Config +func (pp *PostgisProvider) NewDescribeCollectionProvider(r *http.Request) (codegen.Provider, error) { + return core.NewDescribeCollectionProvider(pp.Config)(r) +} diff --git a/postgis/describecollectionprovider.go b/postgis/describecollectionprovider.go deleted file mode 100644 index 5419109..0000000 --- a/postgis/describecollectionprovider.go +++ /dev/null @@ -1,82 +0,0 @@ -package postgis - -import ( - "fmt" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -type DescribeCollectionProvider struct { - data codegen.Collection - contenttype string -} - -func (pp *PostgisProvider) NewDescribeCollectionProvider(r *http.Request) (codegen.Provider, error) { - path := r.URL.Path // collections/{{collectionId}} - - collectionId, _ := codegen.ParametersForDescribeCollection(r) - - p := &DescribeCollectionProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - - p.contenttype = ct - - for _, cn := range pp.PostGis.Collections { - // maybe convert to map, but not thread safe! - if cn.Identifier != collectionId { - continue - } - - cInfo := codegen.Collection{ - Id: cn.Identifier, - Title: cn.Identifier, - Description: cn.Description, - Crs: []string{}, - - Links: []codegen.Link{}, - } - - // create links - hrefBase := fmt.Sprintf("%s%s", pp.Config.Service.Url, path) // /collections - links, _ := provider.CreateLinks(collectionId, p.String(), hrefBase, "self", ct) - - cihrefBase := fmt.Sprintf("%s/items", hrefBase) - ilinks, _ := provider.CreateLinks("items of "+collectionId, p.String(), cihrefBase, "item", ct) - cInfo.Links = append(links, ilinks...) - - for _, c := range pp.Config.Datasource.Collections { - if c.Identifier == cn.Identifier { - if len(c.Links) != 0 { - cInfo.Links = append(cInfo.Links, c.Links...) - } - break - } - } - - p.data = cInfo - break - } - - return p, nil -} - -func (dcp *DescribeCollectionProvider) Provide() (interface{}, error) { - return dcp.data, nil -} - -func (dcp *DescribeCollectionProvider) ContentType() string { - return dcp.contenttype -} - -func (dcp *DescribeCollectionProvider) String() string { - return "describecollection" -} - -func (dcp *DescribeCollectionProvider) SrsId() string { - return "n.a." -} diff --git a/postgis/extra_types.go b/postgis/extra_types.go deleted file mode 100644 index d6c510c..0000000 --- a/postgis/extra_types.go +++ /dev/null @@ -1,31 +0,0 @@ -package postgis - -import ( - "oaf-server/codegen" - - "github.com/go-spatial/geom/encoding/geojson" -) - -// this code is generated by go generate -// Not anymore geojson.FeatureType included - -type FeatureCollectionGeoJSON struct { - NumberReturned int64 `json:"numberReturned,omitempty"` - TimeStamp string `json:"timeStamp,omitempty"` - Type string `json:"type"` - Features []*Feature `json:"features"` - Links []codegen.Link `json:"links,omitempty"` - NumberMatched int64 `json:"numberMatched,omitempty"` - Offset int64 -} - -type Feature struct { - Type string `json:"type"` - ID interface{} `json:"id,omitempty"` - // can be null - Geometry geojson.Geometry `json:"geometry"` - // can be null - Properties map[string]interface{} `json:"properties"` - // can be null - Links []codegen.Link `json:"links,omitempty"` -} diff --git a/postgis/getfeatureprovider.go b/postgis/feature.go similarity index 76% rename from postgis/getfeatureprovider.go rename to postgis/feature.go index f420ff5..0fb83c0 100644 --- a/postgis/getfeatureprovider.go +++ b/postgis/feature.go @@ -5,15 +5,18 @@ import ( "fmt" "net/http" "oaf-server/codegen" - "oaf-server/provider" + "oaf-server/core" ) +// GetFeatureProvider is returned by the NewGetFeatureProvider +// containing the data, srsid and contenttype for the response type GetFeatureProvider struct { - data *Feature + data *core.Feature srsid string contenttype string } +// NewGetFeatureProvider handles the request and return the GetFeatureProvider func (pp *PostgisProvider) NewGetFeatureProvider(r *http.Request) (codegen.Provider, error) { collectionId, featureId, _ := codegen.ParametersForGetFeature(r) @@ -25,7 +28,7 @@ func (pp *PostgisProvider) NewGetFeatureProvider(r *http.Request) (codegen.Provi path := r.URL.Path - ct, err := provider.GetContentType(r, p.String()) + ct, err := core.GetContentType(r, p.String()) if err != nil { return nil, err } @@ -60,13 +63,13 @@ func (pp *PostgisProvider) NewGetFeatureProvider(r *http.Request) (codegen.Provi feature := fcGeoJSON.Features[0] hrefBase := fmt.Sprintf("%s%s", pp.Config.Service.Url, path) // /collections - links, _ := provider.CreateFeatureLinks("feature", hrefBase, "self", ct) + links, _ := core.CreateFeatureLinks("feature", hrefBase, "self", ct) feature.Links = links p.data = feature } else { - return p, fmt.Errorf("Feature with id: %s not found", string(featureId)) + return p, fmt.Errorf("feature with id: %s not found", string(featureId)) } return p, nil @@ -75,18 +78,22 @@ func (pp *PostgisProvider) NewGetFeatureProvider(r *http.Request) (codegen.Provi return p, errors.New("Cannot find collection : " + collectionId) } +// Provide provides the data func (gfp *GetFeatureProvider) Provide() (interface{}, error) { return gfp.data, nil } +// ContentType returns the ContentType func (gfp *GetFeatureProvider) ContentType() string { return gfp.contenttype } +// String returns the provider name func (gfp *GetFeatureProvider) String() string { - return "getfeature" + return "feature" } +// SrsId returns the srsid func (gfp *GetFeatureProvider) SrsId() string { return gfp.srsid } diff --git a/postgis/getfeaturesprovider.go b/postgis/features.go similarity index 71% rename from postgis/getfeaturesprovider.go rename to postgis/features.go index 397b402..bf6fba4 100644 --- a/postgis/getfeaturesprovider.go +++ b/postgis/features.go @@ -6,22 +6,25 @@ import ( "log" "net/http" "oaf-server/codegen" - "oaf-server/provider" + "oaf-server/core" ) +// GetFeaturesProvider is returned by the NewGetFeaturesProvider +// containing the data, srsid and contenttype for the response type GetFeaturesProvider struct { - data FeatureCollectionGeoJSON + data core.FeatureCollection srsid string contenttype string } +// NewGetFeaturesProvider handles the request and return the GetFeaturesProvider func (pp *PostgisProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Provider, error) { collectionId, limit, offset, _, bbox, time := codegen.ParametersForGetFeatures(r) - limitParam := provider.ParseLimit(limit, pp.CommonProvider.DefaultReturnLimit, pp.CommonProvider.MaxReturnLimit) - offsetParam := provider.ParseUint(offset, 0) - bboxParam := provider.ParseBBox(bbox, pp.PostGis.BBox) + limitParam := core.ParseLimit(limit, uint64(pp.Config.DefaultFeatureLimit), uint64(pp.Config.MaxFeatureLimit)) + offsetParam := core.ParseUint(offset, 0) + bboxParam := core.ParseBBox(bbox, pp.PostGis.BBox) if time != "" { log.Println("Time selection currently not implemented") @@ -30,7 +33,7 @@ func (pp *PostgisProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Prov path := r.URL.Path // collections/{collectionId}/items p := &GetFeaturesProvider{srsid: fmt.Sprintf("EPSG:%d", pp.PostGis.Srid)} - ct, err := provider.GetContentType(r, p.String()) + ct, err := core.GetContentType(r, p.String()) if err != nil { return nil, err @@ -70,7 +73,7 @@ func (pp *PostgisProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Prov for _, feature := range fcGeoJSON.Features { hrefBase := fmt.Sprintf("%s%s/%v", pp.Config.Service.Url, path, feature.ID) // /collections - links, _ := provider.CreateFeatureLinks("feature", hrefBase, "self", ct) + links, _ := core.CreateFeatureLinks("feature", hrefBase, "self", ct) feature.Links = links } @@ -86,15 +89,15 @@ func (pp *PostgisProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Prov // create links hrefBase := fmt.Sprintf("%s%s", pp.Config.Service.Url, path) // /collections - links, _ := provider.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "self", ct) - _ = provider.ProcesLinksForParams(links, requestParams) + links, _ := core.CreateFeatureLinks("features "+cn.Identifier, hrefBase, "self", ct) + _ = core.ProcesLinksForParams(links, requestParams) // next => offsetParam + limitParam < numbersMatched if (int64(limitParam)) == fcGeoJSON.NumberReturned { - ilinks, _ := provider.CreateFeatureLinks("next features "+cn.Identifier, hrefBase, "next", ct) + ilinks, _ := core.CreateFeatureLinks("next features "+cn.Identifier, hrefBase, "next", ct) requestParams.Set("offset", fmt.Sprintf("%d", fcGeoJSON.Offset)) - _ = provider.ProcesLinksForParams(ilinks, requestParams) + _ = core.ProcesLinksForParams(ilinks, requestParams) links = append(links, ilinks...) } @@ -108,18 +111,22 @@ func (pp *PostgisProvider) NewGetFeaturesProvider(r *http.Request) (codegen.Prov return p, nil } +// Provide provides the data func (gfp *GetFeaturesProvider) Provide() (interface{}, error) { return gfp.data, nil } +// ContentType returns the ContentType func (gfp *GetFeaturesProvider) ContentType() string { return gfp.contenttype } +// String returns the provider name func (gfp *GetFeaturesProvider) String() string { - return "getfeatures" + return "features" } +// SrsId returns the srsid func (gfp *GetFeaturesProvider) SrsId() string { return gfp.srsid } diff --git a/postgis/getapiprovider.go b/postgis/getapiprovider.go deleted file mode 100644 index 95e22cb..0000000 --- a/postgis/getapiprovider.go +++ /dev/null @@ -1,44 +0,0 @@ -package postgis - -import ( - "net/http" - "oaf-server/codegen" - "oaf-server/provider" - - "github.com/getkin/kin-openapi/openapi3" -) - -type GetApiProvider struct { - data *openapi3.T - contenttype string -} - -func (pp *PostgisProvider) NewGetApiProvider(r *http.Request) (codegen.Provider, error) { - p := &GetApiProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - - p.contenttype = ct - - p.data = pp.ApiProcessed - return p, nil -} - -func (gap *GetApiProvider) Provide() (interface{}, error) { - return gap.data, nil -} - -func (gap *GetApiProvider) ContentType() string { - return gap.contenttype -} - -func (gap *GetApiProvider) String() string { - return "api" -} - -func (gap *GetApiProvider) SrsId() string { - return "n.a" -} diff --git a/postgis/getcollectionsprovider.go b/postgis/getcollectionsprovider.go deleted file mode 100644 index ac89c62..0000000 --- a/postgis/getcollectionsprovider.go +++ /dev/null @@ -1,88 +0,0 @@ -package postgis - -import ( - "fmt" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -type GetCollectionsProvider struct { - data codegen.Collections - contenttype string -} - -func (pp *PostgisProvider) NewGetCollectionsProvider(r *http.Request) (codegen.Provider, error) { - - path := r.URL.Path // collections - - p := &GetCollectionsProvider{} - - ct, err := provider.GetContentType(r, p.String()) - if err != nil { - return nil, err - } - - p.contenttype = ct - - csInfo := codegen.Collections{Links: []codegen.Link{}, Collections: []codegen.Collection{}} - // create Links - hrefBase := fmt.Sprintf("%s%s", pp.Config.Service.Url, path) // /collections - links, _ := provider.CreateLinks("collections ", p.String(), hrefBase, "self", ct) - csInfo.Links = append(csInfo.Links, links...) - for _, cn := range pp.PostGis.Collections { - clinks, _ := provider.CreateLinks("collection "+cn.Identifier, p.String(), fmt.Sprintf("%s/%s", hrefBase, cn.Identifier), "item", ct) - csInfo.Links = append(csInfo.Links, clinks...) - } - - for _, cn := range pp.PostGis.Collections { - - cInfo := codegen.Collection{ - Id: cn.Identifier, - Title: cn.Identifier, - Description: cn.Description, - Crs: []string{}, - Links: []codegen.Link{}, - } - - chrefBase := fmt.Sprintf("%s/%s", hrefBase, cn.Identifier) - - clinks, _ := provider.CreateLinks("collection "+cn.Identifier, p.String(), chrefBase, "self", ct) - cInfo.Links = append(cInfo.Links, clinks...) - - cihrefBase := fmt.Sprintf("%s/items", chrefBase) - ilinks, _ := provider.CreateLinks("items "+cn.Identifier, p.String(), cihrefBase, "item", ct) - cInfo.Links = append(cInfo.Links, ilinks...) - - for _, c := range pp.Config.Datasource.Collections { - if c.Identifier == cn.Identifier { - if len(c.Links) != 0 { - cInfo.Links = append(cInfo.Links, c.Links...) - } - break - } - } - - csInfo.Collections = append(csInfo.Collections, cInfo) - } - - p.data = csInfo - - return p, nil -} - -func (gcp *GetCollectionsProvider) Provide() (interface{}, error) { - return gcp.data, nil -} - -func (gcp *GetCollectionsProvider) ContentType() string { - return gcp.contenttype -} - -func (gcp *GetCollectionsProvider) String() string { - return "getcollections" -} - -func (gcp *GetCollectionsProvider) SrsId() string { - return "n.a." -} diff --git a/postgis/getconformancedeclarationprovider.go b/postgis/getconformancedeclarationprovider.go deleted file mode 100644 index 60a94c2..0000000 --- a/postgis/getconformancedeclarationprovider.go +++ /dev/null @@ -1,65 +0,0 @@ -package postgis - -import ( - "errors" - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -const ( - core = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core" - oas30 = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30" - html = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/html" - gjson = "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" -) - -type GetConformanceDeclarationProvider struct { - data map[string][]string - contenttype string -} - -func (pp *PostgisProvider) NewGetConformanceDeclarationProvider(r *http.Request) (codegen.Provider, error) { - p := &GetConformanceDeclarationProvider{} - - ct, err := provider.GetContentType(r, p.String()) - - if err != nil { - return nil, err - } - - p.contenttype = ct - path := r.URL.Path - pathItem := pp.ApiProcessed.Paths.Find(path) - if pathItem == nil { - return p, errors.New("Invalid path :" + path) - } - - for k := range r.URL.Query() { - if notfound := pathItem.Get.Parameters.GetByInAndName("query", k) == nil; notfound { - return p, errors.New("Invalid query parameter :" + k) - } - } - - d := make(map[string][]string) - d["conformsTo"] = []string{core, oas30, html, gjson} - - p.data = d - return p, nil -} - -func (gcdp *GetConformanceDeclarationProvider) Provide() (interface{}, error) { - return gcdp.data, nil -} - -func (gcdp *GetConformanceDeclarationProvider) ContentType() string { - return gcdp.contenttype -} - -func (gcdp *GetConformanceDeclarationProvider) String() string { - return "getconformancedeclaration" -} - -func (gcdp *GetConformanceDeclarationProvider) SrsId() string { - return "n.a." -} diff --git a/postgis/getlandingpageprovider.go b/postgis/getlandingpageprovider.go deleted file mode 100644 index b69096a..0000000 --- a/postgis/getlandingpageprovider.go +++ /dev/null @@ -1,11 +0,0 @@ -package postgis - -import ( - "net/http" - "oaf-server/codegen" - "oaf-server/provider" -) - -func (pp *PostgisProvider) NewGetLandingPageProvider(r *http.Request) (codegen.Provider, error) { - return provider.NewGetLandingPageProvider(pp.Config.Service)(r) -} diff --git a/postgis/initprovider.go b/postgis/initprovider.go index 496ae05..32b91b9 100644 --- a/postgis/initprovider.go +++ b/postgis/initprovider.go @@ -1,33 +1,34 @@ package postgis import ( - "oaf-server/provider" + "oaf-server/core" "github.com/getkin/kin-openapi/openapi3" _ "github.com/mattn/go-sqlite3" ) +// PostgisProvider type PostgisProvider struct { - CommonProvider provider.CommonProvider - PostGis Postgis - CrsMap map[string]string - Config provider.Config - Api *openapi3.T - ApiProcessed *openapi3.T + PostGis Postgis + Config core.Config + Api *openapi3.T + ApiProcessed *openapi3.T } -func NewPostgisWithCommonProvider(api *openapi3.T, commonProvider provider.CommonProvider, config provider.Config) *PostgisProvider { +// NewPostgisWithCommonProvider returns a new PostgisProvider set with the +// given config and OAS3 spec +func NewPostgisWithCommonProvider(api *openapi3.T, config core.Config) *PostgisProvider { p := &PostgisProvider{ - CrsMap: map[string]string{"4326": "http://wfww.opengis.net/def/crs/OGC/1.3/CRS84"}, - Config: config, - CommonProvider: commonProvider, - Api: api, + Config: config, + Api: api, } return p } +// Init initialize the PostGIS database +// and processed the OAS3 spec with the available collections func (pg *PostgisProvider) Init() (err error) { pg.PostGis, err = NewPostgis(pg.Config) - pg.ApiProcessed = provider.CreateProvidesSpecificParameters(pg.Api, &pg.PostGis.Collections) + pg.ApiProcessed = core.CreateProvidesSpecificParameters(pg.Api, &pg.PostGis.Collections) return } diff --git a/postgis/postgis.go b/postgis/postgis.go index baebd21..e4c6cf3 100644 --- a/postgis/postgis.go +++ b/postgis/postgis.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "log" - "oaf-server/provider" + "oaf-server/core" "time" "github.com/go-spatial/geom/encoding/geojson" @@ -14,20 +14,18 @@ import ( _ "github.com/lib/pq" ) -type IdNotFoundError struct { - err string -} - +// Postgis configuration type Postgis struct { ApplicationId string UserVersion string db *sqlx.DB - Collections []provider.Collection + Collections []core.Collection BBox [4]float64 Srid int64 } -func NewPostgis(config provider.Config) (Postgis, error) { +// NewPostgis returns the Postgis build on the given Config +func NewPostgis(config core.Config) (Postgis, error) { postgis := Postgis{} @@ -55,12 +53,14 @@ func NewPostgis(config provider.Config) (Postgis, error) { return postgis, nil } +// Close closes the postgis database func (postgis Postgis) Close() error { return postgis.db.Close() } -func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection provider.Collection, whereMap map[string]string, offset uint64, limit uint64, featureId interface{}, bbox [4]float64) (result FeatureCollectionGeoJSON, err error) { - result = FeatureCollectionGeoJSON{} +// GetFeatures return the FeatureCollection +func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection core.Collection, whereMap map[string]string, offset uint64, limit uint64, featureId interface{}, bbox [4]float64) (result core.FeatureCollection, err error) { + result = core.FeatureCollection{} if len(bbox) > 4 { err = errors.New("bbox with 6 elements not supported") return @@ -140,7 +140,7 @@ func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection result.NumberReturned = 0 result.Type = "FeatureCollection" - result.Features = make([]*Feature, 0) + result.Features = make([]*core.Feature, 0) for rows.Next() { if err = ctx.Err(); err != nil { @@ -160,7 +160,7 @@ func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection return } - feature := &Feature{Type: "Feature", Properties: make(map[string]interface{})} + feature := &core.Feature{Feature: geojson.Feature{Properties: make(map[string]interface{})}} for i, colName := range cols { // check if the context cancelled or timed out @@ -175,7 +175,7 @@ func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection switch colName { case FeatureIDColumn: - ID, err := provider.ConvertFeatureID(vals[i]) + ID, err := core.ConvertFeatureID(vals[i]) if err != nil { return result, err } @@ -257,6 +257,7 @@ func (postgis Postgis) GetFeatures(ctx context.Context, db *sqlx.DB, collection return } +// GetVersion returns a string containing the PostGIS version func (postgis *Postgis) GetVersion(ctx context.Context, db *sqlx.DB) (string, error) { if postgis.UserVersion != "" { diff --git a/provider/initprovider.go b/provider/initprovider.go deleted file mode 100644 index c15b2ce..0000000 --- a/provider/initprovider.go +++ /dev/null @@ -1,16 +0,0 @@ -package provider - -type CommonProvider struct { - ServiceSpecPath string - ServiceEndpoint string - MaxReturnLimit uint64 - DefaultReturnLimit uint64 -} - -func NewCommonProvider(serviceSpecPath string, defaultReturnLimit uint64, maxReturnLimit uint64) CommonProvider { - return CommonProvider{ - ServiceSpecPath: serviceSpecPath, - DefaultReturnLimit: defaultReturnLimit, - MaxReturnLimit: maxReturnLimit, - } -} diff --git a/server/server.go b/server/server.go index 055d197..b7f4380 100644 --- a/server/server.go +++ b/server/server.go @@ -9,13 +9,15 @@ import ( "log" "net/http" "oaf-server/codegen" - "oaf-server/gpkg" - "oaf-server/provider" + "oaf-server/core" "oaf-server/spec" "github.com/getkin/kin-openapi/openapi3" ) +var isTesting = false + +// Server type Server struct { ServiceEndpoint string ServiceSpecPath string @@ -26,6 +28,11 @@ type Server struct { Templates *template.Template } +// NewServer returns a initialized Server +// The returned Server contains: +// - openapi3.T +// - templates +// - set Max and Default feature limits func NewServer(serviceEndpoint, serviceSpecPath string, defaultReturnLimit, maxReturnLimit uint64) (*Server, error) { openapi, err := spec.GetOpenAPI(serviceSpecPath) @@ -38,12 +45,18 @@ func NewServer(serviceEndpoint, serviceSpecPath string, defaultReturnLimit, maxR server := &Server{ServiceEndpoint: serviceEndpoint, ServiceSpecPath: serviceSpecPath, MaxReturnLimit: maxReturnLimit, DefaultReturnLimit: defaultReturnLimit, Openapi: openapi} + templates := `templates/*` + + if isTesting { + templates = `../templates/*` + } + // add templates to server server.Templates = template.Must(template.New("templates").Funcs( template.FuncMap{ "isOdd": func(i int) bool { return i%2 != 0 }, - "hasFeatures": func(i []gpkg.Feature) bool { return len(i) > 0 }, - "upperFirst": provider.UpperFirst, + "hasFeatures": func(i []core.Feature) bool { return len(i) > 0 }, + "upperFirst": core.UpperFirst, "dict": func(values ...interface{}) (map[string]interface{}, error) { if len(values)%2 != 0 { return nil, errors.New("invalid dict call") @@ -58,12 +71,12 @@ func NewServer(serviceEndpoint, serviceSpecPath string, defaultReturnLimit, maxR } return dict, nil }, - //}).ParseGlob("/templates/*")) // prod - }).ParseGlob("templates/*")) // IDE + }).ParseGlob(templates)) return server, nil } +// SetProviders calls the Init() for the configured Provider func (s *Server) SetProviders(providers codegen.Providers) (*Server, error) { err := providers.Init() @@ -75,6 +88,8 @@ func (s *Server) SetProviders(providers codegen.Providers) (*Server, error) { return s, nil } +// HandleForProvider process the given Provider +// And does post-processing regarding the reponse like setting the Content-Type func (s *Server) HandleForProvider(providerFunc func(r *http.Request) (codegen.Provider, error)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { @@ -89,10 +104,10 @@ func (s *Server) HandleForProvider(providerFunc func(r *http.Request) (codegen.P default: jsonError(w, "PROVIDER CREATION", v.Error(), http.StatusNotFound) return - case *provider.InvalidContentTypeError: + case *core.InvalidContentTypeError: jsonError(w, "CLIENT ERROR", v.Error(), http.StatusBadRequest) return - case *provider.InvalidFormatError: + case *core.InvalidFormatError: jsonError(w, "CLIENT ERROR", v.Error(), http.StatusBadRequest) return } @@ -113,13 +128,13 @@ func (s *Server) HandleForProvider(providerFunc func(r *http.Request) (codegen.P var encodedContent []byte - if p.ContentType() == provider.JSONContentType || p.ContentType() == provider.LDJSONContentType || p.ContentType() == provider.GEOJSONContentType { + if p.ContentType() == core.JSONContentType || p.ContentType() == core.LDJSONContentType || p.ContentType() == core.GEOJSONContentType { encodedContent, err = json.Marshal(result) if err != nil { jsonError(w, "JSON MARSHALLER", err.Error(), http.StatusInternalServerError) return } - } else if p.ContentType() == provider.HTMLContentType { + } else if p.ContentType() == core.HTMLContentType { providerID := p.String() rmap := make(map[string]interface{}) diff --git a/server/server_test.go b/server/server_test.go index 87ebf0e..56097bb 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -8,23 +8,25 @@ import ( "net/http" "net/http/httptest" "oaf-server/codegen" - gpkg "oaf-server/gpkg" - "oaf-server/provider" + "oaf-server/core" + "oaf-server/geopackage" + "oaf-server/spec" "testing" _ "github.com/mattn/go-sqlite3" ) -func TestNewServerWithGeopackageProviderForRoot(t *testing.T) { - - crsMap := make(map[string]string) +func init() { + isTesting = true +} +func TestNewServerWithGeopackageProviderForRoot(t *testing.T) { serverEndpoint := "http://testhost:1234" - commonProvider := provider.NewCommonProvider("../spec/oaf.json", 100, 500) - config := provider.Config{Datasource: provider.Datasource{Geopackage: &provider.Geopackage{File: "../example/addresses.gpkg", Fid: "fid"}}} + api, _ := spec.GetOpenAPI("../spec/oaf.json") + config := core.Config{Datasource: core.Datasource{Geopackage: &core.Geopackage{File: "../example/addresses.gpkg", Fid: "fid"}}} - gpkgp := gpkg.NewGeopackageWithCommonProvider(nil, commonProvider, crsMap, config) + gpkgp := geopackage.NewGeopackageWithCommonProvider(api, config) server, _ := NewServer(serverEndpoint, "../spec/oaf.json", 100, 500) server, _ = server.SetProviders(gpkgp) @@ -33,29 +35,41 @@ func TestNewServerWithGeopackageProviderForRoot(t *testing.T) { defer ts.Close() // replace with test endpoint - gpkgp.CommonProvider.ServiceEndpoint = ts.URL + gpkgp.Config.Service.Url = ts.URL tests := []struct { name string path string - want provider.GetLandingPageProvider - check func(want provider.GetLandingPageProvider) error + want core.GetLandingPageProvider + check func(want core.GetLandingPageProvider) error }{ - {"root call", "", provider.GetLandingPageProvider{}, func(want provider.GetLandingPageProvider) error { + {"root call", "", core.GetLandingPageProvider{}, func(want core.GetLandingPageProvider) error { - if len(want.Links) != 8 { + if len(want.Links) != 11 { return errors.New("error invalid number of links") } - rels := []string{"self", "alternate", "service", "service", "conformance", "conformance", "data", "data"} - paths := []string{"?f=json", "?f=html", "/api?f=json", "/api?f=html", "/conformance?f=json", "/conformance?f=html", "/collections?f=json", "/collections?f=html"} + rps := map[string][]string{ + "self": {"?f=json"}, + "alternate": {"?f=html", "?f=jsonld"}, + "service-doc": {"/api?f=html"}, + "service-desc": {"/api?f=json"}, + "conformance": {"/conformance?f=json", "/conformance?f=html", "/conformance?f=jsonld"}, + "data": {"/collections?f=json", "/collections?f=html", "/collections?f=jsonld"}, + } + + found := false + for _, v := range want.Links { - for i, v := range want.Links { - if v.Rel != rels[i] { - return fmt.Errorf("Error invalid link rel: %s", v.Rel) - } + hrefs := rps[v.Rel] + found = false + for _, href := range hrefs { - if v.Href != fmt.Sprintf("%s%s", ts.URL, paths[i]) { + if v.Href == fmt.Sprintf("%s%s", ts.URL, href) { + found = true + } + } + if !found { return fmt.Errorf("Error invalid path rel: %s", v.Href) } } @@ -88,15 +102,12 @@ func TestNewServerWithGeopackageProviderForRoot(t *testing.T) { } func TestNewServerWithGeopackageProviderForCollection(t *testing.T) { - - crsMap := make(map[string]string) - serverEndpoint := "http://testhost:1234" - commonProvider := provider.NewCommonProvider("../spec/oaf.json", 100, 500) - config := provider.Config{Datasource: provider.Datasource{Geopackage: &provider.Geopackage{File: "../example/addresses.gpkg", Fid: "fid"}}} + api, _ := spec.GetOpenAPI("../spec/oaf.json") + config := core.Config{Datasource: core.Datasource{Geopackage: &core.Geopackage{File: "../example/addresses.gpkg", Fid: "fid"}}} - gpkgp := gpkg.NewGeopackageWithCommonProvider(nil, commonProvider, crsMap, config) + gpkgp := geopackage.NewGeopackageWithCommonProvider(api, config) server, _ := NewServer(serverEndpoint, "../spec/oaf.json", 100, 500) server, _ = server.SetProviders(gpkgp) @@ -105,7 +116,7 @@ func TestNewServerWithGeopackageProviderForCollection(t *testing.T) { defer ts.Close() // replace with test endpoint - gpkgp.CommonProvider.ServiceEndpoint = ts.URL + gpkgp.Config.Service.Url = ts.URL tests := []struct { name string diff --git a/spec/specificationloader.go b/spec/specificationloader.go index 6230df7..675e76c 100644 --- a/spec/specificationloader.go +++ b/spec/specificationloader.go @@ -6,6 +6,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) +// GetOpenAPI reads a file containing the OAS3 spec and return the openapi3.T struct func GetOpenAPI(serviceSpecPath string) (*openapi3.T, error) { loader := openapi3.NewLoader() @@ -15,8 +16,6 @@ func GetOpenAPI(serviceSpecPath string) (*openapi3.T, error) { if err != nil { log.Printf("Cannot Loadswagger from file :%s", serviceSpecPath) } - // tweak for missing schemaƛ reference to geojson - //swagger.Components.Schemas["geometryGeoJSON"] = &openapi3.SchemaRef{Ref: "http://geojson.org/schema/Geometry.json"} - //swagger.Components.Schemas["featureGeoJSON"] = &openapi3.SchemaRef{Ref: "http://geojson.org/schema/Feature.json"} + return openapi, err } diff --git a/start.go b/start.go index 85d66d0..825b23e 100644 --- a/start.go +++ b/start.go @@ -1,89 +1,122 @@ package main import ( - "flag" "fmt" - "io/ioutil" "log" "net/http" "oaf-server/codegen" - "oaf-server/gpkg" + "oaf-server/core" + "oaf-server/geopackage" "oaf-server/postgis" - "oaf-server/provider" "oaf-server/server" "os" "regexp" - "strconv" "github.com/getkin/kin-openapi/openapi3" - "gopkg.in/yaml.v2" + "github.com/urfave/cli/v2" "github.com/rs/cors" ) func main() { - bindHost := flag.String("s", envString("BIND_HOST", "0.0.0.0"), "server internal bind address, default; 0.0.0.0") - bindPort := flag.Int("p", envInt("BIND_PORT", 8080), "server internal bind address, default; 8080") + app := cli.NewApp() + app.Name = "GOAF" + app.Usage = "A Golang OGC API Features implementation" + app.HideVersion = true + app.HideHelp = true + + app.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "host", + Aliases: []string{"h"}, + Usage: "server internal bind host address", + DefaultText: "0.0.0.0", + Required: false, + EnvVars: []string{"HOST"}, + }, + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "server internal bind port", + DefaultText: "8080", + Required: false, + EnvVars: []string{"PORT"}, + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Path to the configuration", + Required: true, + EnvVars: []string{"CONFIG"}, + }, + } - configfilepath := flag.String("c", envString("CONFIG", ""), "configfile path") - flag.Parse() + app.Action = func(c *cli.Context) error { - config := &provider.Config{} - config.ReadConfig(*configfilepath) + configfilepath := c.String("config") + config := &core.Config{} + config.ReadConfig(configfilepath) - // stage 1: create server with spec path and limits - apiServer, err := server.NewServer(config.Service.Url, config.Openapi, uint64(config.DefaultFeatureLimit), uint64(config.MaxFeatureLimit)) - if err != nil { - log.Fatal("Server initialisation error:", err) - } + // stage 1: create server with spec path and limits + apiServer, err := server.NewServer(config.Service.Url, config.Openapi, uint64(config.DefaultFeatureLimit), uint64(config.MaxFeatureLimit)) + if err != nil { + log.Fatal("Server initialisation error:", err) + } - // stage 2: Create providers based upon provider name - commonProvider := provider.NewCommonProvider(config.Openapi, uint64(config.DefaultFeatureLimit), uint64(config.MaxFeatureLimit)) - // providers := getProvider(apiServer.Openapi, providerName, commonProvider, crsMapFilePath, gpkgFilePath, featureIdKey, configFilePath, connectionStr) + // stage 2: Create providers based upon provider name + // commonProvider := core.NewCommonProvider(config.Openapi, uint64(config.DefaultFeatureLimit), uint64(config.MaxFeatureLimit)) + providers := getProvider(apiServer.Openapi, *config) - providers := getProvider(apiServer.Openapi, commonProvider, *config) + if providers == nil { + log.Fatal("Incorrect provider provided valid names are: gpkg, postgis") + } - if providers == nil { - log.Fatal("Incorrect provider provided valid names are: gpkg, postgis") - } + // stage 3: Add providers, also initialises them + apiServer, err = apiServer.SetProviders(providers) + if err != nil { + log.Fatal("Server initialisation error:", err) + } - // stage 3: Add providers, also initialises them - apiServer, err = apiServer.SetProviders(providers) - if err != nil { - log.Fatal("Server initialisation error:", err) - } + // stage 4: Prepare routing + router := apiServer.Router() - // stage 4: Prepare routing - router := apiServer.Router() + // extra routing for healthcheck + addHealthHandler(router) - // extra routing for healthcheck - addHealthHandler(router) + fs := http.FileServer(http.Dir("swagger-ui")) + router.Handler(regexp.MustCompile("/swagger-ui"), http.StripPrefix("/swagger-ui/", fs)) - fs := http.FileServer(http.Dir("swagger-ui")) - router.Handler(regexp.MustCompile("/swagger-ui"), http.StripPrefix("/swagger-ui/", fs)) + // cors handler + handler := cors.Default().Handler(router) - // cors handler - handler := cors.Default().Handler(router) + host := c.String("host") + port := c.String("port") - // ServerEndpoint can be different from bind address due to routing externally - bindAddress := fmt.Sprintf("%v:%v", *bindHost, *bindPort) + bindAddress := "0.0.0.0:8080" + if host != "" && port != "" { + bindAddress = fmt.Sprintf("%v:%v", host, port) + } - log.Print("|\n") - log.Printf("| SERVING ON: %s \n", apiServer.ServiceEndpoint) + // ServerEndpoint can be different from bind address due to routing externally + log.Print("|\n") + log.Printf("| SERVING ON: %s \n", apiServer.ServiceEndpoint) - // stage 5: Start server - if err := http.ListenAndServe(bindAddress, handler); err != nil { - log.Fatal("ListenAndServe:", err) + // stage 5: Start server + return http.ListenAndServe(bindAddress, handler) } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } -func getProvider(api *openapi3.T, commonProvider provider.CommonProvider, config provider.Config) codegen.Providers { +func getProvider(api *openapi3.T, config core.Config) codegen.Providers { if config.Datasource.Geopackage != nil { - return addGeopackageProviders(api, commonProvider, "", config) + return geopackage.NewGeopackageWithCommonProvider(api, config) } else if config.Datasource.PostGIS != nil { - return postgis.NewPostgisWithCommonProvider(api, commonProvider, config) + return postgis.NewPostgisWithCommonProvider(api, config) } return nil @@ -98,45 +131,3 @@ func addHealthHandler(router *server.RegexpHandler) { } }) } - -func addGeopackageProviders(api *openapi3.T, commonProvider provider.CommonProvider, crsMapFilePath string, config provider.Config) *gpkg.GeoPackageProvider { - crsMap := make(map[string]string) - csrMapFile, err := ioutil.ReadFile(crsMapFilePath) - if err != nil { - log.Printf("Could not read crsmap file: %s, using default CRS Map", crsMapFilePath) - } else { - err := yaml.Unmarshal(csrMapFile, &crsMap) - log.Print(crsMap) - if err != nil { - log.Printf("Could not unmarshal crsmap file: %s, using default CRS Map", crsMapFilePath) - } - } - - if crsMap[`4326`] == `` { - crsMap[`4326`] = `http://www.opengis.net/def/crs/OGC/1.3/CRS84` - } - - return gpkg.NewGeopackageWithCommonProvider(api, commonProvider, crsMap, config) -} - -func envString(key, defaultValue string) string { - value := os.Getenv(key) - if value != "" { - return value - } - - return defaultValue -} - -func envInt(key string, defaultValue int) int { - value := os.Getenv(key) - if value != "" { - i, e := strconv.ParseInt(value, 10, 32) - if e != nil { - return defaultValue - } - return int(i) - } - - return defaultValue -} diff --git a/templates/describecollection.html b/templates/collection.html similarity index 100% rename from templates/describecollection.html rename to templates/collection.html diff --git a/templates/getcollections.html b/templates/collections.html similarity index 100% rename from templates/getcollections.html rename to templates/collections.html diff --git a/templates/getconformancedeclaration.html b/templates/conformance.html similarity index 100% rename from templates/getconformancedeclaration.html rename to templates/conformance.html diff --git a/templates/getfeature.html b/templates/feature.html similarity index 100% rename from templates/getfeature.html rename to templates/feature.html diff --git a/templates/getfeatures.html b/templates/features.html similarity index 100% rename from templates/getfeatures.html rename to templates/features.html