Skip to content

Commit

Permalink
Merge pull request #3 from PDOK/validation-test
Browse files Browse the repository at this point in the history
major refactor
  • Loading branch information
WouterVisscher authored Aug 14, 2020
2 parents d57c4a4 + 10fdcbc commit df471eb
Show file tree
Hide file tree
Showing 45 changed files with 4,418 additions and 3,189 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
![GitHub license](https://img.shields.io/github/license/PDOK/ogc-specifications)
[![GitHub release](https://img.shields.io/github/release/PDOK/ogc-specifications.svg)](https://github.com/PDOK/ogc-specifications/releases)
[![Go Report Card](https://goreportcard.com/badge/PDOK/ogc-specifications)](https://goreportcard.com/report/PDOK/ogc-specifications)

# ogc-specifications

The package ogc-specifications is a implementation of the OGC Webservice Specifications as defined by the [OGC](https://www.ogc.org/).
This package has support for the following OGC Webservice Specifications:
![GitHub license](https://img.shields.io/github/license/PDOK/ogc-specifications)
[![GitHub release](https://img.shields.io/github/release/PDOK/ogc-specifications.svg)](https://github.com/PDOK/ogc-specifications/releases)
[![Go Report Card](https://goreportcard.com/badge/PDOK/ogc-specifications)](https://goreportcard.com/report/PDOK/ogc-specifications)

The package ogc-specifications is a implementation of the OGC Web Service Specifications as defined by the [OGC](https://www.ogc.org/).
This package has support for the following OGC Web Service Specifications:

| Spec | Version | Operation | Request | Reponse |
| Spec | Version | Operation | Request | Response |
| --- | --- | --- | --- | --- |
| WMS | 1.3.0 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: |
| WMS | 1.3.0 | GetMap | :heavy_check_mark: | |
Expand All @@ -18,11 +18,11 @@ This package has support for the following OGC Webservice Specifications:
| WMTS | 1.0.0 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: |
| WCS | 2.0.1 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: |

It will provide the user with structs that can be used with in a developers application, so one doesn't needs to create/build those complex structs for 'every' application that has more then 'simple' interaction with a OGC Webservice. It will allow the developer to parse XML documents and query strings like they are defined in the OGC specification an build go structs with it and it will generate XML documents and query strings based on those structs.
It will provide the user with OperationRequest, OperationResponse and Capabilities structs that can be used with in a developers application, so one doesn't needs to create/build those complex structs for 'every' application that has more then 'simple' interaction with a OGC Web Service. It will allow the developer to parse XML documents and query strings like they are defined in the OGC specification an build go structs with it and it will generate XML documents and KVP query strings based on those structs.

## Notice

This is still a 'work in progres' with the following major todo's:
This is still a 'work-in-progress' with the following major to do's:

- [ ] Validation support
- [ ] YAML parser
Expand All @@ -39,7 +39,7 @@ go get github.com/pdok/ogc-specifications
```

```import
import "github.com/pdok/ogc-specifications"
import ows "github.com/pdok/ogc-specifications/pkg/ows"
```

## Test
Expand All @@ -60,7 +60,7 @@ And generate CPU & MEM profiles:
go test -cpuprofile cpu.prof -memprofile mem.prof -bench=. ./...
```

For visualisation use [pprof](https://github.com/google/pprof)
For visualization use [pprof](https://github.com/google/pprof)

## Usage

Expand Down
9 changes: 2 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
module github.com/pdok/ogc-specifications

go 1.13
go 1.14

require (
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f // indirect
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
gopkg.in/yaml.v2 v2.3.0
)
require gopkg.in/yaml.v2 v2.3.0
14 changes: 0 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f h1:nTaA/z8mev5oJv1dNSbu8Pwvu5CJyBZKwDvHpr9FZ4I=
github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
Expand Down
80 changes: 80 additions & 0 deletions images/layout.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<mxfile host="ffd562a7-3b88-44f1-8042-1855deb27ea2" modified="2020-08-05T07:26:25.991Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.47.3 Chrome/78.0.3904.130 Electron/7.3.2 Safari/537.36" etag="RhS-coRVlT8jDarhV28o" version="13.1.3" pages="2">
<diagram id="6hGFLwfOUW9BJ-s0fimq" name="Interfaces">
<mxGraphModel dx="1683" dy="893" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="8" value="validate against" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="5" target="2" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="used by" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="2" target="7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="2" value="Capabilities" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="370" y="440" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="5" value="OperationRequest" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="290" y="320" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="7" value="OperationResponse" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="530" y="290" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="11" value="INTERFACES" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="130" y="140" width="80" height="20" as="geometry"/>
</mxCell>
<mxCell id="16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="14" target="5">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="17" value="proxies" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="16">
<mxGeometry x="-0.2167" y="-2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="14" value="OperationRequestKVP" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="160" y="220" width="150" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="upT7JopI6jNTdr680uCO" name="WMS">
<mxGraphModel dx="1683" dy="893" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="1ZcEmNP2XnzkbwVKSMnx-0"/>
<mxCell id="1ZcEmNP2XnzkbwVKSMnx-1" parent="1ZcEmNP2XnzkbwVKSMnx-0"/>
<mxCell id="22YBPdbhmI1sAbwN2nd--0" value="Capabilities" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="330" y="630" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-0" value="GetCapabilities" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="250" y="270" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-1" value="GetFeatureInfo" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="250" y="470" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-2" value="GetMap" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="250" y="370" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeWidth=3;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="220" y="230" width="180" height="330" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-5" value="OperationRequest" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="255" y="240" width="110" height="20" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-6" value="GetCapabilities" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="570" y="270" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-7" value="GetFeatureInfo" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="570" y="470" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-8" value="GetMap" style="rounded=0;whiteSpace=wrap;html=1;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="570" y="370" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-9" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeWidth=3;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="540" y="230" width="180" height="330" as="geometry"/>
</mxCell>
<mxCell id="8w07LZEVmTztw3cIHKnJ-10" value="OperationResponse" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1ZcEmNP2XnzkbwVKSMnx-1" vertex="1">
<mxGeometry x="575" y="240" width="110" height="20" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
11 changes: 11 additions & 0 deletions pkg/ows/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ows

// Capabilities interface
// return an error, if needed, not a Exception
// because this isn't a true OWS object only a base
// from which GetCapabilities can build and OperationRequest
// and OperationResponse can be validated against
type Capabilities interface {
ParseXML([]byte) error
ParseYAMl([]byte) error
}
87 changes: 87 additions & 0 deletions pkg/ows/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ package ows
import (
"encoding/xml"
"fmt"
"regexp"
"strconv"
"strings"
)

//
const (
codeSpace = `urn:ogc:def:crs:EPSG::`
EPSG = `EPSG`
)

// BoundingBox struct
Expand All @@ -28,6 +37,46 @@ func (b *BoundingBox) BuildKVP() string {
return fmt.Sprintf("%f,%f,%f,%f", b.LowerCorner[0], b.LowerCorner[1], b.UpperCorner[0], b.UpperCorner[1])
}

//ParseString builds a BoundingBox based on a string
func (b *BoundingBox) ParseString(boundingbox string) Exception {
result := strings.Split(boundingbox, ",")
var lx, ly, ux, uy float64
var err error

if len(result) < 4 {
return InvalidParameterValue(boundingbox, `boundingbox`)
}

if len(result) == 4 || len(result) == 5 {
if lx, err = strconv.ParseFloat(result[0], 64); err != nil {
return InvalidParameterValue(boundingbox, `boundingbox`)
}
if ly, err = strconv.ParseFloat(result[1], 64); err != nil {
return InvalidParameterValue(boundingbox, `boundingbox`)
}
if ux, err = strconv.ParseFloat(result[2], 64); err != nil {
return InvalidParameterValue(boundingbox, `boundingbox`)
}
if uy, err = strconv.ParseFloat(result[3], 64); err != nil {
return InvalidParameterValue(boundingbox, `boundingbox`)
}
}

b.LowerCorner = [2]float64{lx, ly}
b.UpperCorner = [2]float64{ux, uy}

if len(result) == 5 {
b.Crs = result[4]
}

return nil
}

// Keywords in struct for repeatability
type Keywords struct {
Keyword []string `xml:"Keyword" yaml:"keyword"`
}

// StripDuplicateAttr removes the duplicate Attributes from a []Attribute
func StripDuplicateAttr(attr []xml.Attr) []xml.Attr {
attributemap := make(map[xml.Name]string)
Expand All @@ -41,3 +90,41 @@ func StripDuplicateAttr(attr []xml.Attr) []xml.Attr {
}
return strippedAttr
}

// 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
f := strings.Index(code[1], EPSG)
if f > -1 {
c.Namespace = EPSG
} else {
c.Namespace = code[1]
}

// the regex already checks if it [0-9]
i, _ := strconv.Atoi(code[2])
c.Code = i
}
}
70 changes: 59 additions & 11 deletions pkg/ows/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,38 @@ func TestBoundingBoxBuildQueryString(t *testing.T) {
0: {boundingbox: BoundingBox{}, boundingboxstring: `0.000000,0.000000,0.000000,0.000000`},
1: {boundingbox: BoundingBox{LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}}, boundingboxstring: `-180.000000,-90.000000,180.000000,90.000000`},
}
for k, a := range tests {
str := a.boundingbox.BuildKVP()
if str != a.boundingboxstring {
t.Errorf("test: %d, expected: %v+,\n got: %v+", k, a.boundingboxstring, str)
for k, test := range tests {
str := test.boundingbox.BuildKVP()
if str != test.boundingboxstring {
t.Errorf("test: %d, expected: %v+,\n got: %v+", k, test.boundingboxstring, str)
}
}
}

func TestBuildBoundingBox(t *testing.T) {
var tests = []struct {
boundingbox string
bbox BoundingBox
Exception Exception
}{
0: {boundingbox: "0,0,100,100", bbox: BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{100, 100}}},
1: {boundingbox: "0,0,-100,-100", bbox: BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{-100, -100}}}, // while this isn't correct, this will be 'addressed' in the validation step
2: {boundingbox: "0,0,100", Exception: InvalidParameterValue(`0,0,100`, `boundingbox`)},
3: {boundingbox: ",,,", Exception: InvalidParameterValue(`,,,`, `boundingbox`)},
4: {boundingbox: ",,,100", Exception: InvalidParameterValue(`,,,100`, `boundingbox`)},
5: {boundingbox: "number,,,100", Exception: InvalidParameterValue(`number,,,100`, `boundingbox`)},
}

for k, test := range tests {
var bbox BoundingBox
if err := bbox.ParseString(test.boundingbox); err != nil {
if err != test.Exception {
t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Exception, err)
}
} else {
if bbox != test.bbox {
t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.bbox, bbox)
}
}
}
}
Expand All @@ -32,23 +60,43 @@ func TestStripDuplicateAttr(t *testing.T) {
expected: []xml.Attr{{Name: xml.Name{Local: "gml"}, Value: "http://www.opengis.net/gml/3.2"}}},
}

for k, a := range tests {
stripped := StripDuplicateAttr(a.attributes)
if len(a.expected) != len(stripped) {
t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, stripped)
for k, test := range tests {
stripped := StripDuplicateAttr(test.attributes)
if len(test.expected) != len(stripped) {
t.Errorf("test: %d, expected: %s,\n got: %s", k, test.expected, stripped)
} else {
c := false
for _, exceptedattr := range a.expected {
for _, exceptedAttr := range test.expected {
for _, result := range stripped {
if exceptedattr == result {
if exceptedAttr == result {
c = true
}
}
if !c {
t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, stripped)
t.Errorf("test: %d, expected: %s,\n got: %s", k, test.expected, stripped)
}
c = false
}
}
}
}

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: `EPSG`}},
2: {input: `EPSG:4326`, expectedCRS: CRS{Code: 4326, Namespace: `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)
}
}
}
Loading

0 comments on commit df471eb

Please sign in to comment.