Mercurial > gemma
view pkg/wfs/capabilities.go @ 3488:aa264021e2bf zpg-ldc
merged default into zpg-ldc branch.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 27 May 2019 16:38:07 +0200 |
parents | 02505fcff63c |
children | 04876d865528 |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package wfs import ( "encoding/xml" "errors" "io" "regexp" "strconv" "strings" "golang.org/x/net/html/charset" ) // Keyword stores a value. type Keyword struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Keyword"` Value string `xml:",cdata"` } // Keywords stores a list of keywords. type Keywords struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Keywords"` Keywords []Keyword `xml:"Keyword"` } // ServiceIdentification contains meta informations about a WFS. type ServiceIdentification struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 ServiceIdentification"` Title string Abstract string Keywords Keywords `xml:"Keywords"` ServiceType string ServiceTypeVersion string } // Get stores the link to the GET method type Get struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Get"` HRef string `xml:"http://www.w3.org/1999/xlink href,attr"` } // Post stores the link to the POST method. type Post struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Post"` HRef string `xml:"http://www.w3.org/1999/xlink href,attr"` } // HTTP is a container for HTTP methods. type HTTP struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 HTTP"` Get *Get `xml:"Get"` Post *Post `xml:"Post"` } // DCP wraps the HTTP container. type DCP struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 DCP"` HTTP HTTP `xml:"HTTP"` } // Value is a simple string value. type Value struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Value"` Value string `xml:",cdata"` } // AllowedValues is list positive list of values. type AllowedValues struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 AllowedValues"` Values []Value `xml:"Value"` } // Parameter is a named parameter with a list of allowed values. type Parameter struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Parameter"` Name string `xml:"name,attr"` AllowedValues AllowedValues `xml:"AllowedValues"` } // DefaultValue is the default value of a constraint. type DefaultValue struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 DefaultValue"` Value string `xml:",cdata"` } // Constraint is a named constraint with a list of allowed values // and a default value. type Constraint struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Constraint"` Name string `xml:"name,attr"` AllowedValues AllowedValues `xml:"AllowedValues"` DefaultValue *DefaultValue `xml:"DefaultValue"` } // Operation contains informations of a WFS operation. type Operation struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Operation"` Name string `xml:"name,attr"` DCP DCP `xml:"DCP"` Parameters []*Parameter `xml:"Parameter"` Constraints []*Constraint `xml:"Constraint"` } // OperationsMetadata is list of operations and constraints. type OperationsMetadata struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 OperationsMetadata"` Operations []*Operation `xml:"Operation"` Constraints []*Constraint `xml:"Constraint"` } // WGS84BoundingBox is a bounding box feature type in WGS84. type WGS84BoundingBox struct { XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 WGS84BoundingBox"` LowerCorner string `xml:"LowerCorner"` UpperCorner string `xml:"UpperCorner"` } // FeatureType is layer served by the WFS: type FeatureType struct { XMLName xml.Name `xml:"http://www.opengis.net/wfs/2.0 FeatureType"` Name string `xml:"Name"` Title string `xml:"Title"` Abstract string `xml:"Abstract"` Keywords Keywords `xml:"Keywords"` DefaultCRS string `xml:"DefaultCRS"` OtherCRSs []string `xml:"OtherCRS"` WGS84BoundingBox *WGS84BoundingBox `xml:"WGS84BoundingBox"` Namespaces []xml.Name `xml:"-"` } // shadowFeatureType is used to prevent recursive UnmarshalXML for FeatureType. type shadowFeatureType struct { XMLName xml.Name `xml:"http://www.opengis.net/wfs/2.0 FeatureType"` Name string `xml:"Name"` Title string `xml:"Title"` Abstract string `xml:"Abstract"` Keywords Keywords `xml:"Keywords"` DefaultCRS string `xml:"DefaultCRS"` OtherCRSs []string `xml:"OtherCRS"` WGS84BoundingBox *WGS84BoundingBox `xml:"WGS84BoundingBox"` Namespaces []xml.Name `xml:"-"` } // UnmarshalXML implements xml.Unmarshaler for better namespace handling. func (ft *FeatureType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // Filter out the namespaces for this feature type. var ns []xml.Name for _, attr := range start.Attr { if attr.Name.Space == "xmlns" { ns = append(ns, xml.Name{Space: attr.Name.Local, Local: attr.Value}) } } var sft shadowFeatureType if err := d.DecodeElement(&sft, &start); err != nil { return err } *ft = FeatureType(sft) ft.Namespaces = ns return nil } // FeatureTypeList is the list of layers served by the WFS. type FeatureTypeList struct { XMLName xml.Name `xml:"http://www.opengis.net/wfs/2.0 FeatureTypeList"` FeatureTypes []*FeatureType `xml:"FeatureType"` } // Capabilities is the top level metadata struct. type Capabilities struct { XMLName xml.Name `xml:"http://www.opengis.net/wfs/2.0 WFS_Capabilities"` BaseURL string `xml:"-"` ServiceIdentification ServiceIdentification OperationsMetadata OperationsMetadata FeatureTypeList FeatureTypeList } // FindOperation searches the capabilities for a specifc operation. // Returns nil if not found. func (c *Capabilities) FindOperation(name string) *Operation { for _, op := range c.OperationsMetadata.Operations { if op.Name == name { return op } } return nil } // SupportsHits checks if a operation supports the hits request. func (op *Operation) SupportsHits() bool { for _, p := range op.Parameters { if p.Name == "resultType" { for _, av := range p.AllowedValues.Values { if av.Value == "hits" { return true } } } } return false } // SupportsOutputFormat checks if one of the given formats is supported. func (op *Operation) SupportsOutputFormat(formats ...string) string { for _, p := range op.Parameters { if p.Name == "outputFormat" { for _, av := range p.AllowedValues.Values { v := strings.ToLower(av.Value) for _, f := range formats { if v == strings.ToLower(f) { return av.Value } } } } } return "" } // FeaturesPerPage returns the number of features per page. // Returns if paging is not supported by the operation. func (op *Operation) FeaturesPerPage() (int, bool) { for _, c := range op.Constraints { if c.Name == "CountDefault" { if c.DefaultValue != nil { if v, err := strconv.Atoi(c.DefaultValue.Value); err == nil { return v, true } } for _, av := range c.AllowedValues.Values { if v, err := strconv.Atoi(av.Value); err == nil { return v, true } } } } return 0, false } // FindFeatureType searches the layers for a given name. // Returns nil if not found. func (c *Capabilities) FindFeatureType(name string) *FeatureType { for _, ft := range c.FeatureTypeList.FeatureTypes { if ft.Name == name { return ft } } return nil } // FindParameter searches for named parameter. Returns nil // if not found. func (op *Operation) FindParameter(name string) *Parameter { for _, p := range op.Parameters { if p.Name == name { return p } } return nil } // WFS200 is dotted version string of version 2.0.0. const WFS200 = "2.0.0" var versionRe = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`) func versionIsLess(a, b string) bool { am := versionRe.FindStringSubmatch(a) bm := versionRe.FindStringSubmatch(b) var n int if len(am) < len(bm) { n = len(am) } else { n = len(bm) } n-- for i := 0; i < n; i++ { ai, _ := strconv.Atoi(am[i+1]) bi, _ := strconv.Atoi(bm[i+1]) switch { case ai < bi: return true case ai > bi: return false } } return false } func maxVersion(a, b string) string { am := versionRe.FindStringSubmatch(a) bm := versionRe.FindStringSubmatch(b) var n int if len(am) < len(bm) { n = len(am) } else { n = len(bm) } n-- for i := 0; i < n; i++ { ai, _ := strconv.Atoi(am[i+1]) bi, _ := strconv.Atoi(bm[i+1]) switch { case ai > bi: return a case bi > ai: return b } } return a } // HighestWFSVersion figures out the highest supported WFS version. // Defaults to def. func (c *Capabilities) HighestWFSVersion(def string) string { op := c.FindOperation("GetCapabilities") if op == nil { return def } p := op.FindParameter("AcceptVersions") if p == nil { return def } if len(p.AllowedValues.Values) == 0 { return def } max := p.AllowedValues.Values[0].Value for _, v := range p.AllowedValues.Values[1:] { max = maxVersion(max, v.Value) } return max } var ( // ErrInvalidCRS is returned if a given string is not valid CRS URN. ErrInvalidCRS = errors.New("Invalid CRS string") crsRe = regexp.MustCompile(`urn:ogc:def:crs:EPSG:[^:]*:(\d+)`) ) // CRSToEPSG extracts the EPSG code from a given CRS URN string. func CRSToEPSG(s string) (int, error) { m := crsRe.FindStringSubmatch(s) if m == nil { return 0, ErrInvalidCRS } return strconv.Atoi(m[1]) } // ParseCapabilities constructs a capabilities document from an io.Reader. func ParseCapabilities(r io.Reader) (*Capabilities, error) { decoder := xml.NewDecoder(r) decoder.CharsetReader = charset.NewReaderLabel var capabilities Capabilities if err := decoder.Decode(&capabilities); err != nil { return nil, err } return &capabilities, nil }