Skip to content

Commit 8860a12

Browse files
committed
PDOK-17552 Operator uitwerken voor ATOM v3. Mapping
1 parent 28538b4 commit 8860a12

File tree

4 files changed

+206
-124
lines changed

4 files changed

+206
-124
lines changed

api/v3/atom_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Service struct {
5555
OwnerInfoRef string `json:"ownerInfoRef"`
5656
ServiceMetadataLinks MetadataLink `json:"serviceMetadataLinks,omitempty"`
5757
Rights string `json:"rights,omitempty"`
58+
Author Author `json:"author,omitempty"`
5859
}
5960

6061
// Link represents a link in the service or dataset feed
@@ -81,7 +82,6 @@ type DatasetFeed struct {
8182
Subtitle string `json:"subtitle,omitempty"`
8283
Links []Link `json:"links,omitempty"` // Todo kan weg?
8384
DatasetMetadataLinks MetadataLink `json:"datasetMetadataLinks,omitempty"`
84-
Author Author `json:"author,omitempty"`
8585
SpatialDatasetIdentifierCode string `json:"spatial_dataset_identifier_code,omitempty"`
8686
SpatialDatasetIdentifierNamespace string `json:"spatial_dataset_identifier_namespace,omitempty"`
8787
Entries []Entry `json:"entries,omitempty"`

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
godebug default=go1.23
66

77
require (
8+
github.com/cbroglie/mustache v1.4.0
89
github.com/go-logr/logr v1.4.2
910
github.com/onsi/ginkgo/v2 v2.21.0
1011
github.com/onsi/gomega v1.35.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1010
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
1111
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
1212
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
13+
github.com/cbroglie/mustache v1.4.0 h1:Azg0dVhxTml5me+7PsZ7WPrQq1Gkf3WApcHMjMprYoU=
14+
github.com/cbroglie/mustache v1.4.0/go.mod h1:SS1FTIghy0sjse4DUVGV1k/40B1qE1XkD9DtDsHo9iM=
1315
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1416
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1517
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Lines changed: 202 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,222 @@
11
package atom_generator
22

33
import (
4+
"fmt"
5+
"github.com/cbroglie/mustache"
46
atom_feed "github.com/pdok/atom-generator/feeds"
57
pdoknlv3 "github.com/pdok/atom-operator/api/v3"
68
v1 "github.com/pdok/smooth-operator/api/v1"
9+
"strings"
10+
"time"
711
)
812

913
func MapAtomV3ToAtomGeneratorConfig(atom pdoknlv3.Atom, ownerInfo v1.OwnerInfo) (atomGeneratorConfig atom_feed.Feeds, err error) {
1014

11-
lang := "nl"
15+
var describedbyLink, searchLink, relatedLink atom_feed.Link
1216

13-
// Todo use these templates
14-
// csv := ownerInfo.Spec.MetadataUrls.CSV
15-
// opensearch := ownerInfo.Spec.MetadataUrls.OpenSearch
16-
// html := ownerInfo.Spec.MetadataUrls.HTML
17+
language := "nl"
18+
xmlSheet := "https://service.pdok.nl/atom/style/style.xsl"
19+
selfLink := getSelfLink(atom, language)
20+
describedbyLink, err = getCSWDescribedbyLink(atom, language, ownerInfo)
21+
if err != nil {
22+
return atom_feed.Feeds{}, err
23+
}
24+
searchLink, err = getSearchLink(atom, language, ownerInfo)
25+
if err != nil {
26+
return atom_feed.Feeds{}, err
27+
}
28+
relatedLink, err = getHTMLRelatedLink(atom, language, ownerInfo)
29+
latestUpdated := getLatestUpdate(atom.Spec.DatasetFeeds)
1730

1831
atomGeneratorConfig = atom_feed.Feeds{
1932
Feeds: []atom_feed.Feed{
2033
{
21-
ID: atom.Spec.Service.BaseURL + "/index.xml",
22-
InspireDls: "http://inspire.ec.europa.eu/schemas/inspire_dls/1.0",
23-
Lang: &lang,
24-
// XMLStylesheet: "",
34+
//XMLName: Name{"http://www.w3.org/2005/Atom", "feed"},
35+
XMLStylesheet: &xmlSheet,
36+
Xmlns: "http://www.w3.org/2005/Atom",
37+
Georss: "http://www.georss.org/georss",
38+
InspireDls: "http://inspire.ec.europa.eu/schemas/inspire_dls/1.0",
39+
Lang: &language,
40+
ID: atom.Spec.Service.BaseURL + "/index.xml",
41+
Title: atom.Spec.Service.Title,
42+
Subtitle: atom.Spec.Service.Subtitle,
43+
// Feed Links
44+
Self: &selfLink,
45+
Describedby: &describedbyLink,
46+
Search: &searchLink,
47+
Link: []atom_feed.Link{
48+
relatedLink,
49+
},
50+
Rights: atom.Spec.Service.Rights,
51+
Updated: &latestUpdated,
52+
Author: getAuthor(atom.Spec.Service.Author),
53+
Entry: getEntriesArray(atom, ownerInfo),
2554
},
2655
},
2756
}
28-
29-
// atomGeneratorConfig = AtomGeneratorConfig{
30-
// Feeds: []Feed{
31-
// {
32-
// ID: atom.Spec.Service.BaseURL + "/index.xml",
33-
// InspireDLS: "http://inspire.ec.europa.eu/schemas/inspire_dls/1.0",
34-
// Lang: "nl",
35-
// Stylesheet: "example.com/styles/atom.xsl",
36-
// Title: "Service Title",
37-
// Subtitle: "Service Subtitle",
38-
// Link: []Link{
39-
// {
40-
// Rel: "self",
41-
// Href: atom.Spec.Service.BaseURL + "/index.xml",
42-
// Title: "Service Title",
43-
// Type: "application/atom+xml",
44-
// },
45-
// {
46-
// Rel: "describedby",
47-
// Href: "example.com/getrecord?id=service1",
48-
// Type: "application/xml",
49-
// },
50-
// {
51-
// Rel: "search",
52-
// Href: "example.com/opensearch.xml",
53-
// Title: "Open Search document voor INSPIRE Download service PDOK",
54-
// Type: "application/opensearchdescription+xml",
55-
// },
56-
// {
57-
// Rel: "related",
58-
// Href: "example.com/metadata/service1",
59-
// Type: "text/html",
60-
// Title: "NGR pagina voor deze download service",
61-
// },
62-
// },
63-
// Rights: "All rights reserved",
64-
// Updated: &updatedTime,
65-
// Author: Author{
66-
// Name: "PDOK Beheer",
67-
// Email: "[email protected]",
68-
// },
69-
// Entry: []Entry{
70-
// {
71-
// ID: "example.com/atom/dataset1.xml",
72-
// Title: "Dataset 1 Title",
73-
// SpatialDatasetIdentifierCode: "dataset1-id",
74-
// SpatialDatasetIdentifierNamespace: "http://www.pdok.nl",
75-
// Link: []Link{
76-
// {
77-
// Rel: "describedby",
78-
// Href: "example.com/getrecord?id=dataset1",
79-
// Type: "application/xml",
80-
// },
81-
// {
82-
// Rel: "alternate",
83-
// Href: "example.com/atom/dataset1.xml",
84-
// Type: "application/atom+xml",
85-
// Title: "Dataset 1 Title",
86-
// },
87-
// },
88-
// Updated: &updatedTime,
89-
// Summary: "Dataset 1 Subtitle",
90-
// Polygon: "42.0 12.0 42.0 13.0 43.0 13.0 43.0 12.0 42.0 12.0",
91-
// Category: []Category{
92-
// {
93-
// Term: "urn:ogc:def:crs:EPSG::4326",
94-
// Label: "EPSG:4326",
95-
// },
96-
// },
97-
// },
98-
// // Adding another entry for completeness
99-
// {
100-
// ID: "example.com/atom/dataset2.xml",
101-
// Title: "Dataset 2 Title",
102-
// Link: []Link{
103-
// {
104-
// Rel: "self",
105-
// Href: "example.com/atom/dataset2.xml",
106-
// },
107-
// {
108-
// Rel: "up",
109-
// Href: "example.com/atom/index.xml",
110-
// Type: "application/atom+xml",
111-
// Title: "Top Atom Download Service Feed",
112-
// },
113-
// {
114-
// Rel: "describedby",
115-
// Href: "example.com/getrecord?id=service1",
116-
// Type: "text/html",
117-
// },
118-
// {
119-
// Rel: "related",
120-
// Href: "example.com/metadata/dataset2",
121-
// Type: "text/html",
122-
// Title: "NGR pagina voor deze dataset",
123-
// },
124-
// {
125-
// Rel: "describedby",
126-
// Href: "example.com/link1",
127-
// Title: "Link Type 1",
128-
// Type: "application/pdf",
129-
// Hreflang: "en",
130-
// },
131-
// // Add more links if needed
132-
// },
133-
// Rights: "All rights reserved",
134-
// Updated: "20-04-2024 huplelepup",
135-
// },
136-
// },
137-
// },
138-
// },
139-
// }
140-
141-
// return
14257
return atomGeneratorConfig, err
14358
}
59+
60+
func getLatestUpdate(feeds []pdoknlv3.DatasetFeed) string {
61+
updateTime := feeds[0].Entries[0].Updated
62+
for _, datasetFeed := range feeds {
63+
for _, entry := range datasetFeed.Entries {
64+
if updateTime.Before(entry.Updated) {
65+
updateTime = entry.Updated
66+
}
67+
}
68+
}
69+
return updateTime.Format(time.RFC3339)
70+
}
71+
72+
func getEntriesArray(atom pdoknlv3.Atom, ownerInfo v1.OwnerInfo) []atom_feed.Entry {
73+
var retEntriesArray []atom_feed.Entry
74+
for _, datasetFeed := range atom.Spec.DatasetFeeds {
75+
for _, entry := range datasetFeed.Entries {
76+
updateTime := entry.Updated.Format(time.RFC3339)
77+
78+
singleEntry := atom_feed.Entry{
79+
ID: entry.TechnicalName,
80+
Title: entry.Title,
81+
Content: entry.Content,
82+
Summary: datasetFeed.Subtitle,
83+
Rights: atom.Spec.Service.Rights,
84+
Updated: &updateTime,
85+
Polygon: getBoundingBoxPolygon(entry.Polygon.BBox),
86+
SpatialDatasetIdentifierCode: datasetFeed.SpatialDatasetIdentifierCode,
87+
SpatialDatasetIdentifierNamespace: datasetFeed.SpatialDatasetIdentifierNamespace,
88+
Category: getCategory(entry.SRS),
89+
Link: getEntryLinksArray(entry),
90+
}
91+
retEntriesArray = append(retEntriesArray, singleEntry)
92+
}
93+
}
94+
95+
return retEntriesArray
96+
}
97+
98+
func getEntryLinksArray(entry pdoknlv3.Entry) []atom_feed.Link {
99+
linksArray := []atom_feed.Link{}
100+
for _, link := range entry.DownloadLinks {
101+
dataLink := link.Data
102+
bboxString := getBboxString(link.BBox)
103+
104+
l := atom_feed.Link{
105+
Data: &dataLink,
106+
Rel: link.Rel,
107+
Version: link.Version,
108+
Time: link.Time,
109+
Bbox: &bboxString,
110+
}
111+
linksArray = append(linksArray, l)
112+
}
113+
return linksArray
114+
}
115+
116+
func getBboxString(bbox *pdoknlv3.BBox) string {
117+
var sb strings.Builder
118+
sb.WriteString(bbox.MinX + " " + bbox.MinY + " " + bbox.MaxX + " " + bbox.MaxY)
119+
return sb.String()
120+
}
121+
122+
func getCategory(srs *pdoknlv3.SRS) []atom_feed.Category {
123+
cat := []atom_feed.Category{
124+
{
125+
Term: srs.URI,
126+
Label: srs.Name,
127+
},
128+
}
129+
return cat
130+
}
131+
132+
func getBoundingBoxPolygon(bbox pdoknlv3.BBox) string {
133+
var sb strings.Builder
134+
// punt links beneden start van een polygon
135+
sb.WriteString(bbox.MinX + " " + bbox.MinY + " ")
136+
// punt links boven start van een polygon
137+
sb.WriteString(bbox.MinX + " " + bbox.MaxY + " ")
138+
// punt rechts boven start van een polygon
139+
sb.WriteString(bbox.MaxX + " " + bbox.MaxY + " ")
140+
// punt rechts beneden start van een polygon
141+
sb.WriteString(bbox.MaxX + " " + bbox.MinY + " ")
142+
// punt links beneden. eninde van een polygon is gelijk aan de start
143+
sb.WriteString(bbox.MinX + " " + bbox.MinY + " ")
144+
return sb.String()
145+
}
146+
147+
func getAuthor(author pdoknlv3.Author) atom_feed.Author {
148+
return atom_feed.Author{
149+
Name: author.Name,
150+
Email: author.Email,
151+
}
152+
}
153+
154+
func getSelfLink(atom pdoknlv3.Atom, language string) atom_feed.Link {
155+
return atom_feed.Link{
156+
Rel: "self",
157+
Href: atom.Spec.Service.BaseURL + "/index.xml",
158+
Title: strings.Replace(atom.Spec.Service.Title, "\"", "\\\"", -1),
159+
Type: "application/atom+xml",
160+
Hreflang: &language,
161+
}
162+
}
163+
164+
func replaceMustachTemplate(hrefTemplate string, identifier string) (string, error) {
165+
templateVariable := map[string]string{"identifier": identifier}
166+
return mustache.Render(hrefTemplate, templateVariable)
167+
}
168+
169+
func getCSWDescribedbyLink(atom pdoknlv3.Atom, language string, ownerInfo v1.OwnerInfo) (atom_feed.Link, error) {
170+
for _, template := range atom.Spec.Service.ServiceMetadataLinks.Templates {
171+
if template == "csw" {
172+
href, err := replaceMustachTemplate(ownerInfo.Spec.MetadataUrls.CSV.HrefTemplate, atom.Spec.Service.ServiceMetadataLinks.MetadataIdentifier)
173+
if err != nil {
174+
return atom_feed.Link{}, err
175+
}
176+
return atom_feed.Link{
177+
Rel: "describedby",
178+
Href: href,
179+
Type: "application/xml",
180+
Hreflang: &language,
181+
}, nil
182+
}
183+
}
184+
return atom_feed.Link{}, fmt.Errorf("OwnerInfo heeft geen CSW template")
185+
}
186+
187+
func getSearchLink(atom pdoknlv3.Atom, language string, ownerInfo v1.OwnerInfo) (atom_feed.Link, error) {
188+
for _, template := range atom.Spec.Service.ServiceMetadataLinks.Templates {
189+
if template == "opensearch" {
190+
href, err := replaceMustachTemplate(ownerInfo.Spec.MetadataUrls.OpenSearch.HrefTemplate, atom.Spec.Service.ServiceMetadataLinks.MetadataIdentifier)
191+
if err != nil {
192+
return atom_feed.Link{}, err
193+
}
194+
195+
return atom_feed.Link{
196+
Rel: "search",
197+
Href: href,
198+
Type: "application/xml",
199+
Hreflang: &language,
200+
}, nil
201+
}
202+
}
203+
return atom_feed.Link{}, fmt.Errorf("OwnerInfo heeft geen opensearch template")
204+
}
205+
206+
func getHTMLRelatedLink(atom pdoknlv3.Atom, language string, ownerInfo v1.OwnerInfo) (atom_feed.Link, error) {
207+
for _, template := range atom.Spec.Service.ServiceMetadataLinks.Templates {
208+
if template == "html" {
209+
href, err := replaceMustachTemplate(ownerInfo.Spec.MetadataUrls.HTML.HrefTemplate, atom.Spec.Service.ServiceMetadataLinks.MetadataIdentifier)
210+
if err != nil {
211+
return atom_feed.Link{}, err
212+
}
213+
return atom_feed.Link{
214+
Rel: "related",
215+
Href: href,
216+
Type: "text/html",
217+
Hreflang: &language,
218+
}, nil
219+
}
220+
}
221+
return atom_feed.Link{}, fmt.Errorf("OwnerInfo heeft geen html template")
222+
}

0 commit comments

Comments
 (0)