@defra/forms-engine-plugin 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/geospatial-map.js +34 -3
- package/.server/client/javascripts/geospatial-map.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +0 -6
- package/.server/client/javascripts/map.js +1 -14
- package/.server/client/javascripts/map.js.map +1 -1
- package/.server/server/plugins/engine/components/GeospatialField.d.ts +1 -0
- package/.server/server/plugins/engine/components/GeospatialField.js +6 -2
- package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +2 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.js +32 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.test.js +21 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.test.js.map +1 -1
- package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -1
- package/.server/server/plugins/map/routes/index.d.ts +9 -1
- package/.server/server/plugins/map/routes/index.js +51 -2
- package/.server/server/plugins/map/routes/index.js.map +1 -1
- package/.server/server/plugins/map/routes/vts/countries.geojson +39285 -0
- package/.server/server/plugins/map/types.d.ts +23 -1
- package/.server/server/plugins/map/types.js +14 -1
- package/.server/server/plugins/map/types.js.map +1 -1
- package/package.json +10 -8
- package/src/client/javascripts/geospatial-map.js +39 -3
- package/src/client/javascripts/map.js +1 -14
- package/src/server/plugins/engine/components/GeospatialField.ts +8 -2
- package/src/server/plugins/engine/components/helpers/geospatial.ts +48 -2
- package/src/server/plugins/engine/views/components/geospatialfield.html +1 -1
- package/src/server/plugins/map/routes/index.js +58 -2
- package/src/server/plugins/map/routes/vts/countries.geojson +39285 -0
- package/src/server/plugins/map/types.js +14 -1
|
@@ -33,6 +33,19 @@ export type MapReverseGeocodeQuery = {
|
|
|
33
33
|
*/
|
|
34
34
|
northing: number;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Geospatial countries query params
|
|
38
|
+
*/
|
|
39
|
+
export type GeospatialCountriesQuery = {
|
|
40
|
+
/**
|
|
41
|
+
* - the country to omit
|
|
42
|
+
*/
|
|
43
|
+
omit: string;
|
|
44
|
+
/**
|
|
45
|
+
* - the country to include
|
|
46
|
+
*/
|
|
47
|
+
only: string;
|
|
48
|
+
};
|
|
36
49
|
/**
|
|
37
50
|
* Map geocode get request
|
|
38
51
|
*/
|
|
@@ -52,7 +65,7 @@ export type MapGeocodeGetRequestRefs = {
|
|
|
52
65
|
Query: MapGeocodeQuery;
|
|
53
66
|
};
|
|
54
67
|
/**
|
|
55
|
-
* Map
|
|
68
|
+
* Map reverse geocode get request
|
|
56
69
|
*/
|
|
57
70
|
export type MapReverseGeocodeGetRequestRefs = {
|
|
58
71
|
/**
|
|
@@ -60,6 +73,15 @@ export type MapReverseGeocodeGetRequestRefs = {
|
|
|
60
73
|
*/
|
|
61
74
|
Query: MapReverseGeocodeQuery;
|
|
62
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Map countries geojson get request
|
|
78
|
+
*/
|
|
79
|
+
export type GeospatialCountriesGetRequestRefs = {
|
|
80
|
+
/**
|
|
81
|
+
* - Request query
|
|
82
|
+
*/
|
|
83
|
+
Query: GeospatialCountriesQuery;
|
|
84
|
+
};
|
|
63
85
|
export type MapProxyRequestRefs = MapProxyGetRequestRefs;
|
|
64
86
|
export type MapGeocodeRequestRefs = MapGeocodeGetRequestRefs;
|
|
65
87
|
export type MapReverseGeocodeRequestRefs = MapReverseGeocodeGetRequestRefs;
|
|
@@ -28,6 +28,13 @@
|
|
|
28
28
|
* @property {number} northing - the Northing point
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Geospatial countries query params
|
|
33
|
+
* @typedef {object} GeospatialCountriesQuery
|
|
34
|
+
* @property {string} omit - the country to omit
|
|
35
|
+
* @property {string} only - the country to include
|
|
36
|
+
*/
|
|
37
|
+
|
|
31
38
|
/**
|
|
32
39
|
* Map geocode get request
|
|
33
40
|
* @typedef {object} MapProxyGetRequestRefs
|
|
@@ -41,11 +48,17 @@
|
|
|
41
48
|
*/
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
|
-
* Map
|
|
51
|
+
* Map reverse geocode get request
|
|
45
52
|
* @typedef {object} MapReverseGeocodeGetRequestRefs
|
|
46
53
|
* @property {MapReverseGeocodeQuery} Query - Request query
|
|
47
54
|
*/
|
|
48
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Map countries geojson get request
|
|
58
|
+
* @typedef {object} GeospatialCountriesGetRequestRefs
|
|
59
|
+
* @property {GeospatialCountriesQuery} Query - Request query
|
|
60
|
+
*/
|
|
61
|
+
|
|
49
62
|
/**
|
|
50
63
|
* @typedef {MapProxyGetRequestRefs} MapProxyRequestRefs
|
|
51
64
|
* @typedef {MapGeocodeGetRequestRefs} MapGeocodeRequestRefs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/map/types.js"],"sourcesContent":["/**\n * @typedef {{\n * ordnanceSurveyApiKey: string\n * ordnanceSurveyApiSecret: string\n * }} MapConfiguration\n */\n\n//\n// Route types\n//\n\n/**\n * Map proxy query params\n * @typedef {object} MapProxyQuery\n * @property {string} url - the proxied url\n */\n\n/**\n * Map geocode query params\n * @typedef {object} MapGeocodeQuery\n * @property {string} query - name query\n */\n\n/**\n * Map reverse geocode query params\n * @typedef {object} MapReverseGeocodeQuery\n * @property {number} easting - the Easting point\n * @property {number} northing - the Northing point\n */\n\n/**\n * Map geocode get request\n * @typedef {object} MapProxyGetRequestRefs\n * @property {MapProxyQuery} Query - Request query\n */\n\n/**\n * Map geocode get request\n * @typedef {object} MapGeocodeGetRequestRefs\n * @property {MapGeocodeQuery} Query - Request query\n */\n\n/**\n * Map
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/map/types.js"],"sourcesContent":["/**\n * @typedef {{\n * ordnanceSurveyApiKey: string\n * ordnanceSurveyApiSecret: string\n * }} MapConfiguration\n */\n\n//\n// Route types\n//\n\n/**\n * Map proxy query params\n * @typedef {object} MapProxyQuery\n * @property {string} url - the proxied url\n */\n\n/**\n * Map geocode query params\n * @typedef {object} MapGeocodeQuery\n * @property {string} query - name query\n */\n\n/**\n * Map reverse geocode query params\n * @typedef {object} MapReverseGeocodeQuery\n * @property {number} easting - the Easting point\n * @property {number} northing - the Northing point\n */\n\n/**\n * Geospatial countries query params\n * @typedef {object} GeospatialCountriesQuery\n * @property {string} omit - the country to omit\n * @property {string} only - the country to include\n */\n\n/**\n * Map geocode get request\n * @typedef {object} MapProxyGetRequestRefs\n * @property {MapProxyQuery} Query - Request query\n */\n\n/**\n * Map geocode get request\n * @typedef {object} MapGeocodeGetRequestRefs\n * @property {MapGeocodeQuery} Query - Request query\n */\n\n/**\n * Map reverse geocode get request\n * @typedef {object} MapReverseGeocodeGetRequestRefs\n * @property {MapReverseGeocodeQuery} Query - Request query\n */\n\n/**\n * Map countries geojson get request\n * @typedef {object} GeospatialCountriesGetRequestRefs\n * @property {GeospatialCountriesQuery} Query - Request query\n */\n\n/**\n * @typedef {MapProxyGetRequestRefs} MapProxyRequestRefs\n * @typedef {MapGeocodeGetRequestRefs} MapGeocodeRequestRefs\n * @typedef {MapReverseGeocodeGetRequestRefs} MapReverseGeocodeRequestRefs\n * @typedef {Request<MapGeocodeGetRequestRefs>} MapProxyGetRequest\n * @typedef {Request<MapGeocodeGetRequestRefs>} MapGeocodeGetRequest\n * @typedef {Request<MapReverseGeocodeGetRequestRefs>} MapReverseGeocodeGetRequest\n * @typedef {MapProxyGetRequest | MapGeocodeGetRequest | MapReverseGeocodeGetRequest} MapRequest\n */\n\n//\n// Service types\n//\n\n/**\n * @typedef {object} OsNamesFindResponse\n * @property {OsNamesFindHeader} header - Metadata about the search request and results.\n * @property {OsNamesFindResult[]} results - An array of matched place records from the search.\n */\n\n/**\n * @typedef {object} OsNamesFindHeader\n * @property {string} uri - The query URI (usually same as search text).\n * @property {string} query - The original text query string passed to the API.\n * @property {string} format - The response format returned (e.g., \"JSON\").\n * @property {number} maxresults - The maximum number of results requested.\n * @property {number} offset - The offset used in the search results.\n * @property {number} totalresults - The total number of results that matched the query.\n * @property {string} filter - The original filter string passed to the API.\n */\n\n/**\n * @typedef {object} OsNamesFindGazetteerEntry\n * @property {string} ID - Unique identifier for the place/feature.\n * @property {string} NAMES_URI - A URI (identifier) for this named feature.\n * @property {string} NAME1 - Primary name of the feature.\n * @property {string} TYPE - General type classification of the feature.\n * @property {string} LOCAL_TYPE - Local or more specific type classification.\n * @property {number} GEOMETRY_X - Easting coordinate (British National Grid).\n * @property {number} GEOMETRY_Y - Northing coordinate (British National Grid).\n * @property {number} MOST_DETAIL_VIEW_RES - Most detailed resolution available.\n * @property {number} LEAST_DETAIL_VIEW_RES - Least detailed resolution available.\n * @property {number} [MBR_XMIN] - Minimum bounding box X (easting).\n * @property {number} [MBR_YMIN] - Minimum bounding box Y (northing).\n * @property {number} [MBR_XMAX] - Maximum bounding box X (easting).\n * @property {number} [MBR_YMAX] - Maximum bounding box Y (northing).\n * @property {string} [DISTRICT_BOROUGH] - (Optional) District borough.\n * @property {string} [DISTRICT_BOROUGH_URI] - (Optional) URI for the district borough.\n * @property {string} [DISTRICT_BOROUGH_TYPE] - (Optional) Type of the district borough.\n * @property {string} [POSTCODE_DISTRICT] - (Optional) Postcode district.\n * @property {string} [POSTCODE_DISTRICT_URI] - (Optional) URI for the postcode district.\n * @property {string} [POPULATED_PLACE] - (Optional) Name of associated populated place.\n * @property {string} [POPULATED_PLACE_URI] - (Optional) URI of populated place.\n * @property {string} [POPULATED_PLACE_TYPE] - (Optional) Type of populated place.\n * @property {string} [COUNTY_UNITARY] - (Optional) County or unitary authority name.\n * @property {string} [COUNTY_UNITARY_URI] - (Optional) URI for county/unitary authority.\n * @property {string} [COUNTY_UNITARY_TYPE] - (Optional) Classification of county/unitary authority.\n * @property {string} [REGION] - (Optional) Region name.\n * @property {string} [REGION_URI] - (Optional) URI for region.\n * @property {string} [COUNTRY] - (Optional) Country name.\n * @property {string} [COUNTRY_URI] - (Optional) URI for country.\n */\n\n/**\n * OS names GAZETTEER_ENTRY response\n * @typedef {object} OsNamesFindResult\n * @property {OsNamesFindGazetteerEntry} GAZETTEER_ENTRY - Gazetteer entry\n */\n\n/**\n * @import { Request } from '@hapi/hapi'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
"dev:debug": "concurrently \"npm run client:watch\" \"npm run server:watch:debug\" --kill-others --names \"client,server\" --prefix-colors \"red.dim,blue.dim\"",
|
|
45
45
|
"format": "npm run format:check -- --write",
|
|
46
46
|
"format:check": "prettier --cache --cache-location .cache/prettier --cache-strategy content --check \"**/*.{cjs,js,json,md,mjs,scss,ts}\"",
|
|
47
|
+
"predocs:dev": "node scripts/generate-schema-docs.js && node scripts/generate-component-docs.js",
|
|
47
48
|
"docs:dev": "BROWSERSLIST_ENV=javascripts docusaurus start --host 0.0.0.0",
|
|
48
49
|
"docs:build": "BROWSERSLIST_ENV=javascripts docusaurus build",
|
|
49
|
-
"docs:build:all": "node scripts/generate-schema-docs.js && npm run docs:build",
|
|
50
|
+
"docs:build:all": "node scripts/generate-schema-docs.js && node scripts/generate-component-docs.js && npm run docs:build",
|
|
50
51
|
"docs:serve": "docusaurus serve --host 0.0.0.0",
|
|
51
52
|
"docs:clear": "docusaurus clear",
|
|
52
53
|
"generate-schema-docs": "node scripts/generate-schema-docs.js",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"minimatch": "3.1.5"
|
|
79
80
|
},
|
|
80
81
|
"serialize-javascript": ">=7.0.3",
|
|
81
|
-
"react-router": "^
|
|
82
|
+
"react-router": "^5.3.4"
|
|
82
83
|
},
|
|
83
84
|
"engines": {
|
|
84
85
|
"node": ">=22.11.0 <25.0.0",
|
|
@@ -86,9 +87,9 @@
|
|
|
86
87
|
},
|
|
87
88
|
"license": "SEE LICENSE IN LICENSE",
|
|
88
89
|
"dependencies": {
|
|
89
|
-
"@defra/forms-model": "^3.0.
|
|
90
|
+
"@defra/forms-model": "^3.0.655",
|
|
90
91
|
"@defra/hapi-tracing": "^1.29.0",
|
|
91
|
-
"@defra/interactive-map": "^0.0.
|
|
92
|
+
"@defra/interactive-map": "^0.0.22-alpha",
|
|
92
93
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
93
94
|
"@hapi/boom": "^10.0.1",
|
|
94
95
|
"@hapi/bourne": "^3.0.0",
|
|
@@ -104,6 +105,7 @@
|
|
|
104
105
|
"@hapi/wreck": "^18.1.0",
|
|
105
106
|
"@hapi/yar": "^11.0.3",
|
|
106
107
|
"@turf/bbox": "^7.3.4",
|
|
108
|
+
"@turf/boolean-within": "^7.3.5",
|
|
107
109
|
"@turf/centroid": "^7.3.4",
|
|
108
110
|
"@types/humanize-duration": "^3.27.4",
|
|
109
111
|
"accessible-autocomplete": "^3.0.1",
|
|
@@ -143,9 +145,9 @@
|
|
|
143
145
|
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
144
146
|
"@babel/preset-env": "^7.28.5",
|
|
145
147
|
"@babel/preset-typescript": "^7.28.5",
|
|
146
|
-
"@defra/docusaurus-theme-govuk": "^0.0.
|
|
147
|
-
"@docusaurus/core": "^3.
|
|
148
|
-
"@docusaurus/plugin-content-docs": "^3.
|
|
148
|
+
"@defra/docusaurus-theme-govuk": "^0.0.22-alpha",
|
|
149
|
+
"@docusaurus/core": "^3.10.1",
|
|
150
|
+
"@docusaurus/plugin-content-docs": "^3.10.1",
|
|
149
151
|
"@easyops-cn/docusaurus-search-local": "^0.55.0",
|
|
150
152
|
"@hapi/basic": "^7.0.2",
|
|
151
153
|
"@mdx-js/react": "^3.1.1",
|
|
@@ -39,8 +39,8 @@ const lineFeatureProperties = {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const polygonFeatureProperties = {
|
|
42
|
-
stroke: '
|
|
43
|
-
fill: 'rgba(
|
|
42
|
+
stroke: 'rgb(0, 0, 0)',
|
|
43
|
+
fill: 'rgba(255, 221, 0, 0.2)',
|
|
44
44
|
strokeWidth: 2
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -111,11 +111,47 @@ export function processGeospatial(config, geospatial, index) {
|
|
|
111
111
|
const geojson = getGeoJSON(geospatialInput)
|
|
112
112
|
const bounds = geojson.features.length ? getBoundingBox(geojson) : undefined
|
|
113
113
|
const drawPlugin = defra.drawMLPlugin()
|
|
114
|
+
const plugins = [drawPlugin]
|
|
115
|
+
const country = geospatial.dataset.country
|
|
116
|
+
|
|
117
|
+
if (country) {
|
|
118
|
+
// Add the country bounds as a dataset plugin to show the valid area on the map
|
|
119
|
+
// and provide feedback to the user when they add features outside of the bounds.
|
|
120
|
+
const datasetsPlugin = defra.datasetsMaplibrePlugin({
|
|
121
|
+
datasets: [
|
|
122
|
+
{
|
|
123
|
+
id: 'invalid-area',
|
|
124
|
+
label: 'Invalid areas',
|
|
125
|
+
geojson: `${config.apiPath}/maps/countries.geojson?omit=${country}`,
|
|
126
|
+
showInKey: false,
|
|
127
|
+
showInMenu: false,
|
|
128
|
+
style: {
|
|
129
|
+
stroke: 'gray',
|
|
130
|
+
strokeWidth: 1,
|
|
131
|
+
fill: 'rgba(211,211,211,0.8)'
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'valid-area',
|
|
136
|
+
label: 'Valid areas',
|
|
137
|
+
geojson: `${config.apiPath}/maps/countries.geojson?only=${country}`,
|
|
138
|
+
showInKey: false,
|
|
139
|
+
showInMenu: false,
|
|
140
|
+
style: {
|
|
141
|
+
stroke: 'rgba(0,112,60,1)',
|
|
142
|
+
strokeWidth: 1
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
plugins.push(datasetsPlugin)
|
|
149
|
+
}
|
|
114
150
|
|
|
115
151
|
const initConfig = {
|
|
116
152
|
...defaultConfig,
|
|
117
153
|
bounds,
|
|
118
|
-
plugins
|
|
154
|
+
plugins
|
|
119
155
|
}
|
|
120
156
|
|
|
121
157
|
const { map, interactPlugin } = createMap(mapId, initConfig, config)
|
|
@@ -240,18 +240,6 @@ export function makeTileRequestTransformer(apiPath) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
/**
|
|
244
|
-
* Temporary transform request function to transform geocode requests. Fixed in v0.0.18 of interactive map so this is not needed when we upgrade.
|
|
245
|
-
* @param {object} request
|
|
246
|
-
* @param {string} request.url
|
|
247
|
-
* @param {{ method: 'get' }} request.options
|
|
248
|
-
* @returns {Request}
|
|
249
|
-
*/
|
|
250
|
-
export const transformGeocodeRequest = (request) => {
|
|
251
|
-
const url = new URL(request.url, window.location.origin)
|
|
252
|
-
return new Request(url.toString(), request.options)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
243
|
/**
|
|
256
244
|
* Create a Defra map instance
|
|
257
245
|
* @param {string} mapId - the map id
|
|
@@ -267,7 +255,7 @@ export function createMap(mapId, initConfig, mapsConfig) {
|
|
|
267
255
|
|
|
268
256
|
const interactPlugin = defra.interactPlugin({
|
|
269
257
|
markerColor: { outdoor: '#ff0000', dark: '#00ff00' },
|
|
270
|
-
|
|
258
|
+
interactionModes: ['placeMarker'],
|
|
271
259
|
multiSelect: false
|
|
272
260
|
})
|
|
273
261
|
|
|
@@ -332,7 +320,6 @@ export function createMap(mapId, initConfig, mapsConfig) {
|
|
|
332
320
|
}),
|
|
333
321
|
interactPlugin,
|
|
334
322
|
defra.searchPlugin({
|
|
335
|
-
transformRequest: transformGeocodeRequest,
|
|
336
323
|
osNamesURL: `${apiPath}/geocode-proxy?query={query}`,
|
|
337
324
|
width: '300px',
|
|
338
325
|
showMarker: false
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FormComponent,
|
|
7
7
|
isGeospatialState
|
|
8
8
|
} from './FormComponent.js'
|
|
9
|
-
import {
|
|
9
|
+
import { getGeospatialSchema } from './helpers/geospatial.js'
|
|
10
10
|
import { messageTemplate } from '../pageControllers/validationOptions.js'
|
|
11
11
|
import {
|
|
12
12
|
type ErrorMessageTemplateList,
|
|
@@ -31,7 +31,9 @@ export class GeospatialField extends FormComponent {
|
|
|
31
31
|
|
|
32
32
|
const { options } = def
|
|
33
33
|
|
|
34
|
-
let formSchema =
|
|
34
|
+
let formSchema = getGeospatialSchema(options.countries?.at(0))
|
|
35
|
+
.label(this.label)
|
|
36
|
+
.required()
|
|
35
37
|
|
|
36
38
|
formSchema = formSchema.max(50)
|
|
37
39
|
|
|
@@ -90,6 +92,7 @@ export class GeospatialField extends FormComponent {
|
|
|
90
92
|
|
|
91
93
|
return {
|
|
92
94
|
...viewModel,
|
|
95
|
+
country: this.options.countries?.at(0),
|
|
93
96
|
value
|
|
94
97
|
}
|
|
95
98
|
}
|
|
@@ -101,6 +104,9 @@ export class GeospatialField extends FormComponent {
|
|
|
101
104
|
if (err.name === 'description') {
|
|
102
105
|
err.href = `#description_${err.path[1]}`
|
|
103
106
|
err.text = `Enter description for location ${Number(err.path[1]) + 1}`
|
|
107
|
+
} else if (typeof err.name === 'number' && err.context?.country) {
|
|
108
|
+
err.href = `#description_${err.path[1]}`
|
|
109
|
+
err.text = `Location ${Number(err.path[1]) + 1} must be in ${err.context.country}`
|
|
104
110
|
}
|
|
105
111
|
})
|
|
106
112
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeospatialFieldOptionsCountryEnum,
|
|
3
|
+
type GeospatialFieldOptionsCountry
|
|
4
|
+
} from '@defra/forms-model'
|
|
1
5
|
import Bourne from '@hapi/bourne'
|
|
2
|
-
import
|
|
6
|
+
import { booleanWithin } from '@turf/boolean-within'
|
|
7
|
+
import JoiBase, { type CustomValidator } from 'joi'
|
|
3
8
|
|
|
4
9
|
import {
|
|
5
10
|
type Coordinates,
|
|
@@ -7,6 +12,14 @@ import {
|
|
|
7
12
|
type FeatureProperties,
|
|
8
13
|
type Geometry
|
|
9
14
|
} from '../../types.js'
|
|
15
|
+
import { countries } from '../../../map/routes/index.js'
|
|
16
|
+
|
|
17
|
+
const countriesDesc: Record<GeospatialFieldOptionsCountryEnum, string> = {
|
|
18
|
+
[GeospatialFieldOptionsCountryEnum.England]: 'England',
|
|
19
|
+
[GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',
|
|
20
|
+
[GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',
|
|
21
|
+
[GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'
|
|
22
|
+
}
|
|
10
23
|
|
|
11
24
|
const Joi = JoiBase.extend({
|
|
12
25
|
type: 'array',
|
|
@@ -83,11 +96,44 @@ const featureSchema = Joi.object<Feature>().keys({
|
|
|
83
96
|
geometry: featureGeometrySchema
|
|
84
97
|
})
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
const geospatialSchema = Joi.array<Feature[]>()
|
|
87
100
|
.items(featureSchema)
|
|
88
101
|
.unique('id')
|
|
89
102
|
.required()
|
|
90
103
|
|
|
104
|
+
export function getGeospatialSchema(country?: GeospatialFieldOptionsCountry) {
|
|
105
|
+
if (!country) {
|
|
106
|
+
return geospatialSchema
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const validateCountryBounds: CustomValidator = (value, helpers) => {
|
|
110
|
+
const countryFeature = countries.features.find(
|
|
111
|
+
(feature) => feature.id === country
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (!countryFeature) {
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
116
|
+
return value
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = booleanWithin(value, countryFeature)
|
|
120
|
+
|
|
121
|
+
if (!result) {
|
|
122
|
+
return helpers.error('any.custom', {
|
|
123
|
+
country: countriesDesc[country as GeospatialFieldOptionsCountryEnum]
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
128
|
+
return value
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return Joi.array<Feature[]>()
|
|
132
|
+
.items(featureSchema.custom(validateCountryBounds))
|
|
133
|
+
.unique('id')
|
|
134
|
+
.required()
|
|
135
|
+
}
|
|
136
|
+
|
|
91
137
|
/**
|
|
92
138
|
* @import { CustomHelpers } from 'joi'
|
|
93
139
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{% from "govuk/components/textarea/macro.njk" import govukTextarea %}
|
|
2
2
|
|
|
3
3
|
{% macro GeospatialField(component) %}
|
|
4
|
-
<div class="app-geospatial-field">
|
|
4
|
+
<div class="app-geospatial-field" data-country="{{component.model.country}}">
|
|
5
5
|
{{ govukTextarea(component.model) }}
|
|
6
6
|
</div>
|
|
7
7
|
{% endmacro %}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
1
2
|
import { resolve } from 'node:path'
|
|
2
3
|
|
|
4
|
+
import { GeospatialFieldOptionsCountryEnum } from '@defra/forms-model'
|
|
3
5
|
import { StatusCodes } from 'http-status-codes'
|
|
4
6
|
import Joi from 'joi'
|
|
5
7
|
|
|
@@ -10,6 +12,18 @@ import {
|
|
|
10
12
|
request as httpRequest
|
|
11
13
|
} from '../../../services/httpService.js'
|
|
12
14
|
|
|
15
|
+
const filePath = resolve(import.meta.dirname, './vts/countries.geojson')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {FeatureCollection}
|
|
19
|
+
*/
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
21
|
+
export const countries = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
22
|
+
|
|
23
|
+
export const countrySchema = Joi.string().valid(
|
|
24
|
+
...Object.values(GeospatialFieldOptionsCountryEnum)
|
|
25
|
+
)
|
|
26
|
+
|
|
13
27
|
/**
|
|
14
28
|
* Gets the map support routes
|
|
15
29
|
* @param {MapConfiguration} options - ordnance survey names api key
|
|
@@ -20,7 +34,8 @@ export function getRoutes(options) {
|
|
|
20
34
|
mapProxyRoute(options),
|
|
21
35
|
tileProxyRoute(options),
|
|
22
36
|
geocodeProxyRoute(options),
|
|
23
|
-
reverseGeocodeProxyRoute(options)
|
|
37
|
+
reverseGeocodeProxyRoute(options),
|
|
38
|
+
getGeospatialCountries()
|
|
24
39
|
]
|
|
25
40
|
}
|
|
26
41
|
|
|
@@ -188,7 +203,48 @@ function mapStyleResourceRoutes() {
|
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Resource routes to return sprites and glyphs
|
|
208
|
+
* @returns {ServerRoute<GeospatialCountriesGetRequestRefs>}
|
|
209
|
+
*/
|
|
210
|
+
function getGeospatialCountries() {
|
|
211
|
+
return {
|
|
212
|
+
method: 'GET',
|
|
213
|
+
path: '/api/maps/countries.geojson',
|
|
214
|
+
handler: (request) => {
|
|
215
|
+
const { omit, only } = request.query
|
|
216
|
+
|
|
217
|
+
if (omit) {
|
|
218
|
+
return {
|
|
219
|
+
...countries,
|
|
220
|
+
features: countries.features.filter((feature) => feature.id !== omit)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (only) {
|
|
225
|
+
return {
|
|
226
|
+
...countries,
|
|
227
|
+
features: countries.features.filter((feature) => feature.id === only)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return countries
|
|
232
|
+
},
|
|
233
|
+
options: {
|
|
234
|
+
validate: {
|
|
235
|
+
query: Joi.object()
|
|
236
|
+
.keys({
|
|
237
|
+
omit: countrySchema.optional(),
|
|
238
|
+
only: countrySchema.optional()
|
|
239
|
+
})
|
|
240
|
+
.optional()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
191
246
|
/**
|
|
192
247
|
* @import { ServerRoute } from '@hapi/hapi'
|
|
193
|
-
* @import {
|
|
248
|
+
* @import { FeatureCollection } from 'geojson'
|
|
249
|
+
* @import { MapConfiguration, MapProxyGetRequestRefs, MapGeocodeGetRequestRefs, MapReverseGeocodeGetRequestRefs, GeospatialCountriesGetRequestRefs } from '../types.js'
|
|
194
250
|
*/
|