@dataforsyningen/saul 3.1.2 → 3.2.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/config.js.example CHANGED
@@ -2,9 +2,10 @@ export default {
2
2
 
3
3
  // STAC TOKEN can be aquired from https://dataforsyningen.dk/
4
4
  API_STAC_TOKEN: "",
5
- API_STAC_BASEURL: "https://api.dataforsyningen.dk/skraafotoapi",
5
+ API_STAC_BASEURL: "https://api.dataforsyningen.dk/skraafoto_api_test/v1.0",
6
6
 
7
7
  // DHM URL and auth can be aquired from https://datafordeler.dk/dataoversigt/danmarks-hoejdemodel-dhm/koter/
8
+ API_DHM_WCS_BASEURL: "https://services.datafordeler.dk/DHMNedboer/dhm_wcs/1.0.0/WCS",
8
9
  API_DHM_BASEURL: "https://services.datafordeler.dk/DHMTerraen/DHMKoter/1.0.0/GEOREST/HentKoter",
9
10
  API_DHM_USERNAME: "",
10
11
  API_DHM_PASSWORD: ""
package/index.js CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  export {
4
4
  image2world,
5
- world2image,
5
+ world2image, // Deprecated. Use getImageXY
6
6
  getWorldXYZ,
7
+ getImageXY,
7
8
  getZ, // Deprecated. Use getElevation
8
9
  iterate // Deprecated. Use getWorldXYZ
9
10
  } from './modules/saul-core.js'
@@ -12,6 +13,7 @@ export {
12
13
  postSTAC,
13
14
  getSTAC,
14
15
  getDHM,
16
+ getTerrainGeoTIFF,
15
17
  get,
16
18
  post
17
19
  } from './modules/api.js'
@@ -22,6 +24,5 @@ export {
22
24
  } from './modules/saul-projection.js'
23
25
 
24
26
  export {
25
- getTerrainGeoTIFF,
26
27
  getElevation
27
28
  } from './modules/saul-elevation.js'
package/modules/api.js CHANGED
@@ -2,6 +2,8 @@
2
2
  * SAUL API utilities
3
3
  */
4
4
 
5
+ import { fromArrayBuffer } from 'geotiff'
6
+
5
7
  let error_msg
6
8
  let load_stack = []
7
9
 
@@ -184,10 +186,50 @@ function postSTAC(endpoint, data, auth) {
184
186
  .then((data) => data)
185
187
  }
186
188
 
189
+
190
+ /** Converts raw GeoTIFF arrayBuffer to image */
191
+ async function consumeGeoTIFF(raw_data) {
192
+ const tiff = await fromArrayBuffer(raw_data)
193
+ const image = await tiff.getImage()
194
+ return image
195
+ }
196
+
197
+
198
+ /** Fetches a GeoTIFF with elevation data matching the bounding box of a STAC API item (image)
199
+ * @param {object} stac_item - STAC API item from a featureCollection request
200
+ * @param {{API_DHM_WCS_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
201
+ * @param {number} [resolution] - Resolution (1 - 0.01). Higher number means more pixels and better precision.
202
+ * @returns {object} GeoTiff data
203
+ */
204
+ function getTerrainGeoTIFF(stac_item, auth, resolution = 0.05) {
205
+
206
+ const bbox = stac_item.bbox
207
+ const width = Math.round( stac_item.properties['proj:shape'][0] * resolution )
208
+ const height = Math.round( stac_item.properties['proj:shape'][1] * resolution )
209
+
210
+ // GET request for DHM WCS data
211
+ let url = auth.API_DHM_WCS_BASEURL
212
+ url += '?SERVICE=WCS&COVERAGE=dhm_terraen&RESPONSE_CRS=epsg:25832&CRS=epsg:25832&FORMAT=GTiff&REQUEST=GetCoverage&VERSION=1.0.0'
213
+ url += `&username=${ auth.API_DHM_USERNAME }&password=${ auth.API_DHM_PASSWORD }` // TODO: Should be auth stuff
214
+ url += `&height=${ height }`
215
+ url += `&width=${ width }`
216
+ url += `&bbox=${ bbox[0]},${ bbox[1]},${ bbox[2]},${ bbox[3]}`
217
+
218
+ return get(url, {}, false)
219
+ .then((response) => {
220
+ return response.arrayBuffer()
221
+ })
222
+ .then((arrayBuffer) => {
223
+ return consumeGeoTIFF(arrayBuffer)
224
+ })
225
+ }
226
+
227
+
187
228
  export {
188
229
  get,
189
230
  post,
190
231
  getSTAC,
191
232
  postSTAC,
192
- getDHM
233
+ getDHM,
234
+ getTerrainGeoTIFF
193
235
  }
@@ -57,18 +57,22 @@ function image2world(image_data, col, row, Z = 0) {
57
57
  var X = (Z-Z0)*kx + X0
58
58
  var Y = (Z-Z0)*ky + Y0
59
59
 
60
- return[X,Y,Z]
60
+ return[
61
+ Math.round(X * 100) / 100,
62
+ Math.round(Y * 100) / 100,
63
+ Z
64
+ ]
61
65
  }
62
66
 
63
67
  /**
64
- * Converts lat,lon coordinates to x,y coordinates within a specific image
68
+ * Converts world lat,lon coordinates to x,y coordinates within a specific image
65
69
  * @param {Object} image_data - skraafoto-stac-api image data
66
70
  * @param {Number} Y - northing
67
71
  * @param {Number} X - easting
68
72
  * @param {Number} [Z] - elevation (geoide)
69
73
  * @returns {array} [x,y] Column/row image coordinate
70
74
  */
71
- function world2image(image_data, X, Y, Z = 0) {
75
+ function getImageXY(image_data, X, Y, Z = 0) {
72
76
 
73
77
  // constants pulled from image_data
74
78
  const xx0 = image_data.properties['pers:interior_orientation'].principal_point_offset[0]
@@ -109,7 +113,22 @@ function world2image(image_data, X, Y, Z = 0) {
109
113
  var col = ((x_dot-xx0)+(dimX))*(-1)/pix
110
114
  var row = ((y_dot-yy0)+(dimY))*(-1)/pix
111
115
 
112
- return [col, row]
116
+ return [
117
+ Math.round(col),
118
+ Math.round(row)
119
+ ]
120
+ }
121
+
122
+ /**
123
+ * (DEPRECATED - Use getImageXY) Converts lat,lon coordinates to x,y coordinates within a specific image
124
+ * @param {Object} image_data - skraafoto-stac-api image data
125
+ * @param {Number} Y - northing
126
+ * @param {Number} X - easting
127
+ * @param {Number} [Z] - elevation (geoide)
128
+ * @returns {array} [x,y] Column/row image coordinate
129
+ */
130
+ function world2image(image_data, X, Y, Z = 0) {
131
+ return getImageXY(image_data, X, Y, Z)
113
132
  }
114
133
 
115
134
  /** Converts degress to radians */
@@ -118,7 +137,7 @@ function radians(degrees) {
118
137
  }
119
138
 
120
139
  /**
121
- * (DEPRECATED - Use getElevation) Fetches elevation based on X,Y coordinates using DHM/Koter endpoint
140
+ * Fetches a single elevation value based on X,Y coordinates using DHM/Koter endpoint
122
141
  * @param {number} xcoor - EPSG:25832 X coordinate
123
142
  * @param {number} ycoor - EPSG:25832 Y coordinate
124
143
  * @param {{API_DHM_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
@@ -200,14 +219,14 @@ async function compareZ(options) {
200
219
  * @param {object} options.terrain - GeoTIFF object from getTerrainGeoTIFF() output
201
220
  * @returns {array} Array with world coordinates (indexes 0 and 1) and elevation (index 2). Coordinates are EPSG:25832 format.
202
221
  */
203
- async function getWorldXYZ(options) {
222
+ async function getWorldXYZ(options, precision = 0.3) {
204
223
 
205
224
  const best_world_xyz = compareZ({
206
225
  image: options.image,
207
226
  terrain: options.terrain,
208
227
  xy: options.xy,
209
228
  z: 0,
210
- limit: 0.5,
229
+ limit: precision,
211
230
  iteration: 1
212
231
  })
213
232
 
@@ -216,8 +235,9 @@ async function getWorldXYZ(options) {
216
235
 
217
236
  export {
218
237
  image2world,
219
- getWorldXYZ,
220
238
  world2image,
239
+ getWorldXYZ,
240
+ getImageXY,
221
241
  getZ,
222
242
  iterate
223
243
  }
@@ -1,41 +1,7 @@
1
- import { fromArrayBuffer } from 'geotiff'
2
- import { get } from './api.js'
1
+ import { getTerrainGeoTIFF } from './api.js'
3
2
 
4
- /** Fetches a GeoTIFF with elevation data matching the bounding box of a STAC API item (image)
5
- * @param {object} stac_item - STAC API item from a featureCollection request
6
- * @param {{API_DHM_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
7
- * @param {number} [fidelity] - Resolution fidelity. Higher number means more pixels and better precision. Between 1 and 0.01.
8
- * @returns {object} GeoTiff data
9
- */
10
- function getTerrainGeoTIFF(stac_item, auth, fidelity = 0.05) {
11
-
12
- const bbox = stac_item.bbox
13
- const width = Math.round( stac_item.properties['proj:shape'][0] * fidelity )
14
- const height = Math.round( stac_item.properties['proj:shape'][1] * fidelity )
15
-
16
- // DHM data endpoint
17
- let url = 'https://services.datafordeler.dk/DHMNedboer/dhm_wcs/1.0.0/WCS'
18
- url += '?SERVICE=WCS&COVERAGE=dhm_terraen&RESPONSE_CRS=epsg:25832&CRS=epsg:25832&FORMAT=GTiff&REQUEST=GetCoverage&VERSION=1.0.0'
19
- url += `&username=${ auth.API_DHM_USERNAME }&password=${ auth.API_DHM_PASSWORD }` // TODO: Should be auth stuff
20
- url += `&height=${ height }`
21
- url += `&width=${ width }`
22
- url += `&bbox=${ bbox[0]},${ bbox[1]},${ bbox[2]},${ bbox[3]}`
23
-
24
- return get(url, {}, false)
25
- .then((response) => {
26
- return response.arrayBuffer()
27
- })
28
- .then((arrayBuffer) => {
29
- return consumeGeoTIFF(arrayBuffer)
30
- })
31
- }
32
-
33
- async function consumeGeoTIFF(raw_data) {
34
- const tiff = await fromArrayBuffer(raw_data)
35
- const image = await tiff.getImage()
36
- return image
37
- }
38
3
 
4
+ /** Constrain coordinates to be within a bounding box */
39
5
  function constrainToBbox(bbox, x, y) {
40
6
  let new_x = x
41
7
  let new_y = y
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dataforsyningen/saul",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "This is a collection of stand-alone methods for photogrammetry calculations and API interactions for 'Danske Skråfotos'.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "test": "echo 'TESTS WILL ONLY RUN UNDER NODE v18' && node ./test/saul-projection.test.js && node ./test/saul-core.test.js && node ./test/saul-elevation.test.js",
8
+ "test": "echo 'TESTS WILL ONLY RUN UNDER NODE v18' && node ./test/saul-projection.test.js && node ./test/saul-core.test.js && node ./test/saul-combined.test.js && node ./test/saul-elevation.test.js",
9
9
  "docs": "jsdoc ./modules/*.js --destination docs"
10
10
  },
11
11
  "repository": {
@@ -0,0 +1,148 @@
1
+ // You should add your own config.js with proper tokens, passwords, etc.
2
+ // Check config.js.example for info on how to set it up
3
+ import auth from '../config.js'
4
+ import assert from 'assert'
5
+ import { getSTAC, getTerrainGeoTIFF,getZ, getWorldXYZ, getImageXY } from '../index.js'
6
+
7
+
8
+ // Vars
9
+ const resolution = 0.005 // Higher number means more points and better precision
10
+ const max_deviation = 0.5
11
+
12
+ /*
13
+ // North
14
+ let item_1 = {
15
+ id: '2021_83_29_2_0019_00003995',
16
+ xy: [1000,1000]
17
+ }
18
+ // East
19
+ let item_2 = {
20
+ id: '2021_83_29_4_0016_00002877'
21
+ }
22
+ // West
23
+ let item_3 = {
24
+ id: '2021_83_29_5_0022_00003276'
25
+ }
26
+ // South
27
+ let item_4 = {
28
+ id: '2021_83_29_3_0019_00003975'
29
+ }
30
+ // Nadir
31
+ let item_5 = {
32
+ id: '2021_83_29_1_0019_00003985'
33
+ }
34
+ */
35
+
36
+ /*
37
+ // North
38
+ let item_1 = {
39
+ id: '2021_83_33_2_0003_00002821',
40
+ xy: [8000,5000]
41
+ }
42
+ // East
43
+ let item_2 = {
44
+ id: '2021_83_33_4_0002_00000845'
45
+ }
46
+ // West
47
+ let item_3 = {
48
+ id: '2021_83_33_5_0005_00003192'
49
+ }
50
+ // South
51
+ let item_4 = {
52
+ id: '2021_83_33_3_0004_00002970'
53
+ }
54
+ // Nadir
55
+ let item_5 = {
56
+ id: '2021_83_33_1_0004_00002965'
57
+ }
58
+ */
59
+
60
+ // North
61
+ let item_1 = {
62
+ id: '2021_83_29_2_0019_00003995',
63
+ xy: [1000,1000]
64
+ }
65
+ // East
66
+ let item_2 = {
67
+ id: '2021_83_29_4_0016_00002877'
68
+ }
69
+ // West
70
+ let item_3 = {
71
+ id: '2021_83_29_5_0022_00003276'
72
+ }
73
+ // South
74
+ let item_4 = {
75
+ id: '2021_83_29_3_0019_00003975'
76
+ }
77
+ // Nadir
78
+ let item_5 = {
79
+ id: '2021_83_29_1_0019_00003985'
80
+ }
81
+
82
+ // Helper functions
83
+ function is_equalIsh(num1, num2, deviation = max_deviation) {
84
+ if (Math.abs(num1 - num2) > deviation) {
85
+ return false
86
+ } else {
87
+ return true
88
+ }
89
+ }
90
+ function generateSTACurl(stac_item_id) {
91
+ return `/search?limit=1&crs=http://www.opengis.net/def/crs/EPSG/0/25832&ids=${ stac_item_id }`
92
+ }
93
+ async function enrichData(item) {
94
+ let new_item = Object.assign({}, item)
95
+ // add STAC item
96
+ new_item.item = await getSTAC(generateSTACurl(new_item.id), auth)
97
+ new_item.item = new_item.item.features[0]
98
+ // add terrain GeoTIFF
99
+ new_item.terrain = await getTerrainGeoTIFF(new_item.item, auth, resolution)
100
+ // Add imageXY
101
+ new_item.xy = getImageXY(new_item.item, item_1.xyz[0], item_1.xyz[1], item_1.xyz[2])
102
+ // Add world XYZ (from imageYX)
103
+ new_item.xyz = await getWorldXYZ({
104
+ xy: new_item.xy,
105
+ image: new_item.item,
106
+ terrain: new_item.terrain
107
+ })
108
+ // add kote
109
+ new_item.kote = await getZ(new_item.xyz[0], new_item.xyz[1], auth)
110
+ // add resolution info
111
+ new_item.ext_x = Math.round((new_item.item.bbox[2] - new_item.item.bbox[0]) * 100) / 100
112
+ new_item.ext_y = Math.round((new_item.item.bbox[3] - new_item.item.bbox[1]) * 100) / 100
113
+ new_item.z_pr_m_x = Math.round((new_item.item.properties['proj:shape'][0] / new_item.ext_x) * 100 * resolution) / 100
114
+ new_item.z_pr_m_y = Math.round((new_item.item.properties['proj:shape'][1] / new_item.ext_y) * 100 * resolution) / 100
115
+ const width = Math.round( new_item.item.properties['proj:shape'][0] * resolution )
116
+ const height = Math.round( new_item.item.properties['proj:shape'][1] * resolution )
117
+ new_item.z_points = width * height
118
+ return new_item
119
+ }
120
+
121
+ // Fetch image data and terrain
122
+ item_1.item = await getSTAC(generateSTACurl(item_1.id), auth)
123
+ item_1.item = item_1.item.features[0]
124
+ item_1.terrain = await getTerrainGeoTIFF(item_1.item, auth, resolution)
125
+
126
+ // Given image XY for one image, get world XYZ
127
+ item_1.xyz = await getWorldXYZ({
128
+ xy: item_1.xy,
129
+ image: item_1.item,
130
+ terrain: item_1.terrain
131
+ })
132
+
133
+ item_1.kote = await getZ(item_1.xyz[0], item_1.xyz[1], auth)
134
+
135
+ // Get image and corresponding world coordinates for other images (amongst other things)
136
+ item_2 = await enrichData(item_2)
137
+ item_3 = await enrichData(item_3)
138
+ item_4 = await enrichData(item_4)
139
+ item_5 = await enrichData(item_5)
140
+
141
+ // Compare items
142
+ console.table({
143
+ north: item_1,
144
+ east: item_2,
145
+ west: item_3,
146
+ south: item_4,
147
+ nadir: item_5
148
+ })
@@ -5,8 +5,7 @@
5
5
  import auth from '../config.js'
6
6
 
7
7
  import assert from 'assert'
8
- import {getSTAC} from '../modules/api.js'
9
- import {world2image, image2world, getZ, iterate} from '../modules/saul-core.js'
8
+ import {getSTAC, world2image, image2world, getZ, iterate} from '../index.js'
10
9
 
11
10
  /*
12
11
  const item = await getSTAC('/collections/skraafotos2019/items/2019_83_37_2_0046_00001113', auth)
@@ -24,8 +23,7 @@ const image_x = 41839.698939
24
23
  const image_y = 3204.326930
25
24
  const world_elevation = 38.874336
26
25
 
27
- function is_equalIsh(num1, num2) {
28
- const deviation = 0.05
26
+ function is_equalIsh(num1, num2, deviation = 0.05) {
29
27
  if (Math.abs(num1 - num2) > deviation) {
30
28
  return false
31
29
  } else {
@@ -65,7 +63,7 @@ try {
65
63
 
66
64
  let xy = world2image(item, world_x, world_y, world_elevation)
67
65
 
68
- assert(is_equalIsh(xy[0], image_x), "world2image fail: The x coordinate values are not equal")
66
+ assert(is_equalIsh(xy[0], image_x, 1), `world2image fail: The x coordinate values ${xy[0]} ${image_x} are not equal`)
69
67
  assert(is_equalIsh(xy[1], image_y), "world2image fail: The y coordinate values are not equal")
70
68
 
71
69
  console.log("Test world2image OK")
@@ -3,17 +3,15 @@
3
3
  import auth from '../config.js'
4
4
 
5
5
  import assert from 'assert'
6
- import { getTerrainGeoTIFF, getElevation } from '../modules/saul-elevation.js'
7
- import { get } from '../modules/api.js'
8
- import { getZ, getWorldXYZ, world2image } from '../modules/saul-core.js'
6
+ import { getElevation, get, getTerrainGeoTIFF, getZ, getWorldXYZ, world2image } from '../index.js'
9
7
 
10
8
  // Vars
11
9
  const stac_item = '2021_83_29_2_0019_00003995'
12
- const fidelity = 0.05 // Higher number means more points and better precision
10
+ const fidelity = 0.03 // Higher number means more points and better precision
13
11
  const max_deviation = 0.5
14
12
 
15
13
  // STAC API endpoint
16
- let url_stac = 'https://api.dataforsyningen.dk/skraafotoapi_test/search?limit=1&crs=http://www.opengis.net/def/crs/EPSG/0/25832&token=9b554b6c854184c3b0f377ffc7481585'
14
+ let url_stac = auth.API_STAC_BASEURL + '/search?limit=1&crs=http://www.opengis.net/def/crs/EPSG/0/25832&token=9b554b6c854184c3b0f377ffc7481585'
17
15
  url_stac += `&ids=${ stac_item }`
18
16
 
19
17
  function is_equalIsh(num1, num2, deviation = max_deviation) {
@@ -53,10 +51,10 @@ function testGetWorldXYZAnumberOfTimes(item, terrain, times) {
53
51
  image: item,
54
52
  terrain: terrain
55
53
  }).then(world_xy => {
56
- compareElevations(world_xy[0], world_xy[1], terrain) // lower left corner of image
54
+ compareElevations(world_xy[0], world_xy[1], terrain)
57
55
  const image_coords = world2image(item, world_xy[0], world_xy[1], world_xy[2])
58
- assert(is_equalIsh(image_coords[0], xy[0], 0.75), `Image x coordinates ${image_coords[0]} / ${xy[0]} do not match`)
59
- assert(is_equalIsh(image_coords[1], xy[1], 0.75), `Image y coordinates ${image_coords[1]} / ${xy[1]} do not match`)
56
+ assert(is_equalIsh(image_coords[0], xy[0], 2), `Image x coordinates ${image_coords[0]} / ${xy[0]} do not match`)
57
+ assert(is_equalIsh(image_coords[1], xy[1], 2), `Image y coordinates ${image_coords[1]} / ${xy[1]} do not match`)
60
58
  console.log('getWorldXYZ => world2image OK')
61
59
  })
62
60
  }
@@ -67,7 +65,7 @@ function compareElevations(x,y,geotiff) {
67
65
  .then(elevation => {
68
66
  getZ(x, y, auth)
69
67
  .then(getz_e => {
70
- assert(is_equalIsh(getz_e, elevation), `Elevations ${elevation} / ${getz_e} at ${x} ${y} do not match`)
68
+ assert(is_equalIsh(getz_e, elevation, 0.5), `Elevations ${elevation} / ${getz_e} at ${x} ${y} do not match`)
71
69
  console.log(`Elevation at ${ x } ${ y } with delta ${Math.abs(elevation - getz_e).toFixed(2)} OK`)
72
70
  })
73
71
  })
@@ -2,7 +2,7 @@
2
2
 
3
3
  import assert from 'assert'
4
4
  import proj4 from 'proj4'
5
- import {epsg25832proj, createTranslator} from '../modules/saul-projection.js'
5
+ import {epsg25832proj, createTranslator} from '../index.js'
6
6
 
7
7
  // Test epsg25832proj
8
8
  try {