@eturnity/eturnity_maths 8.10.1 → 8.16.1-SLD.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/package.json +1 -1
- package/src/geometry.js +51 -0
- package/src/index.js +1 -0
- package/src/irradiance.js +77 -0
- package/src/objects/Polygon.js +2 -0
- package/src/tests/geometry/distanceTo2DSegment.spec.js +51 -0
package/package.json
CHANGED
package/src/geometry.js
CHANGED
|
@@ -413,6 +413,45 @@ export function getPointInsideOutline(vs, firstLevelHoles = []) {
|
|
|
413
413
|
}
|
|
414
414
|
return result
|
|
415
415
|
}
|
|
416
|
+
export function distanceTo2DPolyline(point, polyline) {
|
|
417
|
+
let minDistance = Infinity
|
|
418
|
+
if (polyline.length < 2) {
|
|
419
|
+
throw new Error('not enough points in polyline')
|
|
420
|
+
}
|
|
421
|
+
for (let i = 0; i < polyline.length - 1; i++) {
|
|
422
|
+
let A = polyline[i]
|
|
423
|
+
let B = polyline[i + 1]
|
|
424
|
+
const distance = distanceTo2DSegment(point, A, B)
|
|
425
|
+
if (distance < minDistance) {
|
|
426
|
+
minDistance = distance
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return minDistance
|
|
430
|
+
}
|
|
431
|
+
export function distanceTo2DSegment(point, A, B) {
|
|
432
|
+
const vectorAB = { x: B.x - A.x, y: B.y - A.y }
|
|
433
|
+
const vectorAP = { x: point.x - A.x, y: point.y - A.y }
|
|
434
|
+
const lengthABSquared = vectorAB.x * vectorAB.x + vectorAB.y * vectorAB.y
|
|
435
|
+
if (lengthABSquared === 0) {
|
|
436
|
+
return Math.sqrt(vectorAP.x * vectorAP.x + vectorAP.y * vectorAP.y)
|
|
437
|
+
}
|
|
438
|
+
const t =
|
|
439
|
+
(vectorAP.x * vectorAB.x + vectorAP.y * vectorAB.y) / lengthABSquared
|
|
440
|
+
if (t < 0) {
|
|
441
|
+
return Math.sqrt(vectorAP.x * vectorAP.x + vectorAP.y * vectorAP.y)
|
|
442
|
+
}
|
|
443
|
+
if (t > 1) {
|
|
444
|
+
const vectorBP = { x: point.x - B.x, y: point.y - B.y }
|
|
445
|
+
return Math.sqrt(vectorBP.x * vectorBP.x + vectorBP.y * vectorBP.y)
|
|
446
|
+
}
|
|
447
|
+
const projection = {
|
|
448
|
+
x: A.x + t * vectorAB.x,
|
|
449
|
+
y: A.y + t * vectorAB.y,
|
|
450
|
+
}
|
|
451
|
+
const dx = point.x - projection.x
|
|
452
|
+
const dy = point.y - projection.y
|
|
453
|
+
return Math.sqrt(dx * dx + dy * dy)
|
|
454
|
+
}
|
|
416
455
|
|
|
417
456
|
export function distanceToEdge2D(M, A, B) {
|
|
418
457
|
const pA = { x: A.x, y: A.y, z: 0 }
|
|
@@ -734,6 +773,18 @@ export function get3pointNotAlignedFromOutline(outline) {
|
|
|
734
773
|
}
|
|
735
774
|
return [A, B, C]
|
|
736
775
|
}
|
|
776
|
+
export function getNormalVectorFromFlatOutline(outline) {
|
|
777
|
+
const ABC = get3pointNotAlignedFromOutline(outline)
|
|
778
|
+
if (!ABC) {
|
|
779
|
+
return null
|
|
780
|
+
}
|
|
781
|
+
let normalVector = getNormalVectorFrom3Points(...ABC)
|
|
782
|
+
if (normalVector.z < 0) {
|
|
783
|
+
normalVector = multiplyVector(-1, normalVector)
|
|
784
|
+
}
|
|
785
|
+
normalVector = normalizeVector(normalVector)
|
|
786
|
+
return normalVector
|
|
787
|
+
}
|
|
737
788
|
export function getNormalVectortoEdgeTowardPolygon(k, outline) {
|
|
738
789
|
const A = outline[k]
|
|
739
790
|
const B = outline[(k + 1) % outline.length]
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { dotProduct, inclineWithNormalVector, normalizeVector } from './index'
|
|
2
|
+
export function ineichenDiffuseIrradiance(dni, dhi, plane_theta, cosAOI) {
|
|
3
|
+
//compute of Ineichen diffuse
|
|
4
|
+
const F = 1.0 - Math.pow(dhi / Math.max(dni * cosAOI + dhi, 1e-6), 2.0)
|
|
5
|
+
const diffusion_Ineichen =
|
|
6
|
+
dhi *
|
|
7
|
+
((1.0 + Math.cos(plane_theta)) / 2.0) *
|
|
8
|
+
(1.0 + F * Math.pow(Math.sin(plane_theta / 2.0), 3.0)) *
|
|
9
|
+
(1.0 + F * Math.pow(cosAOI, 2.0) * Math.pow(Math.cos(plane_theta), 3.0))
|
|
10
|
+
return diffusion_Ineichen
|
|
11
|
+
}
|
|
12
|
+
export function getOptimizedGHI({ dataGrids, lat, lng }) {
|
|
13
|
+
// Compute normal vector for max DNI
|
|
14
|
+
let optimized_panel_tilt
|
|
15
|
+
if (Math.abs(lat) < 25) optimized_panel_tilt = Math.abs(lat) * 0.87
|
|
16
|
+
else if (Math.abs(lat) < 50) optimized_panel_tilt = Math.abs(lat) * 0.76 + 3.1
|
|
17
|
+
else optimized_panel_tilt = Math.abs(lat) * 0.76 + 3.1
|
|
18
|
+
|
|
19
|
+
const phi = 180
|
|
20
|
+
const theta = optimized_panel_tilt
|
|
21
|
+
const phiRad = (phi * Math.PI) / 180
|
|
22
|
+
const thetaRad = (theta * Math.PI) / 180
|
|
23
|
+
const optimizedNormalVector = {
|
|
24
|
+
x: Math.sin(thetaRad) * Math.sin(phiRad),
|
|
25
|
+
y: Math.sin(thetaRad) * Math.cos(phiRad),
|
|
26
|
+
z: Math.cos(thetaRad),
|
|
27
|
+
}
|
|
28
|
+
let { ghi } = getGHI({
|
|
29
|
+
dataGrids: dataGrids,
|
|
30
|
+
normalVector: optimizedNormalVector,
|
|
31
|
+
})
|
|
32
|
+
return ghi
|
|
33
|
+
}
|
|
34
|
+
export function getGHI({ dataGrids, normalVector }) {
|
|
35
|
+
// Compute highest GHI
|
|
36
|
+
normalVector = normalizeVector(normalVector)
|
|
37
|
+
let GHISum = 0
|
|
38
|
+
let DHISum = 0
|
|
39
|
+
let DNISum = 0
|
|
40
|
+
const resolution = {
|
|
41
|
+
x: 1 / dataGrids.dni_grid[0].length,
|
|
42
|
+
y: 1 / dataGrids.dni_grid.length,
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < dataGrids.dni_grid.length; i++) {
|
|
45
|
+
for (let j = 0; j < dataGrids.dni_grid[0].length; j++) {
|
|
46
|
+
const phi = j * resolution.x
|
|
47
|
+
const theta = i * resolution.y
|
|
48
|
+
const phiRad = phi * Math.PI * 2
|
|
49
|
+
const thetaRad = (theta * Math.PI) / 2
|
|
50
|
+
const sunDir = {
|
|
51
|
+
x: Math.sin(thetaRad) * Math.sin(phiRad),
|
|
52
|
+
y: Math.sin(thetaRad) * Math.cos(phiRad),
|
|
53
|
+
z: Math.cos(thetaRad),
|
|
54
|
+
}
|
|
55
|
+
const cosAngle = Math.max(dotProduct(normalVector, sunDir), 0)
|
|
56
|
+
const dni = dataGrids.dni_grid[i][j]
|
|
57
|
+
const dhi = dataGrids.dhi_grid[i][j]
|
|
58
|
+
const plane_theta = inclineWithNormalVector(normalVector)
|
|
59
|
+
const ineichen_dhi = ineichenDiffuseIrradiance(
|
|
60
|
+
dni,
|
|
61
|
+
dhi,
|
|
62
|
+
plane_theta,
|
|
63
|
+
cosAngle
|
|
64
|
+
)
|
|
65
|
+
const ghi = dni * cosAngle + ineichen_dhi
|
|
66
|
+
DHISum += ineichen_dhi
|
|
67
|
+
DNISum += dni
|
|
68
|
+
GHISum += ghi
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
dhi: DHISum,
|
|
74
|
+
dni: DNISum,
|
|
75
|
+
ghi: GHISum,
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/objects/Polygon.js
CHANGED
|
@@ -332,6 +332,8 @@ export class Polygon {
|
|
|
332
332
|
row_spacing_mm: this.data.row_spacing_mm,
|
|
333
333
|
is_row_spacing_automatic: this.data.is_row_spacing_automatic,
|
|
334
334
|
offset_percent: this.data.offset_percent,
|
|
335
|
+
fitting_panel_area_m2: this.data.fitting_panel_area_m2,
|
|
336
|
+
has_fitting_panels: this.data.has_fitting_panels,
|
|
335
337
|
panel_orientation: this.data.panel_orientation,
|
|
336
338
|
panel_direction_degrees: this.data.panel_direction_degrees,
|
|
337
339
|
component_id_pv_module: this.data.component_id_pv_module,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { distanceTo2DSegment } from '../../index'
|
|
2
|
+
|
|
3
|
+
describe('distanceTo2DSegment function', () => {
|
|
4
|
+
test('top middle', () => {
|
|
5
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
6
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
7
|
+
const point = { x: 5, y: 10, z: 0 }
|
|
8
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(10, 4)
|
|
9
|
+
})
|
|
10
|
+
test('bottom', () => {
|
|
11
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
12
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
13
|
+
const point = { x: 5, y: -10, z: 0 }
|
|
14
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(10, 4)
|
|
15
|
+
})
|
|
16
|
+
test('diagonal', () => {
|
|
17
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
18
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
19
|
+
const point = { x: -3, y: 4, z: 0 }
|
|
20
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(5, 4)
|
|
21
|
+
})
|
|
22
|
+
test('point aligned with A and B', () => {
|
|
23
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
24
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
25
|
+
const point = { x: -10, y: 0, z: 0 }
|
|
26
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(10, 4)
|
|
27
|
+
})
|
|
28
|
+
test('point aligned with A and B right', () => {
|
|
29
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
30
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
31
|
+
const point = { x: 20, y: 0, z: 0 }
|
|
32
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(10, 4)
|
|
33
|
+
})
|
|
34
|
+
test('AB in diagonal', () => {
|
|
35
|
+
const A = { x: 0, y: 10, z: 0 }
|
|
36
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
37
|
+
const point = { x: 0, y: 0, z: 0 }
|
|
38
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(7.07106, 4)
|
|
39
|
+
})
|
|
40
|
+
test('point and A and A', () => {
|
|
41
|
+
const A = { x: 0, y: 10, z: 0 }
|
|
42
|
+
const point = { x: 0, y: 0, z: 0 }
|
|
43
|
+
expect(distanceTo2DSegment(point, A, A)).toBeCloseTo(10, 4)
|
|
44
|
+
})
|
|
45
|
+
test('point vertical to B', () => {
|
|
46
|
+
const A = { x: 0, y: 0, z: 0 }
|
|
47
|
+
const B = { x: 10, y: 0, z: 0 }
|
|
48
|
+
const point = { x: 10, y: 10, z: 0 }
|
|
49
|
+
expect(distanceTo2DSegment(point, A, B)).toBeCloseTo(10, 4)
|
|
50
|
+
})
|
|
51
|
+
})
|