Mercurial > gemma
changeset 4926:271616eff8e3 fairway-marks-import
Merge branch default into fairway-marks-import
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Fri, 14 Feb 2020 14:33:42 +0100 |
parents | b86ce7fc4da3 (diff) 0b10d3c68da0 (current diff) |
children | 6081cbe71b81 |
files | |
diffstat | 21 files changed, 1229 insertions(+), 80 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/importconfiguration/Import.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importconfiguration/Import.vue Fri Feb 14 14:33:42 2020 +0100 @@ -6,8 +6,8 @@ <UITableHeader :columns="[ { id: 'id', title: `${idLabel}`, width: '60px;' }, - { id: 'kind', title: `${typeLabel}`, width: '60px;' }, - { id: 'user', title: `${ownerLabel}`, width: '250px' }, + { id: 'kind', title: `${typeLabel}`, width: '80px;' }, + { id: 'user', title: `${ownerLabel}`, width: '230px' }, { id: 'country', title: `${countryLabel}`, width: '80px' }, { id: 'config.cron', title: `${scheduleLabel}`, width: '100px' }, { id: 'config.send-email', title: `${emailLabel}`, width: '70px' } @@ -21,10 +21,10 @@ <div style="width:60px" class="table-cell py-1"> {{ schedule.id }} </div> - <div style="width:60px" class="table-cell py-1"> - {{ schedule.kind.toUpperCase() }} + <div style="width:80px" class="table-cell py-1"> + {{ schedule.kind.replace("fm_", "").toUpperCase() }} </div> - <div style="width:250px;" class="table-cell py-1"> + <div style="width:230px;" class="table-cell py-1"> {{ schedule.user }} </div> <div style="width:80px;" class="table-cell py-1">
--- a/client/src/components/importconfiguration/ImportDetails.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importconfiguration/ImportDetails.vue Fri Feb 14 14:33:42 2020 +0100 @@ -10,6 +10,17 @@ class="custom-select custom-select-sm" id="importtype" > + <optgroup :label="onetimeLabel"> + <option :value="$options.IMPORTTYPES.SOUNDINGRESULTS"> + <translate>Soundingresults</translate> + </option> + <option :value="$options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS"> + <translate>Approved Gaugemeasurements</translate> + </option> + <option :value="$options.IMPORTTYPES.WATERWAYPROFILES"> + <translate>Waterway Profiles</translate> + </option> + </optgroup> <optgroup :label="regularLabel"> <option :value="$options.IMPORTTYPES.WATERWAYAREA"> <translate>Waterway area</translate> @@ -38,16 +49,8 @@ <option :value="$options.IMPORTTYPES.GAUGEMEASUREMENT"> <translate>Gauge measurement</translate> </option> - </optgroup> - <optgroup :label="onetimeLabel"> - <option :value="$options.IMPORTTYPES.SOUNDINGRESULTS"> - <translate>Soundingresults</translate> - </option> - <option :value="$options.IMPORTTYPES.APPROVEDGAUGEMEASUREMENTS"> - <translate>Approved Gaugemeasurements</translate> - </option> - <option :value="$options.IMPORTTYPES.WATERWAYPROFILES"> - <translate>Waterway Profiles</translate> + <option :value="$options.IMPORTTYPES.FAIRWAYMARKS"> + <translate>Fairwaymarks</translate> </option> </optgroup> </select>
--- a/client/src/components/importconfiguration/ScheduledImports.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importconfiguration/ScheduledImports.vue Fri Feb 14 14:33:42 2020 +0100 @@ -104,9 +104,12 @@ @urlChanged="setUrl" @featureTypeChanged="setFeatureType" @sortByChanged="setSortBy" + @selectedMarkChanged="setSelectedMark" + :isUpdate="!this.id" :url="url" :featureType="featureType" :sortBy="sortBy" + :mark="selectedMark" /> <Gaugemeasurement v-if="import_ == $options.IMPORTTYPES.GAUGEMEASUREMENT && !directImport" @@ -712,6 +715,9 @@ setSortBy(value) { this.sortBy = value; }, + setSelectedMark(value) { + this.selectedMark = value; + }, setTolerance(value) { this.tolerance = value; }, @@ -789,6 +795,7 @@ this.directImport = false; this.trys = this.currentSchedule.trys; this.waitRetry = this.currentSchedule.waitRetry; + this.selectedMark = this.currentSchedule.selectedMark; this.retry = this.currentSchedule.trys === null || this.currentSchedule.trys === undefined || @@ -921,9 +928,13 @@ if (this.trys) data["trys"] = Number(this.trys); data["send-email"] = this.eMailNotification; this.triggerActive = false; + const type = + this.import_ === "fairwaymarks" + ? `fm_${this.selectedMark.toLowerCase()}` + : IMPORTTYPEKIND[this.import_]; this.$store .dispatch("importschedule/triggerImport", { - type: IMPORTTYPEKIND[this.import_], + type: type, data }) .then(response => { @@ -957,8 +968,10 @@ } let data = {}; let config = {}; - data["kind"] = IMPORTTYPEKIND[this.import_]; - + data["kind"] = + this.import_ === "fairwaymarks" + ? `fm_${this.selectedMark.toLowerCase()}` + : IMPORTTYPEKIND[this.import_]; if (this.isURLRequired) { if (!this.url) return; config["url"] = this.url;
--- a/client/src/components/importconfiguration/types/Fairwaymarks.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importconfiguration/types/Fairwaymarks.vue Fri Feb 14 14:33:42 2020 +0100 @@ -15,10 +15,29 @@ </div> </div> </div> - <div v-if="!url" class="d-flex px-2"> - <small - ><translate class="text-danger">Please enter a URL</translate></small - > + <div class="d-flex px-2"> + <div class="flex-column w-100"> + <div class="flex-row text-left"> + <small class="text-muted"> + <translate>Type of mark</translate> + </small> + </div> + <div class="w-50 mt-2"> + <template v-if="isUpdate"> + <select v-model="selectedMark" class="form-control form-control-sm"> + <option + v-for="(option, value) in $options.FAIRWAYMARKS" + :key="value" + :value="value" + >{{ option }}</option + > + </select> + </template> + <template v-else=""> + <span class="pl-1">{{ selectedMark }}</span> + </template> + </div> + </div> </div> <div class="d-flex px-2"> <div class="flex-column mt-2 mr-3 w-50"> @@ -80,8 +99,19 @@ * Thomas Junk <thomas.junk@intevation.de> */ export default { - name: "waterwayarea", - props: ["url", "featureType", "sortBy"], + name: "fairwaymarks", + props: ["url", "featureType", "sortBy", "mark", "isUpdate"], + computed: { + selectedMark: { + get() { + return this.mark; + }, + set(value) { + this.selected = value; + this.$emit("selectedMarkChanged", value); + } + } + }, methods: { urlChanged(e) { this.$emit("urlChanged", e.target.value); @@ -92,6 +122,20 @@ sortByChanged(e) { this.$emit("sortByChanged", e.target.value); } + }, + FAIRWAYMARKS: { + BCNISD: "Beacon, isolated danger (MARITIME/Hydro feature)", + BCNLAT: "Beacon, lateral (MARITIME/Hydro feature)", + BOYCAR: "Buoy, cardinal (MARITIME/Hydro feature)", + BOYISD: "Buoy, isolated danger (MARITIME/Hydro feature)", + BOYLAT: "Buoy, lateral (MARITIME/Hydro feature)", + BOYSAW: "Buoy, safe water (MARITIME/Hydro feature)", + BOYSPP: "Buoy, special purpose/general (MARITIME/Hydro feature)", + DAYMAR: "Daymark (MARITIME/Hydro feature)", + LIGHTS: "Light (MARITIME/Hydro feature)", + RTPBCN: "Radar transponder beacon (MARITIME/Hydro feature)", + TOPMAR: "Topmark (MARITIME/Hydro feature)", + notmrk: "Notice mark (IENC feature)" } }; </script>
--- a/client/src/components/importoverview/ImportOverview.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importoverview/ImportOverview.vue Fri Feb 14 14:33:42 2020 +0100 @@ -78,10 +78,10 @@ <UITableHeader :columns="[ { id: 'id', title: `${idLabel}`, width: '70px' }, - { id: 'kind', title: `${kindLabel}`, width: '50px' }, - { id: 'enqueued', title: `${enqueuedLabel}`, width: '138px' }, + { id: 'kind', title: `${kindLabel}`, width: '55px' }, + { id: 'enqueued', title: `${enqueuedLabel}`, width: '135px' }, { id: 'user', title: `${ownerLabel}`, width: '80px' }, - { id: 'country', title: `${countryLabel}`, width: '55px' }, + { id: 'country', title: `${countryLabel}`, width: '50px' }, { id: 'signer', title: `${signerLabel}`, width: '80px' }, { id: 'state', title: `${statusLabel}`, width: '72px' }, { id: 'changed', title: `${changedLabel}`, width: '138px' },
--- a/client/src/components/importoverview/LogEntry.vue Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/components/importoverview/LogEntry.vue Fri Feb 14 14:33:42 2020 +0100 @@ -9,16 +9,16 @@ /> {{ entry.id }} </div> - <div style="width: 50px;" class="table-cell center"> - {{ entry.kind.toUpperCase() }} + <div style="width: 55px;" class="table-cell center"> + {{ entry.kind.replace("fm_", "").toUpperCase() }} </div> - <div style="width: 138px;" class="table-cell center"> + <div style="width: 135px;" class="table-cell center"> {{ entry.enqueued | dateTime }} </div> <div style="width: 80px;" class="table-cell truncate"> {{ entry.user }} </div> - <div style="width: 55px;" class="table-cell center"> + <div style="width: 50px;" class="table-cell center"> {{ userCountries[entry.user] }} </div> <div style="width: 80px;" class="table-cell truncate">
--- a/client/src/store/importschedule.js Fri Feb 14 14:31:12 2020 +0100 +++ b/client/src/store/importschedule.js Fri Feb 14 14:33:42 2020 +0100 @@ -60,6 +60,21 @@ distancemarksashore: "dma" }; +const FAIRWAYMARKKINDS = { + fm_bcnisd: "BCNISD", + fm_bcnlat: "BCNLAT", + fm_boycar: "BOYCAR", + fm_boyisd: "BOYISD", + fm_boylat: "BOYLAT", + fm_boysaw: "BOYSAW", + fm_boyspp: "BOYSPP", + fm_daymar: "DAYMAR", + fm_lights: "LIGHTS", + fm_rtpbcn: "RTPBCN", + fm_topmar: "TOPMAR", + fm_notmrk: "notmrk" +}; + const initializeCurrentSchedule = () => { return { id: null, @@ -91,7 +106,8 @@ depth: null, sourceOrganization: null, trys: null, - waitRetry: null + waitRetry: null, + selectedMark: null }; }; @@ -141,7 +157,14 @@ const { kind, config, id } = payload; const eMailNotification = config["send-email"]; const { cron, url } = config; - Vue.set(state.currentSchedule, "importType", KINDIMPORTTYPE[kind]); + if (FAIRWAYMARKKINDS[kind]) { + Vue.set(state.currentSchedule, "importType", "fairwaymarks"); + debugger; + Vue.set(state.currentSchedule, "selectedMark", FAIRWAYMARKKINDS[kind]); + } else { + Vue.set(state.currentSchedule, "importType", KINDIMPORTTYPE[kind]); + } + Vue.set(state.currentSchedule, "id", id); Vue.set(state.currentSchedule, "trys", config["trys"]); Vue.set(state.currentSchedule, "waitRetry", config["wait-retry"]);
--- a/pkg/controllers/geostyling.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/controllers/geostyling.go Fri Feb 14 14:33:42 2020 +0100 @@ -31,20 +31,20 @@ styleName = "style" ) -func extractStyle(req *http.Request) (string, error) { +func extractStyle(req *http.Request) ([]byte, error) { f, _, err := req.FormFile(styleName) if err != nil { - return "", err + return nil, err } defer f.Close() var buf bytes.Buffer if _, err := io.Copy(&buf, io.LimitReader(f, maxStyleSize)); err != nil { - return "", err + return nil, err } - return buf.String(), nil + return buf.Bytes(), nil } func supportedWMSFeature(name string) bool {
--- a/pkg/controllers/routes.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/controllers/routes.go Fri Feb 14 14:33:42 2020 +0100 @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2020 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // @@ -243,6 +243,9 @@ "bn", "gm", "fa", "wx", "wa", "wg", "dmv", "fd", "dma", "sec", "dsec", "dst", "dsr", + "fm_bcnlat", + "fm_boycar", + "fm_boylat", }, "|") api.Handle("/imports/{kind:"+kinds+"}", waterwayAdmin(&mw.JSONHandler{
--- a/pkg/geoserver/boot.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/geoserver/boot.go Fri Feb 14 14:33:42 2020 +0100 @@ -14,6 +14,7 @@ package geoserver import ( + "archive/zip" "bytes" "encoding/json" "encoding/xml" @@ -478,19 +479,33 @@ return stylePreprocessors[name] } +func isZip(data []byte) bool { + if len(data) == 0 { + return false + } + _, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + return err == nil +} + func updateStyle(entry *models.IntEntry, create bool) error { log.Printf("info: creating style %s\n", entry.Name) // Try to load the style data. - data, err := entry.LoadStyle() + binary, err := entry.LoadStyle() if err != nil { return err } - if processor := FindStylePreprocessor(entry.Name); processor != nil { - if data, err = processor(data); err != nil { - return err + zip := isZip(binary) + + if !zip { // We only support templating for plain XML styles. + if processor := FindStylePreprocessor(entry.Name); processor != nil { + data, err := processor(string(binary)) + if err != nil { + return err + } + binary = []byte(data) } } @@ -548,14 +563,17 @@ req, err := http.NewRequest( http.MethodPut, styleURL+"/"+url.PathEscape(entry.Name), - strings.NewReader(data)) + bytes.NewReader(binary)) if err != nil { return err } auth(req) - if isSymbologyEncoding(data) { + switch { + case zip: + asContentType(req, "application/zip") + case isSymbologyEncoding(binary): asContentType(req, "application/vnd.ogc.se+xml") - } else { + default: asContentType(req, "application/vnd.ogc.sld+xml") } resp, err := http.DefaultClient.Do(req) @@ -596,8 +614,8 @@ } // isSymbologyEncoding tries to figure out if its plain SLD or SE. -func isSymbologyEncoding(data string) bool { - decoder := xml.NewDecoder(strings.NewReader(data)) +func isSymbologyEncoding(data []byte) bool { + decoder := xml.NewDecoder(bytes.NewReader(data)) decoder.CharsetReader = charset.NewReaderLabel for {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/fm.go Fri Feb 14 14:33:42 2020 +0100 @@ -0,0 +1,178 @@ +// 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) 2020 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Tom Gottfried <tom.gottfried@intevation.de> + +package imports + +import ( + "encoding/json" + "fmt" + "io" + + "gemma.intevation.de/gemma/pkg/wfs" +) + +// FairwayMarks is a struct +// to be used as the basis for imports of +// specific types for fairway marks. +type FairwayMarks struct { + // URL the GetCapabilities URL of the WFS service. + URL string `json:"url"` + // FeatureType selects the feature type of the WFS service. + FeatureType string `json:"feature-type"` + // SortBy works around misconfigured services to + // establish a sort order to get the features. + SortBy string `json:"sort-by"` + // User is an optional username for Basic Auth. + User string `json:"user,omitempty"` + // Password is an optional password for Basic Auth. + Password string `json:"password,omitempty"` +} + +// Properties common to all types of fairway marks +type fairwayMarksProperties struct { + Datsta *string `json:"hydro_datsta"` + Datend *string `json:"hydro_datend"` + Persta *string `json:"hydro_persta"` + Perend *string `json:"hydro_perend"` + Objnam *string `json:"hydro_objnam"` + Nobjnm *string `json:"hydro_nobjnm"` + Inform *string `json:"hydro_inform"` + Ninfom *string `json:"hydro_ninfom"` + Scamin *int `json:"hydro_scamin"` + Picrep *string `json:"hydro_picrep"` + Txtdsc *string `json:"hydro_txtdsc"` + Sordat *string `json:"hydro_sordat"` + Sorind *string `json:"hydro_sorind"` +} + +// Common operation of FM imports to get features from WFS service +func getFMFeatures( + feedback Feedback, + fm FairwayMarks, + // Constructor returning pointer to struct + // representing featuretype's properties + newProps func() interface{}, + // Construct pointer to featuretype from given pointSlice and properties + newFeat func(pointSlice, interface{}) interface{}, +) ( + // Slice of features to be converted to featuretypes type + fms []interface{}, + epsg int, + err error, +) { + + feedback.Info("Loading capabilities from %s", fm.URL) + caps, err := wfs.GetCapabilities(fm.URL) + if err != nil { + feedback.Error("Loading capabilities failed: %v", err) + return + } + + ft := caps.FindFeatureType(fm.FeatureType) + if ft == nil { + err = fmt.Errorf("unknown feature type '%s'", fm.FeatureType) + return + } + + feedback.Info("Found feature type '%s", fm.FeatureType) + + epsg, err = wfs.CRSToEPSG(ft.DefaultCRS) + if err != nil { + feedback.Error("Unsupported CRS: '%s'", ft.DefaultCRS) + return + } + + if fm.SortBy != "" { + feedback.Info("Features will be sorted by '%s'", fm.SortBy) + } + + dl, err := wfs.GetFeatures(caps, fm.FeatureType, fm.SortBy) + if err != nil { + feedback.Error("Cannot create GetFeature URLs. %v", err) + return + } + + var ( + unsupported = stringCounter{} + missingProperties int + badProperties int + ) + + err = dl.Download(fm.User, fm.Password, func(url string, r io.Reader) error { + feedback.Info("Get features from: '%s'", url) + rfc, err := wfs.ParseRawFeatureCollection(r) + if err != nil { + return fmt.Errorf("parsing GetFeature document failed: %v", err) + } + if rfc.CRS != nil { + crsName := rfc.CRS.Properties.Name + if epsg, err = wfs.CRSToEPSG(crsName); err != nil { + feedback.Error("Unsupported CRS: %d", crsName) + return err + } + } + + // No features -> ignore. + if rfc.Features == nil { + return nil + } + + feedback.Info("Using EPSG: %d", epsg) + + for _, feature := range rfc.Features { + if feature.Properties == nil || feature.Geometry.Coordinates == nil { + missingProperties++ + continue + } + + props := newProps() + if err := json.Unmarshal(*feature.Properties, props); err != nil { + badProperties++ + continue + } + + switch feature.Geometry.Type { + case "Point": + var p pointSlice + if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil { + return err + } + + f := newFeat(p, props) + fms = append(fms, f) + default: + unsupported[feature.Geometry.Type]++ + } + } + return nil + }) + if err != nil { + return + } + + if badProperties > 0 { + feedback.Warn("Bad properties: %d", badProperties) + } + + if missingProperties > 0 { + feedback.Warn("Missing properties: %d", missingProperties) + } + + if len(unsupported) != 0 { + feedback.Warn("Unsupported types found: %s", unsupported) + } + + feedback.Info("Found %d usable features in data source", len(fms)) + + return +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/fm_bcnlat.go Fri Feb 14 14:33:42 2020 +0100 @@ -0,0 +1,259 @@ +// 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) 2020 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Tom Gottfried <tom.gottfried@intevation.de> + +package imports + +import ( + "context" + "database/sql" + "strings" + "time" + + "gemma.intevation.de/gemma/pkg/pgxutils" +) + +// Bcnlat is an import job to import +// fairway marks of type BCNLAT in form of point geometries +// and attribute data from a WFS service. +type Bcnlat struct { + FairwayMarks +} + +// Description gives a short info about relevant facts of this import. +func (bcnlat *Bcnlat) Description() (string, error) { + return bcnlat.URL + "|" + bcnlat.FeatureType, nil +} + +// BCNLATJobKind is the import queue type identifier. +const BCNLATJobKind JobKind = "fm_bcnlat" + +type bcnlatJobCreator struct{} + +func init() { + RegisterJobCreator(BCNLATJobKind, bcnlatJobCreator{}) +} + +func (bcnlatJobCreator) Description() string { return "fairway marks bcnlat" } + +func (bcnlatJobCreator) AutoAccept() bool { return true } + +func (bcnlatJobCreator) Create() Job { return new(Bcnlat) } + +func (bcnlatJobCreator) Depends() [2][]string { + return [2][]string{ + {"fairway_marks_bcnlat"}, + {}, + } +} + +// StageDone is a NOP for fairway marks imports. +func (bcnlatJobCreator) StageDone(context.Context, *sql.Tx, int64) error { + return nil +} + +// CleanUp for fairway marks imports is a NOP. +func (*Bcnlat) CleanUp() error { return nil } + +type bcnlatProperties struct { + fairwayMarksProperties + Colour *string `json:"hydro_colour"` + Colpat *string `json:"hydro_colpat"` + Condtn *int `json:"hydro_condtn"` + Bcnshp *int `json:"hydro_bcnshp"` + HydroCatlam *int64 `json:"hydro_catlam,omitempty"` + IENCCatlam *int64 `json:"ienc_catlam,omitempty"` + Dirimp *string `json:"ienc_dirimp,omitempty"` +} + +type bcnlatFeaturetype struct { + geom pointSlice + props *bcnlatProperties +} + +const ( + insertBCNLATSQL = ` +with a as ( + select users.current_user_area_utm() AS a +) +INSERT INTO waterway.fairway_marks_bcnlat ( + geom, + datsta, + datend, + persta, + perend, + objnam, + nobjnm, + inform, + ninfom, + scamin, + picrep, + txtdsc, + sordat, + sorind, + colour, + colpat, + condtn, + bcnshp, + catlam +) +SELECT newfm, $3, $4, $5, $6, $7, $8, $9, + $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20 + FROM ST_Transform(ST_GeomFromWKB($1, $2::integer), 4326) AS newfm (newfm) + WHERE pg_has_role('sys_admin', 'MEMBER') + OR ST_Intersects((select a from a), + ST_Transform(newfm, (select ST_SRID(a) from a))) +ON CONFLICT ( + CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, condtn, bcnshp, catlam + ) AS waterway.fairway_marks_bcnlat) + ) + DO NOTHING +RETURNING id +` + insertDirimpSQL = ` +INSERT INTO waterway.fairway_marks_bcnlat_dirimps (fm_bcnlat_id, dirimp) + VALUES ($1, $2) +` +) + +// Do executes the actual import. +func (fm *Bcnlat) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + start := time.Now() + + feedback.Info("Import fairway marks of type BCNLAT/bcnlat") + + fms, epsg, err := getFMFeatures( + feedback, + fm.FairwayMarks, + func() interface{} { return new(bcnlatProperties) }, + func(p pointSlice, props interface{}) interface{} { + return &bcnlatFeaturetype{p, props.(*bcnlatProperties)} + }, + ) + if err != nil { + return nil, err + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + insertStmt, err := tx.PrepareContext(ctx, insertBCNLATSQL) + if err != nil { + return nil, err + } + defer insertStmt.Close() + + insertDirimpStmt, err := tx.PrepareContext(ctx, insertDirimpSQL) + if err != nil { + return nil, err + } + defer insertDirimpStmt.Close() + + savepoint := Savepoint(ctx, tx, "feature") + + var ( + outsideOrDup int + features int + ) + for _, fm := range fms { + + f := fm.(*bcnlatFeaturetype) + + var catlam sql.NullInt64 + if f.props.HydroCatlam != nil { + catlam = sql.NullInt64{Int64: *f.props.HydroCatlam, Valid: true} + } else if f.props.IENCCatlam != nil { + catlam = sql.NullInt64{Int64: *f.props.IENCCatlam, Valid: true} + } + + var fmid int64 + err := savepoint(func() error { + err := insertStmt.QueryRowContext( + ctx, + f.geom.asWKB(), + epsg, + f.props.Datsta, + f.props.Datend, + f.props.Persta, + f.props.Perend, + f.props.Objnam, + f.props.Nobjnm, + f.props.Inform, + f.props.Ninfom, + f.props.Scamin, + f.props.Picrep, + f.props.Txtdsc, + f.props.Sordat, + f.props.Sorind, + f.props.Colour, + f.props.Colpat, + f.props.Condtn, + f.props.Bcnshp, + catlam, + ).Scan(&fmid) + return err + }) + switch { + case err == sql.ErrNoRows: + outsideOrDup++ + // ignore -> filtered by responsibility area or a duplicate + // TODO: handle eventual changes to dirimp + case err != nil: + feedback.Error(pgxutils.ReadableError{Err: err}.Error()) + default: + features++ + + if f.props.Dirimp != nil && *f.props.Dirimp != "" { + dirimps := strings.Split(*f.props.Dirimp, ",") + for _, dirimp := range dirimps { + if err := savepoint(func() error { + _, err := insertDirimpStmt.ExecContext( + ctx, fmid, dirimp) + return err + }); err != nil { + feedback.Warn(pgxutils.ReadableError{Err: err}.Error()) + } + } + } + } + } + + if outsideOrDup > 0 { + feedback.Info( + "Features outside responsibility area and duplicates: %d", + outsideOrDup) + } + + if features == 0 { + err := UnchangedError("no valid new features found") + return nil, err + } + + if err = tx.Commit(); err == nil { + feedback.Info("Storing %d features took %s", + features, time.Since(start)) + } + + return nil, err +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/fm_boycar.go Fri Feb 14 14:33:42 2020 +0100 @@ -0,0 +1,228 @@ +// 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) 2020 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Tom Gottfried <tom.gottfried@intevation.de> + +package imports + +import ( + "context" + "database/sql" + "time" + + "gemma.intevation.de/gemma/pkg/pgxutils" +) + +// Boycar is an import job to import +// fairway marks of type BOYCAR in form of point geometries +// and attribute data from a WFS service. +type Boycar struct { + FairwayMarks +} + +// Description gives a short info about relevant facts of this import. +func (boycar *Boycar) Description() (string, error) { + return boycar.URL + "|" + boycar.FeatureType, nil +} + +// BOYCARJobKind is the import queue type identifier. +const BOYCARJobKind JobKind = "fm_boycar" + +type boycarJobCreator struct{} + +func init() { + RegisterJobCreator(BOYCARJobKind, boycarJobCreator{}) +} + +func (boycarJobCreator) Description() string { return "fairway marks boycar" } + +func (boycarJobCreator) AutoAccept() bool { return true } + +func (boycarJobCreator) Create() Job { return new(Boycar) } + +func (boycarJobCreator) Depends() [2][]string { + return [2][]string{ + {"fairway_marks_boycar"}, + {}, + } +} + +// StageDone is a NOP for fairway marks imports. +func (boycarJobCreator) StageDone(context.Context, *sql.Tx, int64) error { + return nil +} + +// CleanUp for fairway marks imports is a NOP. +func (*Boycar) CleanUp() error { return nil } + +type boycarProperties struct { + fairwayMarksProperties + Colour *string `json:"hydro_colour"` + Colpat *string `json:"hydro_colpat"` + Conrad *int `json:"hydro_conrad"` + Marsys *int `json:"hydro_marsys"` + Boyshp *int `json:"hydro_boyshp"` + Catcam *int `json:"hydro_catcam"` +} + +type boycarFeaturetype struct { + geom pointSlice + props *boycarProperties +} + +const ( + insertBOYCARSQL = ` +with a as ( + select users.current_user_area_utm() AS a +) +INSERT INTO waterway.fairway_marks_boycar ( + geom, + datsta, + datend, + persta, + perend, + objnam, + nobjnm, + inform, + ninfom, + scamin, + picrep, + txtdsc, + sordat, + sorind, + colour, + colpat, + conrad, + marsys, + boyshp, + catcam +) +SELECT newfm, $3, $4, $5, $6, $7, $8, $9, + $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21 + FROM ST_Transform(ST_GeomFromWKB($1, $2::integer), 4326) AS newfm (newfm) + WHERE pg_has_role('sys_admin', 'MEMBER') + OR ST_Intersects((select a from a), + ST_Transform(newfm, (select ST_SRID(a) from a))) +ON CONFLICT ( + CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, conrad, marsys, boyshp, catcam + ) AS waterway.fairway_marks_boycar) + ) + DO NOTHING +RETURNING id +` +) + +// Do executes the actual import. +func (fm *Boycar) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + start := time.Now() + + feedback.Info("Import fairway marks of type BOYCAR") + + fms, epsg, err := getFMFeatures( + feedback, + fm.FairwayMarks, + func() interface{} { return new(boycarProperties) }, + func(p pointSlice, props interface{}) interface{} { + return &boycarFeaturetype{p, props.(*boycarProperties)} + }, + ) + if err != nil { + return nil, err + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + insertStmt, err := tx.PrepareContext(ctx, insertBOYCARSQL) + if err != nil { + return nil, err + } + defer insertStmt.Close() + + savepoint := Savepoint(ctx, tx, "feature") + + var ( + outsideOrDup int + features int + ) + for _, fm := range fms { + + f := fm.(*boycarFeaturetype) + + var fmid int64 + err := savepoint(func() error { + err := insertStmt.QueryRowContext( + ctx, + f.geom.asWKB(), + epsg, + f.props.Datsta, + f.props.Datend, + f.props.Persta, + f.props.Perend, + f.props.Objnam, + f.props.Nobjnm, + f.props.Inform, + f.props.Ninfom, + f.props.Scamin, + f.props.Picrep, + f.props.Txtdsc, + f.props.Sordat, + f.props.Sorind, + f.props.Colour, + f.props.Colpat, + f.props.Conrad, + f.props.Marsys, + f.props.Boyshp, + f.props.Catcam, + ).Scan(&fmid) + return err + }) + switch { + case err == sql.ErrNoRows: + outsideOrDup++ + // ignore -> filtered by responsibility_areas + case err != nil: + feedback.Error(pgxutils.ReadableError{Err: err}.Error()) + default: + features++ + } + } + + if outsideOrDup > 0 { + feedback.Info( + "Features outside responsibility area and duplicates: %d", + outsideOrDup) + } + + if features == 0 { + err := UnchangedError("no valid new features found") + return nil, err + } + + if err = tx.Commit(); err == nil { + feedback.Info("Storing %d features took %s", + features, time.Since(start)) + } + + return nil, err +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/fm_boylat.go Fri Feb 14 14:33:42 2020 +0100 @@ -0,0 +1,244 @@ +// 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) 2020 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Tom Gottfried <tom.gottfried@intevation.de> + +package imports + +import ( + "context" + "database/sql" + "time" + + "gemma.intevation.de/gemma/pkg/pgxutils" +) + +// Boylat is an import job to import +// fairway marks of type BOYLAT in form of point geometries +// and attribute data from a WFS service. +type Boylat struct { + FairwayMarks +} + +// Description gives a short info about relevant facts of this import. +func (boylat *Boylat) Description() (string, error) { + return boylat.URL + "|" + boylat.FeatureType, nil +} + +// BOYLATJobKind is the import queue type identifier. +const BOYLATJobKind JobKind = "fm_boylat" + +type boylatJobCreator struct{} + +func init() { + RegisterJobCreator(BOYLATJobKind, boylatJobCreator{}) +} + +func (boylatJobCreator) Description() string { return "fairway marks boylat" } + +func (boylatJobCreator) AutoAccept() bool { return true } + +func (boylatJobCreator) Create() Job { return new(Boylat) } + +func (boylatJobCreator) Depends() [2][]string { + return [2][]string{ + {"fairway_marks_boylat"}, + {}, + } +} + +// StageDone is a NOP for fairway marks imports. +func (boylatJobCreator) StageDone(context.Context, *sql.Tx, int64) error { + return nil +} + +// CleanUp for fairway marks imports is a NOP. +func (*Boylat) CleanUp() error { return nil } + +type boylatProperties struct { + fairwayMarksProperties + Colour *string `json:"hydro_colour"` + Colpat *string `json:"hydro_colpat"` + Conrad *int `json:"hydro_conrad"` + HydroMarsys *int64 `json:"hydro_marsys,omitempty"` + IENCMarsys *int64 `json:"ienc_marsys,omitempty"` + Boyshp *int `json:"hydro_boyshp"` + HydroCatlam *int64 `json:"hydro_catlam,omitempty"` + IENCCatlam *int64 `json:"ienc_catlam,omitempty"` +} + +type boylatFeaturetype struct { + geom pointSlice + props *boylatProperties +} + +const ( + insertBOYLATSQL = ` +with a as ( + select users.current_user_area_utm() AS a +) +INSERT INTO waterway.fairway_marks_boylat ( + geom, + datsta, + datend, + persta, + perend, + objnam, + nobjnm, + inform, + ninfom, + scamin, + picrep, + txtdsc, + sordat, + sorind, + colour, + colpat, + conrad, + marsys, + boyshp, + catlam +) +SELECT newfm, $3, $4, $5, $6, $7, $8, $9, + $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21 + FROM ST_Transform(ST_GeomFromWKB($1, $2::integer), 4326) AS newfm (newfm) + WHERE pg_has_role('sys_admin', 'MEMBER') + OR ST_Intersects((select a from a), + ST_Transform(newfm, (select ST_SRID(a) from a))) +ON CONFLICT ( + CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, conrad, marsys, boyshp, catlam + ) AS waterway.fairway_marks_boylat) + ) + DO NOTHING +RETURNING id +` +) + +// Do executes the actual import. +func (fm *Boylat) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + start := time.Now() + + feedback.Info("Import fairway marks of type BOYLAT") + + fms, epsg, err := getFMFeatures( + feedback, + fm.FairwayMarks, + func() interface{} { return new(boylatProperties) }, + func(p pointSlice, props interface{}) interface{} { + return &boylatFeaturetype{p, props.(*boylatProperties)} + }, + ) + if err != nil { + return nil, err + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + insertStmt, err := tx.PrepareContext(ctx, insertBOYLATSQL) + if err != nil { + return nil, err + } + defer insertStmt.Close() + + savepoint := Savepoint(ctx, tx, "feature") + + var ( + outsideOrDup int + features int + ) + for _, fm := range fms { + + f := fm.(*boylatFeaturetype) + + var marsys sql.NullInt64 + if f.props.HydroMarsys != nil { + marsys = sql.NullInt64{Int64: *f.props.HydroMarsys, Valid: true} + } else if f.props.IENCMarsys != nil { + marsys = sql.NullInt64{Int64: *f.props.IENCMarsys, Valid: true} + } + + var catlam sql.NullInt64 + if f.props.HydroCatlam != nil { + catlam = sql.NullInt64{Int64: *f.props.HydroCatlam, Valid: true} + } else if f.props.IENCCatlam != nil { + catlam = sql.NullInt64{Int64: *f.props.IENCCatlam, Valid: true} + } + + var fmid int64 + err := savepoint(func() error { + err := insertStmt.QueryRowContext( + ctx, + f.geom.asWKB(), + epsg, + f.props.Datsta, + f.props.Datend, + f.props.Persta, + f.props.Perend, + f.props.Objnam, + f.props.Nobjnm, + f.props.Inform, + f.props.Ninfom, + f.props.Scamin, + f.props.Picrep, + f.props.Txtdsc, + f.props.Sordat, + f.props.Sorind, + f.props.Colour, + f.props.Colpat, + f.props.Conrad, + marsys, + f.props.Boyshp, + catlam, + ).Scan(&fmid) + return err + }) + switch { + case err == sql.ErrNoRows: + outsideOrDup++ + // ignore -> filtered by responsibility_areas + case err != nil: + feedback.Error(pgxutils.ReadableError{Err: err}.Error()) + default: + features++ + } + } + + if outsideOrDup > 0 { + feedback.Info( + "Features outside responsibility area and duplicates: %d", + outsideOrDup) + } + + if features == 0 { + err := UnchangedError("no valid new features found") + return nil, err + } + + if err = tx.Commit(); err == nil { + feedback.Info("Storing %d features took %s", + features, time.Since(start)) + } + + return nil, err +}
--- a/pkg/imports/modelconvert.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/imports/modelconvert.go Fri Feb 14 14:33:42 2020 +0100 @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2020 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // @@ -18,20 +18,23 @@ ) var kindToImportModel = map[JobKind]func() interface{}{ - BNJobKind: func() interface{} { return new(models.BottleneckImport) }, - GMJobKind: func() interface{} { return new(models.GaugeMeasurementImport) }, - FAJobKind: func() interface{} { return new(models.FairwayAvailabilityImport) }, - WXJobKind: func() interface{} { return new(models.WaterwayAxisImport) }, - WAJobKind: func() interface{} { return new(models.WaterwayAreaImport) }, - WGJobKind: func() interface{} { return new(models.WaterwayGaugeImport) }, - DMVJobKind: func() interface{} { return new(models.DistanceMarksVirtualImport) }, - FDJobKind: func() interface{} { return new(models.FairwayDimensionImport) }, - DMAJobKind: func() interface{} { return new(models.DistanceMarksAshoreImport) }, - STJobKind: func() interface{} { return new(models.StretchImport) }, - SECJobKind: func() interface{} { return new(models.SectionImport) }, - DSECJobKind: func() interface{} { return new(models.SectionDelete) }, - DSTJobKind: func() interface{} { return new(models.StretchDelete) }, - DSRJobKind: func() interface{} { return new(models.SoundingResultDelete) }, + BNJobKind: func() interface{} { return new(models.BottleneckImport) }, + GMJobKind: func() interface{} { return new(models.GaugeMeasurementImport) }, + FAJobKind: func() interface{} { return new(models.FairwayAvailabilityImport) }, + WXJobKind: func() interface{} { return new(models.WaterwayAxisImport) }, + WAJobKind: func() interface{} { return new(models.WaterwayAreaImport) }, + WGJobKind: func() interface{} { return new(models.WaterwayGaugeImport) }, + DMVJobKind: func() interface{} { return new(models.DistanceMarksVirtualImport) }, + FDJobKind: func() interface{} { return new(models.FairwayDimensionImport) }, + DMAJobKind: func() interface{} { return new(models.DistanceMarksAshoreImport) }, + BCNLATJobKind: func() interface{} { return new(models.FairwayMarksImport) }, + BOYCARJobKind: func() interface{} { return new(models.FairwayMarksImport) }, + BOYLATJobKind: func() interface{} { return new(models.FairwayMarksImport) }, + STJobKind: func() interface{} { return new(models.StretchImport) }, + SECJobKind: func() interface{} { return new(models.SectionImport) }, + DSECJobKind: func() interface{} { return new(models.SectionDelete) }, + DSTJobKind: func() interface{} { return new(models.StretchDelete) }, + DSRJobKind: func() interface{} { return new(models.SoundingResultDelete) }, } // ImportModelForJobKind returns the constructor function to @@ -136,6 +139,39 @@ } }, + BCNLATJobKind: func(input interface{}) interface{} { + fmi := input.(*models.FairwayMarksImport) + return &FairwayMarks{ + URL: fmi.URL, + FeatureType: fmi.FeatureType, + SortBy: nilString(fmi.SortBy), + User: nilString(fmi.User), + Password: nilString(fmi.Password), + } + }, + + BOYCARJobKind: func(input interface{}) interface{} { + fmi := input.(*models.FairwayMarksImport) + return &FairwayMarks{ + URL: fmi.URL, + FeatureType: fmi.FeatureType, + SortBy: nilString(fmi.SortBy), + User: nilString(fmi.User), + Password: nilString(fmi.Password), + } + }, + + BOYLATJobKind: func(input interface{}) interface{} { + fmi := input.(*models.FairwayMarksImport) + return &FairwayMarks{ + URL: fmi.URL, + FeatureType: fmi.FeatureType, + SortBy: nilString(fmi.SortBy), + User: nilString(fmi.User), + Password: nilString(fmi.Password), + } + }, + STJobKind: func(input interface{}) interface{} { sti := input.(*models.StretchImport) return &Stretch{
--- a/pkg/models/imports.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/models/imports.go Fri Feb 14 14:33:42 2020 +0100 @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018, 2019 by via donau +// Copyright (C) 2018, 2019, 2020 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // @@ -78,6 +78,11 @@ WFSImport } + // FairwayMarksImport specifies an import of fairway marks. + FairwayMarksImport struct { + WFSImport + } + // FairwayDimensionImport specifies an import of the waterway axis. FairwayDimensionImport struct { WFSImport
--- a/pkg/models/intservices.go Fri Feb 14 14:31:12 2020 +0100 +++ b/pkg/models/intservices.go Fri Feb 14 14:33:42 2020 +0100 @@ -54,20 +54,20 @@ ORDER by name` selectStyleSQL = ` -SELECT XMLSERIALIZE(DOCUMENT style AS text) +SELECT style FROM sys_admin.published_services WHERE name = $1 AND schema = $2` updateStyleSQL = ` UPDATE sys_admin.published_services -SET style = XMLPARSE(DOCUMENT $1) +SET style = $1 WHERE name = $2 AND schema = $3` ) var InternalServices = &IntServices{} -func (e *IntEntry) LoadStyle() (string, error) { - var style string +func (e *IntEntry) LoadStyle() ([]byte, error) { + var style []byte ctx := context.Background() err := auth.RunAs(ctx, "sys_admin", func(conn *sql.Conn) error { @@ -79,7 +79,7 @@ return style, err } -func UpdateInternalStyle(req *http.Request, name, style string) error { +func UpdateInternalStyle(req *http.Request, name string, style []byte) error { return auth.RunAsSessionUser(req, func(conn *sql.Conn) error { _, err := conn.ExecContext( req.Context(), updateStyleSQL,
--- a/schema/gemma.sql Fri Feb 14 14:31:12 2020 +0100 +++ b/schema/gemma.sql Fri Feb 14 14:33:42 2020 +0100 @@ -406,8 +406,8 @@ key_column varchar, -- SRID to be used with SQL view: srid int REFERENCES spatial_ref_sys, - -- SLD style document: - style xml CHECK(style IS DOCUMENT), + -- SLD style document or ZIP blob: + style bytea, as_wms boolean NOT NULL DEFAULT TRUE, as_wfs boolean NOT NULL DEFAULT TRUE, -- Either give a valid relation or a SQL statement: @@ -840,6 +840,93 @@ CHECK(measure_type = 'minimum guaranteed' OR value_lifetime IS NOT NULL) ) + + -- Attributes common to all fairway marks + -- according to IENC feature catalogue + CREATE TABLE fairway_marks ( + geom geography(POINT, 4326) NOT NULL, + datsta varchar, + datend varchar, + persta varchar, + perend varchar, + objnam varchar, + nobjnm varchar, + inform varchar, + ninfom varchar, + scamin int, + picrep varchar, + txtdsc varchar, + sordat varchar, + sorind varchar + ) + + -- Additional attributes for IENC features BCNLAT/bcnlat + CREATE TABLE fairway_marks_bcnlat ( + id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + colour varchar, + colpat varchar, + condtn int, + bcnshp int, + catlam int + ) INHERITS (fairway_marks) + -- Prevent identical entries using composite type comparison + -- (i.e. considering two NULL values in a field equal): + CREATE UNIQUE INDEX fairway_marks_bcnlat_distinct_rows + ON fairway_marks_bcnlat + ((CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, condtn, bcnshp, catlam + ) AS fairway_marks_bcnlat) + )) + + CREATE TABLE fairway_marks_bcnlat_dirimps ( + fm_bcnlat_id int REFERENCES fairway_marks_bcnlat, + dirimp smallint REFERENCES dirimps, + PRIMARY KEY (fm_bcnlat_id, dirimp) + ) + + -- Additional attributes for IENC feature BOYCAR + CREATE TABLE fairway_marks_boycar ( + id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + colour varchar, + colpat varchar, + conrad int, + marsys int, + boyshp int, + catcam int + ) INHERITS (fairway_marks) + -- Prevent identical entries using composite type comparison + -- (i.e. considering two NULL values in a field equal): + CREATE UNIQUE INDEX fairway_marks_boycar_distinct_rows + ON fairway_marks_boycar + ((CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, conrad, marsys, boyshp, catcam + ) AS fairway_marks_boycar) + )) + + -- Additional attributes for IENC feature BOYLAT + CREATE TABLE fairway_marks_boylat ( + id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + colour varchar, + colpat varchar, + conrad int, + marsys int, + boyshp int, + catlam int + ) INHERITS (fairway_marks) + -- Prevent identical entries using composite type comparison + -- (i.e. considering two NULL values in a field equal): + CREATE UNIQUE INDEX fairway_marks_boylat_distinct_rows + ON fairway_marks_boylat + ((CAST((geom, + datsta, datend, persta, perend, objnam, nobjnm, inform, ninfom, + scamin, picrep, txtdsc, sordat, sorind, + 0, colour, colpat, conrad, marsys, boyshp, catlam + ) AS fairway_marks_boylat) + )) ;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1317/01.migrate_styles.sql Fri Feb 14 14:33:42 2020 +0100 @@ -0,0 +1,4 @@ +ALTER TABLE sys_admin.published_services ADD COLUMN bin_blob bytea; +UPDATE sys_admin.published_services SET bin_blob = style::text::bytea WHERE style is NOT NULL; +ALTER TABLE sys_admin.published_services DROP COLUMN style; +ALTER TABLE sys_admin.published_services RENAME COLUMN bin_blob TO style;
--- a/schema/version.sql Fri Feb 14 14:31:12 2020 +0100 +++ b/schema/version.sql Fri Feb 14 14:33:42 2020 +0100 @@ -1,1 +1,1 @@ -INSERT INTO gemma_schema_version(version) VALUES (1316); +INSERT INTO gemma_schema_version(version) VALUES (1317);
--- a/style-templates/upload-styles.sh Fri Feb 14 14:31:12 2020 +0100 +++ b/style-templates/upload-styles.sh Fri Feb 14 14:33:42 2020 +0100 @@ -19,9 +19,11 @@ usage() { cat <<EOF -$ME [OPTION]... +$ME [OPTION]... [input-file]... -Upload map styles to gemma. +Upload map styles to gemma. Uses either the given input files or all files +in the directory where this script resides. Input files must have the name +of a published layer in gemma and the suffix .sld-template. Options: -P, --g_port=GPORT connect to gemma server at GPORT. Default 8000. @@ -83,10 +85,6 @@ esac done -if [ $# != 0 ] ; then - { usage ; exit 23 ; } -fi - # Main ------------------------------------------------------------ # Login to gemma server @@ -108,11 +106,17 @@ if jq -e 'any(. == "sys_admin")' <<<"$roles" > /dev/null then echo "== Configuring geoserver styles" >&2 - for style in $(basename -s .sld-template $(ls $datadir/*.sld-template)) + if [ $# -gt 0 ]; then + files=("$@") + else + files=($(find . -name "*.sld-template" -or -name "*.zip")) + fi + for file in ${files[@]} do - echo "uploading $style ..." + style=$(basename $(basename "$file" .zip) .sld-template) + echo "uploading ${style} ..." curl -f -s -S -H "X-Gemma-Auth:${token}" -X POST \ - -F style=@"${datadir}/${style}.sld-template" \ + -F style=@"${file}" \ "http://${g_host}:${g_port}/api/geo/style/${style}" done echo 'done.'