@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.
- package/modules/saul-core.js +27 -77
- package/modules/saul-elevation.js +20 -2
- package/package.json +1 -1
- package/test/saul-elevation.test.js +42 -15
package/modules/saul-core.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
192
|
+
return world_xyz
|
|
229
193
|
}
|
|
230
194
|
}
|
|
231
195
|
|
|
232
|
-
/**
|
|
233
|
-
*
|
|
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
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
|
|
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
|
@@ -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) >
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|