diff --git a/pkg/wfs200/capabilities.go b/pkg/wfs200/capabilities.go index d09e083..ea36d12 100644 --- a/pkg/wfs200/capabilities.go +++ b/pkg/wfs200/capabilities.go @@ -123,12 +123,12 @@ type FeatureTypeList struct { // FeatureType struct for the WFS 2.0.0 type FeatureType struct { - Name string `xml:"Name" yaml:"name"` - Title string `xml:"Title" yaml:"title"` - Abstract string `xml:"Abstract" yaml:"abstract"` - Keywords *wsc110.Keywords `xml:"ows:Keywords" yaml:"keywords"` - DefaultCRS *wsc110.CRS `xml:"DefaultCRS" yaml:"defaultcrs"` - OtherCRS *[]wsc110.CRS `xml:"OtherCRS" yaml:"othercrs"` + Name string `xml:"Name" yaml:"name"` + Title string `xml:"Title" yaml:"title"` + Abstract string `xml:"Abstract" yaml:"abstract"` + Keywords *[]wsc110.Keywords `xml:"ows:Keywords" yaml:"keywords"` + DefaultCRS *CRS `xml:"DefaultCRS" yaml:"defaultcrs"` + OtherCRS *[]CRS `xml:"OtherCRS" yaml:"othercrs"` OutputFormats struct { Format []string `xml:"Format" yaml:"format"` } `xml:"OutputFormats" yaml:"outputformats"` diff --git a/pkg/wfs200/crs.go b/pkg/wfs200/crs.go new file mode 100644 index 0000000..52a2693 --- /dev/null +++ b/pkg/wfs200/crs.go @@ -0,0 +1,44 @@ +package wfs200 + +import ( + "regexp" + "strconv" +) + +// +const ( + codeSpace = `urn:ogc:def:crs:EPSG:` +) + +// CRS struct with namespace/authority/registry and code +type CRS struct { + Namespace string //TODO maybe AuthorityType is a better name...? + Code int +} + +// String of the EPSGCode +func (c *CRS) String() string { + return c.Namespace + `:` + strconv.Itoa(c.Code) +} + +// Identifier returns the EPSG +func (c *CRS) Identifier() string { + return codeSpace + strconv.Itoa(c.Code) +} + +// ParseString build CRS struct from input string +func (c *CRS) ParseString(s string) { + c.parseString(s) +} + +func (c *CRS) parseString(s string) { + regex := regexp.MustCompile(`(^.*):([0-9]+)`) + code := regex.FindStringSubmatch(s) + if len(code) == 3 { // code[0] is the full match, the other the parts + c.Namespace = codeSpace + + // the regex already checks if it [0-9] + i, _ := strconv.Atoi(code[2]) + c.Code = i + } +} diff --git a/pkg/wfs200/crs_test.go b/pkg/wfs200/crs_test.go new file mode 100644 index 0000000..9175737 --- /dev/null +++ b/pkg/wfs200/crs_test.go @@ -0,0 +1,60 @@ +package wfs200 + +import ( + "testing" + + "gopkg.in/yaml.v3" +) + +func TestCRSParseString(t *testing.T) { + var tests = []struct { + input string + expectedCRS CRS + }{ + 0: {}, // Empty input == empty struct + 1: {input: `urn:ogc:def:crs:EPSG::4326`, expectedCRS: CRS{Code: 4326, Namespace: `urn:ogc:def:crs:EPSG:`}}, + 2: {input: `EPSG:4326`, expectedCRS: CRS{Code: 4326, Namespace: `urn:ogc:def:crs:EPSG:`}}, + } + + for k, test := range tests { + var crs CRS + crs.parseString(test.input) + + if crs.Code != test.expectedCRS.Code || crs.Namespace != test.expectedCRS.Namespace { + t.Errorf("test: %d, expected: %v,\n got: %v", k, test.expectedCRS, crs) + } + } +} + +func TestUnmarshalYAMLCrs(t *testing.T) { + + var stringYAML = `defaultcrs: urn:ogc:def:crs:EPSG::4326 +othercrs: +- EPSG:4258 +- urn:ogc:def:crs:EPSG::3857` + + type FeatureType struct { + DefaultCRS CRS `yaml:"defaultcrs"` + OtherCRS []CRS `yaml:"othercrs"` + } + + var tests = []struct { + yaml []byte + expectedcrs CRS + }{ + 0: {yaml: []byte(stringYAML), expectedcrs: CRS{Code: 4326, Namespace: codeSpace}}, + 1: {yaml: []byte(`defaultcrs: urn:ogc:def:crs:EPSG::4326`), expectedcrs: CRS{Code: 4326, Namespace: codeSpace}}, + 2: {yaml: []byte(`defaultcrs: EPSG:4326`), expectedcrs: CRS{Code: 4326, Namespace: codeSpace}}, + } + for k, test := range tests { + var ftl FeatureType + err := yaml.Unmarshal(test.yaml, &ftl) + if err != nil { + t.Errorf("test: %d, yaml.UnMarshal failed with '%s'\n", k, err) + } else { + if ftl.DefaultCRS.Code != test.expectedcrs.Code || ftl.DefaultCRS.Namespace != test.expectedcrs.Namespace { + t.Errorf("test: %d, expected: %v+,\n got: %v+", k, test.expectedcrs, ftl.DefaultCRS) + } + } + } +} diff --git a/pkg/wfs200/crs_xml.go b/pkg/wfs200/crs_xml.go new file mode 100644 index 0000000..9238966 --- /dev/null +++ b/pkg/wfs200/crs_xml.go @@ -0,0 +1,36 @@ +package wfs200 + +import ( + "encoding/xml" + "fmt" +) + +// MarshalXML Position +func (c *CRS) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + var s = `` + if c.Namespace != `` { + s = fmt.Sprintf("%s:%d", c.Namespace, c.Code) + } + + return e.EncodeElement(s, start) +} + +// UnmarshalXML Position +func (c *CRS) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var crs CRS + for { + token, err := d.Token() + if err != nil { + return err + } + switch el := token.(type) { + case xml.CharData: + crs.parseString(string([]byte(el))) + case xml.EndElement: + if el == start.End() { + *c = crs + return nil + } + } + } +} diff --git a/pkg/wfs200/crs_yaml.go b/pkg/wfs200/crs_yaml.go new file mode 100644 index 0000000..f040a09 --- /dev/null +++ b/pkg/wfs200/crs_yaml.go @@ -0,0 +1,16 @@ +package wfs200 + +// UnmarshalYAML CRS +func (c *CRS) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + + var crs CRS + crs.parseString(s) + + *c = crs + + return nil +} diff --git a/pkg/wms130/capabilities.go b/pkg/wms130/capabilities.go index fbcd069..cbb3a48 100644 --- a/pkg/wms130/capabilities.go +++ b/pkg/wms130/capabilities.go @@ -211,7 +211,7 @@ func (c *Capabilities) GetLayer(layername string) (Layer, Exceptions) { // RequestType containing the formats and DCPTypes available type RequestType struct { Format []string `xml:"Format" yaml:"format"` - DCPType DCPType `xml:"DCPType" yaml:"dcptype"` + DCPType *DCPType `xml:"DCPType" yaml:"dcptype"` } // Identifier in struct for repeatability diff --git a/pkg/wms130/getmap_benchmark_test.go b/pkg/wms130/getmap_benchmark_test.go index 585b84e..f54a8cc 100644 --- a/pkg/wms130/getmap_benchmark_test.go +++ b/pkg/wms130/getmap_benchmark_test.go @@ -159,7 +159,7 @@ func BenchmarkGetMapValidate(b *testing.B) { Request: Request{ GetMap: RequestType{ Format: []string{`image/jpeg`}, - DCPType: DCPType{}, + DCPType: &DCPType{}, }, }, Layer: []Layer{ @@ -251,7 +251,7 @@ func BenchmarkGetMapParseValidate(b *testing.B) { Request: Request{ GetMap: RequestType{ Format: []string{`image/jpeg`}, - DCPType: DCPType{}, + DCPType: &DCPType{}, }, }, Layer: []Layer{ diff --git a/pkg/wms130/getmap_test.go b/pkg/wms130/getmap_test.go index efe1ffc..b2d861e 100644 --- a/pkg/wms130/getmap_test.go +++ b/pkg/wms130/getmap_test.go @@ -328,18 +328,18 @@ func TestGetMapParseQueryParameters(t *testing.T) { BGCOLOR: {`0x7F7F7F`}, }, exception: InvalidParameterValue(`zzzz`, TRANSPARENT), - }, + }, //REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&LAYERS=Rivers,Roads,Houses&STYLES=CenterLine,CenterLine,Outline&CRS=EPSG:4326&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=1024&HEIGHT=512&FORMAT=image/jpeg&EXCEPTIONS=XML 4: {query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, - LAYERS: {`Rivers,Roads,Houses`}, - STYLES: {`CenterLine,CenterLine,Outline`}, - "CRS": {`EPSG:4326`}, - BBOX: {`-180.0,-90.0,180.0,90.0`}, - WIDTH: {`1024`}, - HEIGHT: {`512`}, - FORMAT: {`image/jpeg`}, - EXCEPTIONS: {`XML`}, - BGCOLOR: {`0x7F7F7F`}, + LAYERS: {`Rivers,Roads,Houses`}, + STYLES: {`CenterLine,CenterLine,Outline`}, + "CRS": {`EPSG:4326`}, + BBOX: {`-180.0,-90.0,180.0,90.0`}, + WIDTH: {`1024`}, + HEIGHT: {`512`}, + FORMAT: {`image/jpeg`}, + EXCEPTIONS: {`XML`}, + BGCOLOR: {`0x7F7F7F`}, }, excepted: GetMapRequest{ BaseRequest: BaseRequest{ @@ -365,15 +365,15 @@ func TestGetMapParseQueryParameters(t *testing.T) { }}, //REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&LAYERS=Rivers&STYLES=&CRS=EPSG:4326&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=1024&HEIGHT=512&FORMAT=image/jpeg&EXCEPTIONS=XML 5: {query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, - LAYERS: {`Rivers`}, - STYLES: {``}, - "CRS": {`EPSG:4326`}, - BBOX: {`-180.0,-90.0,180.0,90.0`}, - WIDTH: {`1024`}, - HEIGHT: {`512`}, - FORMAT: {`image/jpeg`}, - EXCEPTIONS: {`XML`}, - BGCOLOR: {`0x7F7F7F`}, + LAYERS: {`Rivers`}, + STYLES: {``}, + "CRS": {`EPSG:4326`}, + BBOX: {`-180.0,-90.0,180.0,90.0`}, + WIDTH: {`1024`}, + HEIGHT: {`512`}, + FORMAT: {`image/jpeg`}, + EXCEPTIONS: {`XML`}, + BGCOLOR: {`0x7F7F7F`}, }, excepted: GetMapRequest{ BaseRequest: BaseRequest{ @@ -666,7 +666,7 @@ func TestGetMapValidate(t *testing.T) { Request: Request{ GetMap: RequestType{ Format: []string{`image/jpeg`}, - DCPType: DCPType{}, + DCPType: &DCPType{}, }, }, Layer: []Layer{ diff --git a/pkg/wmts100/capabilities_test.go b/pkg/wmts100/capabilities_test.go index 47936c7..9c4cc76 100644 --- a/pkg/wmts100/capabilities_test.go +++ b/pkg/wmts100/capabilities_test.go @@ -51,8 +51,8 @@ var contentsWithLegend = Contents{ func TestBuildStyleWithLegend(t *testing.T) { expected := `` output, _ := xml.MarshalIndent(contentsWithLegend.Layer[0].Style, "", " ") @@ -91,8 +91,8 @@ var contentsWithoutLegend = Contents{ func TestBuildStyleWithoutLegend(t *testing.T) { expected := `` output, _ := xml.MarshalIndent(contentsWithoutLegend.Layer[0].Style, "", " ") if string(output) != expected { diff --git a/pkg/wmts100/getcapabilities_response.go b/pkg/wmts100/getcapabilities_response.go index 45ea9bc..18d51a6 100644 --- a/pkg/wmts100/getcapabilities_response.go +++ b/pkg/wmts100/getcapabilities_response.go @@ -38,11 +38,11 @@ func (gc GetCapabilitiesResponse) ToXML() []byte { type GetCapabilitiesResponse struct { XMLName xml.Name `xml:"Capabilities"` Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider wsc110.ServiceProvider `xml:"ows:ServiceProvider,omitempty" yaml:"serviceprovider"` - OperationsMetadata *OperationsMetadata `xml:"ows:OperationsMetadata,omitempty" yaml:"operationsmetadata"` - Contents Contents `xml:"Contents" yaml:"contents"` - ServiceMetadataURL *ServiceMetadataURL `xml:"ServiceMetadataURL,omitempty" yaml:"servicemetadataurl"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider *wsc110.ServiceProvider `xml:"ows:ServiceProvider,omitempty" yaml:"serviceprovider"` + OperationsMetadata *OperationsMetadata `xml:"ows:OperationsMetadata,omitempty" yaml:"operationsmetadata"` + Contents Contents `xml:"Contents" yaml:"contents"` + ServiceMetadataURL *ServiceMetadataURL `xml:"ServiceMetadataURL,omitempty" yaml:"servicemetadataurl"` } // Namespaces struct containing the namespaces needed for the XML document @@ -97,13 +97,13 @@ type Method struct { // ServiceIdentification struct should only be fill by the "template" configuration wmts100.yaml type ServiceIdentification struct { - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords wsc110.Keywords `xml:"ows:Keywords,omitempty" yaml:"keywords"` - ServiceType string `xml:"ows:ServiceType" yaml:"servicetype"` - ServiceTypeVersion string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` - Fees string `xml:"ows:Fees" yaml:"fees"` - AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords *wsc110.Keywords `xml:"ows:Keywords,omitempty" yaml:"keywords"` + ServiceType string `xml:"ows:ServiceType" yaml:"servicetype"` + ServiceTypeVersion string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` + Fees string `xml:"ows:Fees" yaml:"fees"` + AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` } // ServiceMetadataURL in struct for repeatability diff --git a/pkg/wsc110/keywords.go b/pkg/wsc110/keywords.go index 3077915..7eeb266 100644 --- a/pkg/wsc110/keywords.go +++ b/pkg/wsc110/keywords.go @@ -2,6 +2,9 @@ package wsc110 // Keywords in struct for repeatability type Keywords struct { - Keyword []string `xml:"ows:Keyword" yaml:"keyword"` - Type string `xml:"ows:Type,omitempty" yaml:"type"` + Keyword []string `xml:"ows:Keyword"` + Type *struct { + Text string `xml:",chardata"` + CodeSpace *string `xml:"codeSpace,attr,omitempty"` + } `xml:"ows:Type"` }