@dataforsyningen/saul 3.0.1

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.
@@ -0,0 +1,31 @@
1
+ name: Node.js Package
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 16
15
+ - run: npm ci
16
+ # - run: npm test
17
+
18
+ # Important note:
19
+ # This only works in github actions when you've added an "automation token" by the name of "NPM_TOKEN" to "repository secrets"
20
+ publish-npm:
21
+ needs: build
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v3
25
+ - uses: actions/setup-node@v3
26
+ with:
27
+ node-version: 16
28
+ registry-url: https://registry.npmjs.org
29
+ - run: npm publish --access public
30
+ env:
31
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 SDFI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # SAUL
2
+
3
+ This is a collection of stand-alone methods for photogrammetry calculations and API interactions that are primarily used by https://github.com/SDFIdk/skraafoto_frontend
4
+
5
+ You can use these as building blocks for your own web application that wants to interface with STAC API for Danske Skråfotos.
6
+
7
+ ## Installation
8
+
9
+ 1. Install iva NPM:
10
+ ```
11
+ npm i --save @dataforsyningen/saul
12
+ ```
13
+
14
+ 2. Create a configuration file or Javascript object with proper values and make sure to load it into your application before using any of the SAUL utilities. This package ships with an example configuration file at `/example/config.js.example`
15
+
16
+ ## How to use
17
+
18
+ This is an example of how to import and use those utilities in Javascript:
19
+ ```
20
+ import auth from './config.js' // This is the path to your configuration file
21
+ import {getSTAC} from '@dataforsyningen/saul'
22
+
23
+ let response = await getSTAC('/some-stac-endpoint', auth)
24
+ console.log(response)
25
+ ```
26
+
27
+ ## API docs
28
+
29
+ To read the docs, first build the documentation with JSDoc:
30
+ ```
31
+ npm run docs
32
+ ```
33
+ A `docs` directory will be created with a bunch of HTML files containing the documentation.
34
+
35
+ ## How to test
36
+
37
+ From within `node_modules/@dataforsyningen/saul` you can run the `test` command to check that saul core features are still working.
38
+ ```
39
+ npm run test
40
+ ```
41
+
42
+ ## Acknowledgements
43
+
44
+ SAUL is made available under the MIT license by
45
+ The Skråfoto Team @ [SDFI](https://sdfi.dk/)
@@ -0,0 +1,11 @@
1
+ export default {
2
+
3
+ // STAC TOKEN can be aquired from https://dataforsyningen.dk/
4
+ API_STAC_TOKEN: "",
5
+ API_STAC_BASEURL: "https://api.dataforsyningen.dk/skraafotoapi",
6
+
7
+ // DHM URL and auth can be aquired from https://datafordeler.dk/dataoversigt/danmarks-hoejdemodel-dhm/koter/
8
+ API_DHM_BASEURL: "https://services.datafordeler.dk/DHMTerraen/DHMKoter/1.0.0/GEOREST/HentKoter",
9
+ API_DHM_USERNAME: "",
10
+ API_DHM_PASSWORD: ""
11
+ }
package/example.html ADDED
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width">
6
+ <title>Saul modules example page</title>
7
+ </head>
8
+ <body>
9
+
10
+ <h1>Saul modules example</h1>
11
+
12
+ <p>Just opening this example.html file might not work becuase of CORS restrictions when trying to access STAC API.</p>
13
+ <p>You should run this HTML file from a simple server like Python's <code>http.server</code></p>
14
+
15
+ <h2>This is the result</h2>
16
+ <div id="result"></div>
17
+
18
+ <script type="module">
19
+
20
+ // You should add your own config.js with proper tokens, passwords, etc.
21
+ // Check config.js.example for info on how to set it up
22
+ import auth from './config.js'
23
+
24
+ import {image2world, world2image, getZ, iterate} from './modules/saul-core.js'
25
+ import {getSTAC} from './modules/api.js'
26
+
27
+ let xcoor = 579782.75
28
+ let ycoor = 6131096.03
29
+
30
+ const result_display_element = document.getElementById('result')
31
+ let apidata = await getSTAC('/collections/skraafotos2019/items/2019_83_37_2_0046_00001113', auth)
32
+
33
+ // Let's call an imported method
34
+ let zcoor = await getZ(xcoor, ycoor, auth)
35
+
36
+ let image_coor = world2image(apidata, xcoor, ycoor, zcoor)
37
+
38
+ let col = image_coor[0]
39
+ let row = image_coor[1]
40
+
41
+ let world_coor = image2world(apidata, col, row, zcoor)
42
+
43
+ col = 7375
44
+ row = 5382
45
+ let ite = await iterate(apidata, col, row, auth, 0.05)
46
+
47
+ // Write out result for the user to see
48
+ if (image_coor) {
49
+ result.innerText = xcoor + ' ' + ycoor + ' ' + zcoor + '\n' + image_coor[0] + ' ' + image_coor[1] + '\n' +ite
50
+ } else {
51
+ result.innerText = 'There was no result'
52
+ }
53
+
54
+
55
+ </script>
56
+
57
+ </body>
58
+ </html>
package/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // Import/export modules with their public methods
2
+
3
+ export {
4
+ image2world,
5
+ world2image,
6
+ getZ,
7
+ iterate
8
+ } from './modules/saul-core.js'
9
+
10
+ export {
11
+ postSTAC,
12
+ getSTAC,
13
+ getDHM,
14
+ get,
15
+ post
16
+ } from './modules/api.js'
17
+
18
+ export {
19
+ epsg25832proj,
20
+ createTranslator
21
+ } from './modules/saul-projection.js'
package/modules/api.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * SAUL API utilities
3
+ */
4
+
5
+ let error_msg
6
+ let load_stack = []
7
+
8
+ // These custom events can only be created in a browser environment
9
+ const loadstart = isBrowser() ? new CustomEvent('loadstart') : null
10
+ const loadend = isBrowser() ? new CustomEvent('loadend') : null
11
+ const loaderror = isBrowser() ? new CustomEvent('loaderror', {
12
+ detail: {
13
+ name: getErrorMsg()
14
+ }
15
+ }) : null
16
+
17
+
18
+ /** Check if code is run in a Browser or Node enviroment */
19
+ function isBrowser() {
20
+ // Check if the environment is Node.js
21
+ if (typeof process === "object" &&
22
+ typeof require === "function") {
23
+ return false
24
+ }
25
+ // Check if the environment is
26
+ // a Service worker
27
+ if (typeof importScripts === "function") {
28
+ return false
29
+ }
30
+ // Check if the environment is a Browser
31
+ if (typeof window === "object") {
32
+ return true
33
+ }
34
+ }
35
+
36
+ /** Sends 'loadstart' event if nothing else is currently loading */
37
+ function startLoading() {
38
+ if (isBrowser() && load_stack.length < 1) {
39
+ document.dispatchEvent(loadstart)
40
+ }
41
+ load_stack.push(1)
42
+ }
43
+
44
+ /** Sends 'loadend' event if current loads have finished */
45
+ function endLoading() {
46
+ load_stack.pop()
47
+ if (isBrowser() && load_stack.length < 1) {
48
+ document.dispatchEvent(loadend)
49
+ }
50
+ }
51
+
52
+ /** Sends 'loaderror' event and resets load status */
53
+ function interruptLoading() {
54
+ load_stack = []
55
+ if (isBrowser()) {
56
+ document.dispatchEvent(loaderror)
57
+ }
58
+ }
59
+
60
+ /** Getter for error messages */
61
+ function getErrorMsg() {
62
+ return error_msg
63
+ }
64
+
65
+ /** Returns JSON response data */
66
+ function HttpResponseHandler(response) {
67
+ if (!response.ok) {
68
+ error_msg = response.status
69
+ interruptLoading()
70
+ throw new Error(`HTTP error! Status: ${ response.status }`)
71
+ }
72
+ // We assume the returned data is JSON
73
+ return response.json()
74
+ }
75
+
76
+ /**
77
+ * GET HTTP responsee from API
78
+ * @param {string} url - API service URL, including endpoint paths and query parameters.
79
+ * @param {object} [config] - Custom request configs. See https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#supplying_request_options
80
+ * @returns {object} response object
81
+ */
82
+ function get(url, config = {}) {
83
+ if (!url) {
84
+ console.error('Could not fetch data. Missing API URL')
85
+ } else {
86
+ startLoading()
87
+ return fetch( url, {
88
+ ...config,
89
+ method: 'GET'
90
+ })
91
+ .then((response) => {
92
+ return HttpResponseHandler(response)
93
+ })
94
+ .then((response) => {
95
+ // Finally, return the parsed JSON response
96
+ endLoading()
97
+ return response
98
+ })
99
+ .catch((error) => {
100
+ // ... unless something goes wrong
101
+ console.error(`Fetch error: ${error}`)
102
+ endLoading()
103
+ return error
104
+ })
105
+ }
106
+ }
107
+
108
+ /**
109
+ * POST HTTP request to API
110
+ * @param {string} url - API service URL, including endpoint paths and query parameters.
111
+ * @param {object} requestbody - Request data
112
+ * @param {string} token - Authentication token from Dataforsyningen
113
+ * @returns {object} response object
114
+ */
115
+ function post(url, requestbody, token) {
116
+ if (!url || !token || !requestbody) {
117
+ console.error('Could not fetch data. Missing API token, request body, or URL')
118
+ } else {
119
+ startLoading()
120
+ return fetch( url, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'token': token,
124
+ 'Content-Type': 'application/json'
125
+ },
126
+ body: JSON.stringify(requestbody)
127
+ })
128
+ .then((response) => {
129
+ return HttpResponseHandler(response)
130
+ })
131
+ .then((response) => {
132
+ // Finally, return the parsed JSON response
133
+ endLoading()
134
+ return response
135
+ })
136
+ .catch((error) => {
137
+ // ... unless something goes wrong
138
+ console.error(`Fetch error: ${error}`)
139
+ endLoading()
140
+ return error
141
+ })
142
+ }
143
+ }
144
+
145
+ /**
146
+ * API method to fetch data from DHM
147
+ * @param {string} query - DHM API query. Find details at https://datafordeler.dk/dataoversigt/danmarks-hoejdemodel-dhm/koter/
148
+ * @param {{API_DHM_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
149
+ * @returns {object} Response data
150
+ */
151
+ function getDHM(query, auth) {
152
+ const auth_params = `&username=${auth.API_DHM_USERNAME}&password=${auth.API_DHM_PASSWORD}`
153
+ return get(encodeURI(auth.API_DHM_BASEURL + query + auth_params))
154
+ .then((data) => data)
155
+ }
156
+
157
+ /**
158
+ * API method to GET data from STAC API
159
+ * @param {string} query - STAC API query.
160
+ * @param {{API_STAC_BASEURL: string, API_STAC_TOKEN: string}} auth - API autentication data. See ../config.js.example for reference.
161
+ * @returns {object} Response data
162
+ */
163
+ function getSTAC(query, auth) {
164
+ return get(auth.API_STAC_BASEURL + query, {headers: {'token': auth.API_STAC_TOKEN}})
165
+ .then((data) => data)
166
+ }
167
+
168
+ /**
169
+ * API method to POST data to STAC API
170
+ * @param {string} endpoint - STAC API endpoint. Ex. `/collections`
171
+ * @param {object} data - request body
172
+ * @param {{API_STAC_BASEURL: string, API_STAC_TOKEN: string}} auth - API autentication data. See ../config.js.example for reference.
173
+ * @returns {object} Reponse data
174
+ */
175
+ function postSTAC(endpoint, data, auth) {
176
+ return post(auth.API_STAC_BASEURL + endpoint, data, auth.API_STAC_TOKEN)
177
+ .then((data) => data)
178
+ }
179
+
180
+ export {
181
+ get,
182
+ post,
183
+ getSTAC,
184
+ postSTAC,
185
+ getDHM
186
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * SAUL photogrammetry utilities
3
+ */
4
+
5
+ import { getDHM } from './api.js'
6
+
7
+ /**
8
+ * Converts x,y coordinates from an image to real world lat,lon coordinates
9
+ * @param {object} image_data - skraafoto-stac-api image data
10
+ * @param {number} col - Image x coordinate (from left to right). Should be a coordinate for the entire image, not just the part displayed in the viewport.
11
+ * @param {number} row - Image y coordinate (from top to bottom). Should be a coordinate for the entire image, not just the part displayed in the viewport.
12
+ * @param {number} [Z] - Elevation (geoide)
13
+ * @returns {array} [longitude, latitude, elevation]
14
+ */
15
+ function image2world(image_data, col, row, Z = 0) {
16
+
17
+ // constants pulled from image_data
18
+ const xx0 = image_data.properties['pers:interior_orientation'].principal_point_offset[0]
19
+ const yy0 = image_data.properties['pers:interior_orientation'].principal_point_offset[1]
20
+ const ci = image_data.properties['pers:interior_orientation'].focal_length
21
+ const pix = image_data.properties['pers:interior_orientation'].pixel_spacing[0]
22
+ const dimXi = image_data.properties['pers:interior_orientation'].sensor_array_dimensions[0]
23
+ const dimYi = image_data.properties['pers:interior_orientation'].sensor_array_dimensions[1]
24
+ const X0 = image_data.properties['pers:perspective_center'][0]
25
+ const Y0 = image_data.properties['pers:perspective_center'][1]
26
+ const Z0 = image_data.properties['pers:perspective_center'][2]
27
+ const Ome = image_data.properties['pers:omega']
28
+ const Phi = image_data.properties['pers:phi']
29
+ const Kap = image_data.properties['pers:kappa']
30
+
31
+ // recalc values
32
+ var c = ci*(-1)
33
+ var dimX = dimXi*pix/2*(-1)
34
+ var dimY = dimYi*pix/2*(-1)
35
+
36
+ // ... Do calculations ...
37
+ var o = radians(Ome)
38
+ var p = radians(Phi)
39
+ var k = radians(Kap)
40
+ var D11 = Math.cos(p) * Math.cos(k)
41
+ var D12 = - Math.cos(p) * Math.sin(k)
42
+ var D13 = Math.sin(p)
43
+ var D21 = Math.cos(o) * Math.sin(k) + Math.sin(o) * Math.sin(p) * Math.cos(k)
44
+ var D22 = Math.cos(o) * Math.cos(k) - Math.sin(o) * Math.sin(p) * Math.sin(k)
45
+ var D23 = - Math.sin(o) * Math.cos(p)
46
+ var D31 = Math.sin(o) * Math.sin(k) - Math.cos(o) * Math.sin(p) * Math.cos(k)
47
+ var D32 = Math.sin(o) * Math.cos(k) + Math.cos(o) * Math.sin(p) * Math.sin(k)
48
+ var D33 = Math.cos(o) * Math.cos(p)
49
+
50
+ var x_dot = ((col*pix)-dimX*-1)-xx0
51
+ var y_dot = ((row*pix)-dimY*-1)-yy0
52
+
53
+ var kx = (D11*x_dot + D12*y_dot + D13*c)/(D31*x_dot + D32*y_dot + D33*c)
54
+ var ky = (D21*x_dot + D22*y_dot + D23*c)/(D31*x_dot + D32*y_dot + D33*c)
55
+
56
+ var X = (Z-Z0)*kx + X0
57
+ var Y = (Z-Z0)*ky + Y0
58
+
59
+ return[X,Y,Z]
60
+ }
61
+
62
+ /**
63
+ * Converts lat,lon coordinates to x,y coordinates within a specific image
64
+ * @param {Object} image_data - skraafoto-stac-api image data
65
+ * @param {Number} Y - northing
66
+ * @param {Number} X - easting
67
+ * @param {Number} [Z] - elevation (geoide)
68
+ * @returns {array} [x,y] Column/row image coordinate
69
+ */
70
+ function world2image(image_data, X, Y, Z = 0) {
71
+
72
+ // constants pulled from image_data
73
+ const xx0 = image_data.properties['pers:interior_orientation'].principal_point_offset[0]
74
+ const yy0 = image_data.properties['pers:interior_orientation'].principal_point_offset[1]
75
+ const ci = image_data.properties['pers:interior_orientation'].focal_length
76
+ const pix = image_data.properties['pers:interior_orientation'].pixel_spacing[0]
77
+ const dimXi = image_data.properties['pers:interior_orientation'].sensor_array_dimensions[0]
78
+ const dimYi = image_data.properties['pers:interior_orientation'].sensor_array_dimensions[1]
79
+ const X0 = image_data.properties['pers:perspective_center'][0]
80
+ const Y0 = image_data.properties['pers:perspective_center'][1]
81
+ const Z0 = image_data.properties['pers:perspective_center'][2]
82
+ const Ome = image_data.properties['pers:omega']
83
+ const Phi = image_data.properties['pers:phi']
84
+ const Kap = image_data.properties['pers:kappa']
85
+
86
+ // recalc values
87
+ var c = ci*(-1)
88
+ var dimX = dimXi*pix/2*(-1)
89
+ var dimY = dimYi*pix/2*(-1)
90
+
91
+ // ... Do calculations ...
92
+ var o = radians(Ome)
93
+ var p = radians(Phi)
94
+ var k = radians(Kap)
95
+ var D11 = Math.cos(p) * Math.cos(k)
96
+ var D12 = - Math.cos(p) * Math.sin(k)
97
+ var D13 = Math.sin(p)
98
+ var D21 = Math.cos(o) * Math.sin(k) + Math.sin(o) * Math.sin(p) * Math.cos(k)
99
+ var D22 = Math.cos(o) * Math.cos(k) - Math.sin(o) * Math.sin(p) * Math.sin(k)
100
+ var D23 = - Math.sin(o) * Math.cos(p)
101
+ var D31 = Math.sin(o) * Math.sin(k) - Math.cos(o) * Math.sin(p) * Math.cos(k)
102
+ var D32 = Math.sin(o) * Math.cos(k) + Math.cos(o) * Math.sin(p) * Math.sin(k)
103
+ var D33 = Math.cos(o) * Math.cos(p)
104
+
105
+ var x_dot = (-1)*c*((D11*(X-X0)+D21*(Y-Y0)+D31*(Z-Z0))/(D13*(X-X0)+D23*(Y-Y0)+D33*(Z-Z0)))
106
+ var y_dot = (-1)*c*((D12*(X-X0)+D22*(Y-Y0)+D32*(Z-Z0))/(D13*(X-X0)+D23*(Y-Y0)+D33*(Z-Z0)))
107
+
108
+ var col = ((x_dot-xx0)+(dimX))*(-1)/pix
109
+ var row = ((y_dot-yy0)+(dimY))*(-1)/pix
110
+
111
+ return [col, row]
112
+ }
113
+
114
+ /** Converts degress to radians */
115
+ function radians(degrees) {
116
+ return degrees * (Math.PI / 180)
117
+ }
118
+
119
+ /**
120
+ * Fetches elevation based on X,Y coordinates using DHM/Koter endpoint
121
+ * @param {number} xcoor - EPSG:25832 X coordinate
122
+ * @param {number} ycoor - EPSG:25832 Y coordinate
123
+ * @param {{API_DHM_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
124
+ * @returns {number} Elevation in meters
125
+ */
126
+ async function getZ(xcoor, ycoor, auth) {
127
+ let zcoor_data = await getDHM(`?geop=POINT(${xcoor} ${ycoor})&elevationmodel=dtm`, auth)
128
+ let z = zcoor_data.HentKoterRespons.data[0].kote
129
+ return z
130
+ }
131
+
132
+ /** Iterates guessing at a world coordinate using image coordinates and elevation info */
133
+ function iterateRecursive(image_data, col, row, z, count, limit, auth, i) {
134
+
135
+ // Get coordinates based on current z value
136
+ const worldcoor = image2world(image_data,col,row,z)
137
+
138
+ i--
139
+ if (i <= 0) {
140
+ // max iterations reached
141
+ return [worldcoor, 0, count]
142
+ }
143
+
144
+ // Get new z value from coordinates
145
+ return getZ(worldcoor[0],worldcoor[1], auth).then((new_z) => {
146
+
147
+ // How big is the different between z and new z?
148
+ const delta = Math.abs(new_z - z)
149
+
150
+ if (delta >= limit) {
151
+ // If the difference is too big, try building coordinates with the new z
152
+ count = count + 1
153
+ return iterateRecursive(image_data, col, row, new_z, count, limit, auth, i)
154
+ } else {
155
+ // If the difference is small, return coordinates
156
+ return [worldcoor, delta, count]
157
+ }
158
+ })
159
+ }
160
+
161
+ /**
162
+ * Tries to guess world coordinate for a pixel position within an image using STAC API image data and requests to DHM elevation data.
163
+ * @param {object} image_data - image item data from STAC API
164
+ * @param {number} col - image x coordinate (from left to right)
165
+ * @param {number} row - image y coordinate (from top to bottom)
166
+ * @param {{API_DHM_BASEURL: string, API_DHM_USERNAME: string, API_DHM_PASSWORD: string}} auth - API autentication data. See ../config.js.example for reference.
167
+ * @param {number} [limit] - result may be inaccurate within this limit. Default is 1.
168
+ * @returns {array} [world coordindates (array), elevation discrepancy, calculation iterations]
169
+ */
170
+ function iterate(image_data, col, row, auth, limit = 1) {
171
+ let i = 20 // Maximum number of iterations allowed
172
+ return iterateRecursive(image_data, col, row, 0.5, 0, limit, auth, i)
173
+ }
174
+
175
+ export {
176
+ image2world,
177
+ world2image,
178
+ getZ,
179
+ iterate
180
+ }
181
+
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SAUL map projection and transformation utilities
3
+ */
4
+
5
+ import proj4 from 'proj4'
6
+
7
+ /** Defines an EPSG:25832 projection using proj4.
8
+ * Learn about EPSG:25832 at https://epsg.io/25832 and proj4 at http://proj4js.org/
9
+ * EPSG:25832 definition can be requested at Definition can be requested from https://epsg.io/?format=json&q=25832
10
+ * @param {object} proj4object - a proj4 instance
11
+ * @returns {object} Returns same proj4 instance with an added EPSG:25832 projection definition
12
+ */
13
+ function epsg25832proj(proj4object) {
14
+ proj4object.defs('EPSG:25832', "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
15
+ return proj4object
16
+ }
17
+
18
+ /** Creates an object with methods for translating coordinates from one projection to the other
19
+ * @param {string} [projection1] - from Projection
20
+ * @param {string} [projection2] - to projection
21
+ * @returns {object} An object with two methods is returned, its methods are `forward` which projects from the first projection to the second and `inverse` which projects from the second to the first.
22
+ */
23
+ function createTranslator(projection1 = 'WGS84', projection2 = 'EPSG:25832') {
24
+ if (!proj4.defs['EPSG:25832']) {
25
+ epsg25832proj(proj4) // Define EPSG:25832 projection if missing
26
+ }
27
+ return proj4(projection1, projection2)
28
+ }
29
+
30
+ export {
31
+ epsg25832proj,
32
+ createTranslator
33
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@dataforsyningen/saul",
3
+ "version": "3.0.1",
4
+ "description": "This is a collection of stand-alone methods for photogrammetry calculations and API interactions for 'Danske Skråfotos'.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo 'TESTS WILL ONLY RUN UNDER NODE v18' && node ./test/saul-projection.test.js && node ./test/saul-core.test.js",
9
+ "docs": "jsdoc ./modules/*.js --destination docs"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/SDFIdk/saul.git"
14
+ },
15
+ "keywords": [
16
+ "skråfoto",
17
+ "SDFI",
18
+ "saul"
19
+ ],
20
+ "author": "Styrelsen for Dataforsyning og Infrastruktur (https://sdfi.dk/)",
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/SDFIdk/saul/issues"
24
+ },
25
+ "homepage": "https://github.com/SDFIdk/saul#readme",
26
+ "devDependencies": {
27
+ "jsdoc": "^3.6.10"
28
+ },
29
+ "dependencies": {
30
+ "proj4": "^2.8.0"
31
+ }
32
+ }
@@ -0,0 +1,119 @@
1
+ // Testing saul-core methods
2
+
3
+ // You should add your own config.js with proper tokens, passwords, etc.
4
+ // Check config.js.example for info on how to set it up
5
+ import auth from '../config.js'
6
+
7
+ import assert from 'assert'
8
+ import {getSTAC} from '../modules/api.js'
9
+ import {world2image, image2world, getZ, iterate} from '../modules/saul-core.js'
10
+
11
+ /*
12
+ const item = await getSTAC('/collections/skraafotos2019/items/2019_83_37_2_0046_00001113', auth)
13
+ const world_x = 580341
14
+ const world_y = 6130925
15
+ const image_x = 7373
16
+ const image_y = 3205
17
+ const world_elevation = 32.7199
18
+ */
19
+
20
+ const item = await getSTAC('/collections/skraafotos2021/items/2021_83_36_4_0013_00003824', auth)
21
+ const world_x = 587739
22
+ const world_y = 6130951
23
+ const image_x = 41839.698939
24
+ const image_y = 3204.326930
25
+ const world_elevation = 38.874336
26
+
27
+ function is_equalIsh(num1, num2) {
28
+ const deviation = 0.05
29
+ if (Math.abs(num1 - num2) > deviation) {
30
+ return false
31
+ } else {
32
+ return true
33
+ }
34
+ }
35
+
36
+ // Test getZ
37
+ try {
38
+
39
+ let elevation = await getZ(world_x, world_y, auth)
40
+
41
+ assert(is_equalIsh(elevation, world_elevation), "getZ fail: This is not the Z value we were hoping for.")
42
+
43
+ console.log("Test getZ OK")
44
+
45
+ } catch(error) {
46
+ console.error(error)
47
+ }
48
+
49
+ // Test iterate
50
+ try {
51
+
52
+ let ite = await iterate(item, image_x, image_y, auth, 0.05)
53
+
54
+ assert(is_equalIsh(ite[0][0], world_x), "iterate fail: The coordinate values are not equal") //test if the values are equal
55
+ assert(is_equalIsh(ite[0][1], world_y), "iterate fail: The coordinate values are not equal") //test if the values are equal
56
+
57
+ console.log("Test iterate OK")
58
+
59
+ } catch(error) {
60
+ console.error(error)
61
+ }
62
+
63
+ // Test world2image
64
+ try {
65
+
66
+ let xy = world2image(item, world_x, world_y, world_elevation)
67
+
68
+ assert(is_equalIsh(xy[0], image_x), "world2image fail: The x coordinate values are not equal")
69
+ assert(is_equalIsh(xy[1], image_y), "world2image fail: The y coordinate values are not equal")
70
+
71
+ console.log("Test world2image OK")
72
+
73
+ } catch(error) {
74
+ console.error(error)
75
+ }
76
+
77
+ // Test image2world
78
+ try {
79
+
80
+ let coords = image2world(item, image_x, image_y, world_elevation)
81
+
82
+ assert(is_equalIsh(coords[0], world_x), "image2world fail: The longitude values are not equal")
83
+ assert(is_equalIsh(coords[1], world_y), "image2world fail: The latitude values are not equal")
84
+
85
+ console.log("Test image2world OK")
86
+
87
+ } catch(error) {
88
+ console.error(error)
89
+ }
90
+
91
+ // Test world2image > image2world
92
+ try {
93
+
94
+ let xy1 = world2image(item, world_x, world_y, world_elevation)
95
+ let coords1 = image2world(item, xy1[0], xy1[1], world_elevation)
96
+
97
+ assert(is_equalIsh(coords1[0], world_x), "world2image > image2world fail: The longitude values are not equal")
98
+ assert(is_equalIsh(coords1[1], world_y), "world2image > image2world fail: The latitude values are not equal")
99
+
100
+ console.log("Test world2image > image2world OK")
101
+
102
+ } catch(error) {
103
+ console.error(error)
104
+ }
105
+
106
+ // Test world2image > iterate
107
+ try {
108
+
109
+ let xy2 = world2image(item, world_x, world_y, world_elevation)
110
+ let coords2 = iterate(item, xy2[0], xy2[1], auth)
111
+
112
+ assert(is_equalIsh(coords2[0], world_x), "world2image > iterate fail: The longitude values are not equal")
113
+ assert(is_equalIsh(coords2[1], world_y), "world2image > iterate fail: The latitude values are not equal")
114
+
115
+ console.log("Test world2image > iterate OK")
116
+
117
+ } catch(error) {
118
+ console.error(error)
119
+ }
@@ -0,0 +1,34 @@
1
+ // Testing saul-projection methods
2
+
3
+ import assert from 'assert'
4
+ import proj4 from 'proj4'
5
+ import {epsg25832proj, createTranslator} from '../modules/saul-projection.js'
6
+
7
+ // Test epsg25832proj
8
+ try {
9
+
10
+ const projection = epsg25832proj(proj4)
11
+
12
+ assert.ok(projection.defs['EPSG:25832'], "Projection not defined.")
13
+
14
+ console.log("Test epsg25832proj Ok")
15
+
16
+ } catch(error) {
17
+ console.error(error)
18
+ }
19
+
20
+ // Test createTranslator
21
+ try {
22
+
23
+ const translator = createTranslator()
24
+ const forwarded_coors = translator.forward([10.31,55.37])
25
+ const inverse_coors = translator.inverse(forwarded_coors)
26
+
27
+ assert.strictEqual(forwarded_coors[0], 583023.3267001238, "Failed to forward coordinates")
28
+ assert.strictEqual(inverse_coors[0], 10.31, "Failed to inverse coordinates")
29
+
30
+ console.log("Test createTranslator Ok")
31
+
32
+ } catch(error) {
33
+ console.error(error)
34
+ }