@dataforsyningen/saul 3.1.0 → 3.1.2

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.
@@ -5,8 +5,6 @@
5
5
  import { getDHM } from './api.js'
6
6
  import { getElevation } from './saul-elevation.js'
7
7
 
8
- let iterations
9
-
10
8
  /**
11
9
  * Converts x,y coordinates from an image to real world lat,lon coordinates
12
10
  * @param {object} image_data - skraafoto-stac-api image data
@@ -175,93 +173,45 @@ function iterate(image_data, col, row, auth, limit = 1) {
175
173
  return iterateRecursive(image_data, col, row, 0.5, 0, limit, auth, i)
176
174
  }
177
175
 
178
- /** Find world coordinate and elevation that correspond to image x,y. Uses image item information from STAC API and a GeoTIFF terrain model from Datafordeler.
179
- * @param {object} options
180
- * @param {array} options.xy - image coordinates
181
- * @param {object} options.image - STAC API item
182
- * @param {object} options.terrain - GeoTIFF object from getTerrainGeoTIFF() output
183
- * @returns {array} Array with world coordinates (indexes 0 and 1) and elevation (index 2). Coordinates are EPSG:25832 format.
184
- */
185
- async function getWorldXYZ(options) {
186
-
187
- iterations = 1
188
-
189
- const world_xyz = image2world(options.image, options.xy[0], options.xy[1], 0)
190
- const better_world_xyz = await compareXYZ({
191
- xy: options.xy,
192
- image: options.image,
193
- terrain: options.terrain,
194
- world_xyz: Array.from(world_xyz),
195
- limit: 0.5
196
- })
197
- return better_world_xyz
198
- }
199
-
200
- /**
201
- * Adjusts elevation value if values are too far apart
202
- * @param {number} z - elevation
203
- * @param {number} delta_y - discrepancy value
204
- * @returns {number} Adjusted elevation number
205
- */
206
- function adjustZ(z, delta_y) {
207
- let new_z
208
- if (delta_y < 0) {
209
- new_z = z + 0.1
210
- } else {
211
- new_z = z - 0.1
212
- }
213
- console.log('z', new_z)
214
- return new_z
215
- }
176
+ async function compareZ(options) {
216
177
 
217
- /**
218
- * Compares differences i x and y values
219
- * @param {number} delta_x - x difference
220
- * @param {number} delta_y - y difference
221
- * @param {number} limit - difference may be within this limit
222
- * @returns {boolean} Returns true if differences are within accepted limit
223
- */
224
- function compareXY(delta_x, delta_y, limit) {
225
- if (Math.abs(delta_x) > limit || Math.abs(delta_y) > limit) {
226
- return false
178
+ // Get coordinates based on current z value
179
+ const world_xyz = image2world(options.image, options.xy[0], options.xy[1], options.z)
180
+ // Get new z value from coordinates
181
+ world_xyz[2] = await getElevation(world_xyz[0], world_xyz[1], options.terrain)
182
+ // How big is the different between z and new z?
183
+ const delta = Math.abs(world_xyz[2] - options.z)
184
+
185
+ // Check delta vs. limit. If the difference is too big, try building coordinates with the new z
186
+ // Make sure the recursive loop ends at some point (iteration = 50) even if no precise z is found
187
+ if (options.iteration < 50 && delta > options.limit) {
188
+ options.iteration++
189
+ options.z = world_xyz[2]
190
+ return compareZ(options)
227
191
  } else {
228
- return true
192
+ return world_xyz
229
193
  }
230
194
  }
231
195
 
232
- /**
233
- * Iterates world coordinate calculations in order to get a more precise result
234
- * @param {object} options
196
+ /** Find world coordinate and elevation that correspond to image x,y. Uses image item information from STAC API and a GeoTIFF terrain model from Datafordeler.
197
+ * @param {object} options
235
198
  * @param {array} options.xy - image coordinates
236
199
  * @param {object} options.image - STAC API item
237
200
  * @param {object} options.terrain - GeoTIFF object from getTerrainGeoTIFF() output
238
- * @param {array} options.world_xy - Array with (currently guessed) world coordinates (indexes 0 and 1) and elevation (index 2). Coordinates are EPSG:25832 format.
239
- * @param {number} options.limit - Return when two guesses are within this limit
240
201
  * @returns {array} Array with world coordinates (indexes 0 and 1) and elevation (index 2). Coordinates are EPSG:25832 format.
241
202
  */
242
- async function compareXYZ(options) {
243
-
244
- iterations++
245
- if (iterations > 50) {
246
- console.error('too many iterations')
247
- return false
248
- }
249
-
250
- const z = await getElevation(options.xy[0], options.xy[1], options.terrain)
251
- const world_xyz = image2world(options.image, options.xy[0], options.xy[1], z)
252
- const delta_x = world_xyz[0] - options.world_xyz[0]
253
- const delta_y = world_xyz[1] - options.world_xyz[1]
203
+ async function getWorldXYZ(options) {
254
204
 
255
- if (!compareXY(delta_x, delta_y, options.limit)) {
256
-
257
- const new_options = Object.assign({},options)
258
- new_options.world_xyz = Array.from(world_xyz)
259
- new_options.world_xyz[2] = adjustZ(world_xyz[2], delta_x, delta_y)
260
- return await compareXYZ(new_options)
205
+ const best_world_xyz = compareZ({
206
+ image: options.image,
207
+ terrain: options.terrain,
208
+ xy: options.xy,
209
+ z: 0,
210
+ limit: 0.5,
211
+ iteration: 1
212
+ })
261
213
 
262
- } else {
263
- return world_xyz
264
- }
214
+ return best_world_xyz
265
215
  }
266
216
 
267
217
  export {
@@ -36,6 +36,22 @@ async function consumeGeoTIFF(raw_data) {
36
36
  return image
37
37
  }
38
38
 
39
+ function constrainToBbox(bbox, x, y) {
40
+ let new_x = x
41
+ let new_y = y
42
+ if (x < bbox[0]) {
43
+ new_x = bbox[0] + 1
44
+ } else if (bbox[2] < x) {
45
+ new_x = bbox[2] - 1
46
+ }
47
+ if (y < bbox[1]) {
48
+ new_y = bbox[1] + 1
49
+ } else if (bbox[3] < y) {
50
+ new_y = bbox[3] - 1
51
+ }
52
+ return [new_x, new_y]
53
+ }
54
+
39
55
  /** Calculates nearest elevation for given coordinate using geoTIFF terrain data
40
56
  * Heavily inspired by https://towardsdatascience.com/geotiff-coordinate-querying-with-javascript-5e6caaaf88cf
41
57
  * @param {number} x - EPSG:25832 X coordinate
@@ -51,8 +67,10 @@ async function getElevation(x, y, geoTiff) {
51
67
  const bboxWidth = bbox[2] - bbox[0]
52
68
  const bboxHeight = bbox[3] - bbox[1]
53
69
 
54
- const widthPct = ( x - bbox[0] ) / bboxWidth
55
- const heightPct = ( y - bbox[1] ) / bboxHeight
70
+ const xy = constrainToBbox(bbox, x, y)
71
+
72
+ const widthPct = ( xy[0] - bbox[0] ) / bboxWidth
73
+ const heightPct = ( xy[1] - bbox[1] ) / bboxHeight
56
74
  const xPx = Math.floor( width * widthPct )
57
75
  const yPx = Math.floor( height * ( 1 - heightPct ) )
58
76
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataforsyningen/saul",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
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",
@@ -5,7 +5,7 @@ import auth from '../config.js'
5
5
  import assert from 'assert'
6
6
  import { getTerrainGeoTIFF, getElevation } from '../modules/saul-elevation.js'
7
7
  import { get } from '../modules/api.js'
8
- import { getZ, getWorldXYZ } from '../modules/saul-core.js'
8
+ import { getZ, getWorldXYZ, world2image } from '../modules/saul-core.js'
9
9
 
10
10
  // Vars
11
11
  const stac_item = '2021_83_29_2_0019_00003995'
@@ -16,8 +16,8 @@ const max_deviation = 0.5
16
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'
17
17
  url_stac += `&ids=${ stac_item }`
18
18
 
19
- function is_equalIsh(num1, num2) {
20
- if (Math.abs(num1 - num2) > max_deviation) {
19
+ function is_equalIsh(num1, num2, deviation = max_deviation) {
20
+ if (Math.abs(num1 - num2) > deviation) {
21
21
  return false
22
22
  } else {
23
23
  return true
@@ -30,13 +30,38 @@ function getRandomCoordinate(bbox) {
30
30
  return [x,y]
31
31
  }
32
32
 
33
- function testDataAnumberOfTimes(data, bbox, times) {
33
+ function testGetElevationAnumberOfTimes(data, bbox, times) {
34
34
  for (let i = 1; i <= times; i++) {
35
35
  const world1 = getRandomCoordinate(bbox)
36
36
  compareElevations(world1[0], world1[1], data)
37
37
  }
38
38
  }
39
39
 
40
+ function getRandomImageXY(image) {
41
+ const shapex = image.properties['proj:shape'][0]
42
+ const shapey = image.properties['proj:shape'][1]
43
+ const x = Math.floor(shapex - Math.random() * shapex)
44
+ const y = Math.floor(shapey - Math.random() * shapey)
45
+ return [x,y]
46
+ }
47
+
48
+ function testGetWorldXYZAnumberOfTimes(item, terrain, times) {
49
+ for (let i = 1; i <= times; i++) {
50
+ const xy = getRandomImageXY(item)
51
+ getWorldXYZ({
52
+ xy: xy,
53
+ image: item,
54
+ terrain: terrain
55
+ }).then(world_xy => {
56
+ compareElevations(world_xy[0], world_xy[1], terrain) // lower left corner of image
57
+ 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`)
60
+ console.log('getWorldXYZ => world2image OK')
61
+ })
62
+ }
63
+ }
64
+
40
65
  function compareElevations(x,y,geotiff) {
41
66
  getElevation(x, y, geotiff)
42
67
  .then(elevation => {
@@ -52,8 +77,6 @@ function compareElevations(x,y,geotiff) {
52
77
  get(url_stac)
53
78
  .then((json) => {
54
79
 
55
- console.log('Testing getTerrainGeoTIFF and getElevation')
56
-
57
80
  const width = Math.round( json.features[0].properties['proj:shape'][0] * fidelity )
58
81
  const height = Math.round( json.features[0].properties['proj:shape'][1] * fidelity )
59
82
 
@@ -62,15 +85,19 @@ get(url_stac)
62
85
  getTerrainGeoTIFF(json.features[0], auth, fidelity)
63
86
  .then(data => {
64
87
 
65
- testDataAnumberOfTimes(data, json.features[0].bbox, 5)
66
-
67
- getWorldXYZ({
68
- xy: [1,1],
69
- image: json.features[0],
70
- terrain: data
71
- }).then(world_xy => {
72
- compareElevations(world_xy[0], world_xy[1], data) // lower left corner of image
73
- })
88
+ // Testing getElevation
89
+ try {
90
+ testGetElevationAnumberOfTimes(data, json.features[0].bbox, 5)
91
+ } catch(error) {
92
+ console.error(error)
93
+ }
94
+
95
+ // Testing getWorldXYZ
96
+ try {
97
+ testGetWorldXYZAnumberOfTimes(json.features[0], data, 10)
98
+ } catch(error) {
99
+ console.error(error)
100
+ }
74
101
 
75
102
  })
76
103