@eturnity/eturnity_maths 7.20.0 → 7.24.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.
@@ -1,252 +1,333 @@
1
-
2
- import {
3
- translate2D,
4
- getDegree
5
- } from '../geometry'
1
+ import { translate2D, getDegree } from '../geometry'
6
2
  import {
7
- substractVector,
8
- normalizeVector,
9
- multiplyVector,
10
- addVector
3
+ substractVector,
4
+ normalizeVector,
5
+ multiplyVector,
6
+ addVector
11
7
  } from '../vector'
12
8
 
13
9
  import { v4 as uuidv4 } from 'uuid'
14
- import {updateComputedGeometryPolygon} from './derivedState/updateComputedGeometryPolygon'
10
+ import { updateComputedGeometryPolygon } from './derivedState/updateComputedGeometryPolygon'
11
+ import { rotateTransformation } from '../matrix'
15
12
 
16
13
  export class Polygon {
17
- constructor(outline = [], layer, isClosed = true) {
18
- this.id = uuidv4()
19
- this.name = ''
20
- this.version = 0
21
- this.type = 'polygon'
22
- this.outline = JSON.parse(JSON.stringify(outline))
23
- this.holes = []
24
- this.layer = layer
25
- this.isClosed = isClosed
26
- this.highlight = false
27
- this.visible = true
28
- this.heightReference = 0
29
- this.priority = 0
30
- this.margins = {
31
- isSameMargin: true,
32
- sameMargin: 200,
33
- innerOutline: [],
34
- outterOutline: [],
35
- distances: []
36
- }
37
- this.isLoading = false
38
- if (this.layer == 'obstacle') {
39
- this.isParallel = true
40
- }
41
- }
42
-
43
- clone() {
44
- const polygon = new Polygon()
45
- this.outline.forEach((p) => {
46
- polygon.outline.push({ ...p })
47
- })
48
- this.holes.forEach((h) => {
49
- polygon.holes.push({ ...h })
50
- })
51
- polygon.id = uuidv4()
52
- polygon.layer = this.layer
53
- polygon.isClosed = this.isClosed
54
- return polygon
55
- }
56
- getAngle(i){
57
- const length=this.outline.length
58
- const firstPoint = this.outline[(i-1+length) % length]
59
- const secondPoint = this.outline[i % length]
60
- const thirdPoint = this.outline[(i + 1) % length]
61
- return getDegree(firstPoint, secondPoint, thirdPoint)
62
- }
63
- getDirectionSnapAngle(){
64
- let angles=[]
65
- for(let k=0;k<this.outline.length;k++){
66
- let p_0=this.outline[k]
67
- let p_1=this.outline[(k+1)%this.outline.length]
68
- let angle = Math.atan2(
69
- p_0.x - p_1.x,
70
- p_0.y - p_1.y
71
- )
72
- angle*=180/Math.PI
73
- angles.push(angle+90%360)
74
- angles.push(angle+270%360)
75
- }
76
- return angles
77
- }
78
- getBounds(){
79
- let bounds=this.outline.reduce((acc,cur)=>{
80
- return{
81
- xMin:Math.min(acc.xMin,cur.x),
82
- yMin:Math.min(acc.yMin,cur.y),
83
- xMax:Math.max(acc.xMax,cur.x),
84
- yMax:Math.max(acc.yMax,cur.y),
85
- }
86
- },{xMin:Infinity,xMax:-Infinity,yMin:Infinity,yMax:-Infinity})
87
- let center={
88
- x:(bounds.xMax+bounds.xMin)/2,
89
- y:(bounds.yMax+bounds.yMin)/2,
90
- }
91
- let radius=Math.hypot(bounds.xMax-bounds.xMin,bounds.yMax-bounds.yMin)/2
92
- return {...bounds,radius,center}
93
- }
94
- get4pointsSquareAngle(i,canvasContext){
95
- const length=this.outline.length
96
- const A = this.outline[(i-1+length) % length]
97
- const B = this.outline[i % length]
98
- const C = this.outline[(i + 1) % length]
99
- const toCanvasRef = canvasContext.toCanvasRef
100
- //angle in B
101
- const BD=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(A,B)))
102
- const BE=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(C,B)))
103
- const D=addVector(B,BD)
104
- const E=addVector(B,BE)
105
- const F=addVector(D,BE)
14
+ constructor(outline = [], layer, isClosed = true) {
15
+ this.id = uuidv4()
16
+ this.name = ''
17
+ this.version = 0
18
+ this.type = 'polygon'
19
+ this.outline = JSON.parse(JSON.stringify(outline))
20
+ this.holes = []
21
+ this.roofs = []
22
+ this.layer = layer
23
+ this.isClosed = isClosed
24
+ this.highlight = false
25
+ this.visible = true
26
+ this.heightReference = 0
27
+ this.priority = 0
28
+ this.margins = {
29
+ isSameMargin: true,
30
+ sameMargin: 200,
31
+ innerOutline: [],
32
+ outterOutline: [],
33
+ distances: []
34
+ }
35
+ this.isLoading = false
36
+ this.positionOffset = { x: 0, y: 0, z: 0 }
37
+ this.angleOffset = 0
38
+ if (this.layer == 'obstacle') {
39
+ this.isParallel = true
40
+ }
41
+ }
106
42
 
107
- return [toCanvasRef(D),toCanvasRef(F),toCanvasRef(E)]
108
- }
109
-
110
- translate(vectorInMm) {
111
- this.version++
112
- this.outline = this.outline.map((point) => {
113
- return translate2D(point, vectorInMm)
114
- })
115
- updateComputedGeometryPolygon(this)
116
- }
117
- serialize() {
118
- const baseSerialization = {
119
- id: this.id,
120
- version: this.version,
121
- name: this.name,
122
- type: this.type,
123
- outline: this.outline.map(p=>{return {
124
- x: p.x,
125
- y: p.y,
126
- z: p.z,
127
- selected:p.selected,
128
- open:p.open,
129
- }
130
- }),
131
- holes: this.holes.map((p) => p.id),
132
- layer: this.layer,
133
- highlight: this.highlight,
134
- visible: this.visible,
135
- margins: {
136
- isSameMargin:this.margins.isSameMargin,
137
- sameMargin:this.margins.sameMargin,
138
- distances:JSON.parse(JSON.stringify(this.margins.distances))
139
- }
140
- }
141
- const extraSerialization = {}
142
- if (this.layer == 'roof') {
143
- extraSerialization.moduleFields = this.moduleFields.map((m) => {
144
- return {
145
- id: m.id
146
- }
147
- })
148
- } else if (this.layer == 'obstacle') {
149
- extraSerialization.isParallel = this.isParallel
150
- extraSerialization.height = this.height
151
- extraSerialization.roofs = this.roofs.map((roof) => {
152
- return { id: roof.id }
153
- })
154
- } else if (this.layer == 'moduleField') {
155
- const modules=[]
156
- if(this.panels){
157
- this.panels.forEach((p) => {
158
- modules.push({ id: p.id, index: p.index, outline: p.outline, status: p.status || "active",clipped:p.clipped || false })
159
- })
160
- }
161
- if(this.userDeactivatedPanels){
162
- this.userDeactivatedPanels.forEach((p) => {
163
- modules.push({ id: p.id, index: p.index, outline: p.outline, status: p.status || "user_deactivated",clipped:p.clipped || false })
164
- })
165
- }
166
- extraSerialization.data = {...this.data,modules:[...this.data.modules]}
167
- extraSerialization.roof = { id: this.roof.id }
168
- extraSerialization.pvData = this.pvData?{...this.pvData}:null
169
- extraSerialization.mountingData = this.mountingData?{...this.mountingData}:null
170
- extraSerialization.modules = modules
171
- extraSerialization.needsOptimisation = this.needsOptimisation
172
- extraSerialization.priority = this.priority
43
+ clone() {
44
+ const polygon = new Polygon()
45
+ Object.keys(this).forEach((key) => {
46
+ polygon[key] = this[key]
47
+ })
48
+ polygon.id = uuidv4()
49
+ return polygon
50
+ }
51
+ getAngle(i) {
52
+ i = parseInt(i)
53
+ const length = this.outline.length
54
+ const firstPoint = this.outline[(i - 1 + length) % length]
55
+ const secondPoint = this.outline[i % length]
56
+ const thirdPoint = this.outline[(i + 1) % length]
57
+ return getDegree(firstPoint, secondPoint, thirdPoint)
58
+ }
59
+ getAngleFromTop(i) {
60
+ const length = this.outline.length
61
+ const firstPoint = { ...this.outline[(i - 1 + length) % length], z: 0 }
62
+ const secondPoint = { ...this.outline[i % length], z: 0 }
63
+ const thirdPoint = { ...this.outline[(i + 1) % length], z: 0 }
64
+ return getDegree(firstPoint, secondPoint, thirdPoint)
65
+ }
66
+ getDirectionSnapAngle() {
67
+ let angles = []
68
+ for (let k = 0; k < this.outline.length; k++) {
69
+ let p_0 = this.outline[k]
70
+ let p_1 = this.outline[(k + 1) % this.outline.length]
71
+ let angle = Math.atan2(p_0.x - p_1.x, p_0.y - p_1.y)
72
+ angle *= 180 / Math.PI
73
+ angles.push(angle + (90 % 360))
74
+ angles.push(angle + (270 % 360))
75
+ }
76
+ return angles
77
+ }
78
+ getBounds() {
79
+ let bounds = this.outline.reduce(
80
+ (acc, cur) => {
81
+ return {
82
+ xMin: Math.min(acc.xMin, cur.x),
83
+ yMin: Math.min(acc.yMin, cur.y),
84
+ xMax: Math.max(acc.xMax, cur.x),
85
+ yMax: Math.max(acc.yMax, cur.y)
86
+ }
87
+ },
88
+ { xMin: Infinity, xMax: -Infinity, yMin: Infinity, yMax: -Infinity }
89
+ )
90
+ let center = {
91
+ x: (bounds.xMax + bounds.xMin) / 2,
92
+ y: (bounds.yMax + bounds.yMin) / 2
93
+ }
94
+ let radius =
95
+ Math.hypot(bounds.xMax - bounds.xMin, bounds.yMax - bounds.yMin) / 2
96
+ return { ...bounds, radius, center }
97
+ }
98
+ get4pointsSquareAngle(i, canvasContext) {
99
+ const length = this.outline.length
100
+ const A = this.outline[(i - 1 + length) % length]
101
+ const B = this.outline[i % length]
102
+ const C = this.outline[(i + 1) % length]
103
+ const toCanvasRef = canvasContext.toCanvasRef
104
+ //angle in B
105
+ const BD = multiplyVector(
106
+ 15 * canvasContext.mmPerPx,
107
+ normalizeVector(substractVector(A, B))
108
+ )
109
+ const BE = multiplyVector(
110
+ 15 * canvasContext.mmPerPx,
111
+ normalizeVector(substractVector(C, B))
112
+ )
113
+ const D = addVector(B, BD)
114
+ const E = addVector(B, BE)
115
+ const F = addVector(D, BE)
173
116
 
174
- } else if (this.layer == 'panel') {
175
- extraSerialization.index = this.index
176
- extraSerialization.moduleField = { id: this.moduleField.id }
177
- } else if (this.layer == 'user_deactivated_panel') {
178
- extraSerialization.index = this.index
179
- extraSerialization.moduleField = { id: this.moduleField.id }
180
- }
181
- return JSON.parse(JSON.stringify({ ...baseSerialization, ...extraSerialization }))
182
- }
183
- serializeForBEStorage() {
184
- if (this.layer == 'roof') {
185
- const margins_roof_mm = this.margins.isSameMargin
186
- ? Array(this.outline.length).fill(this.margins.sameMargin)
187
- : this.margins.distances
188
- return {
189
- roof_uuid: this.id,
190
- version: this.version,
191
- roof_outline: this.outline.map((v) => [v.x, v.y, v.z]),
192
- roof_margins_mm: margins_roof_mm,
193
- roof_slope_degrees: this.incline,
194
- roof_orientation_degrees: this.direction,
195
- roof_name: this.name,
196
- shading_data: this.shadingData ? this.shadingData : undefined
197
- }
198
- } else if (this.layer == 'obstacle') {
199
- const margins_obstacle_mm = this.margins.isSameMargin
200
- ? Array(this.outline.length).fill(this.margins.sameMargin)
201
- : this.margins.distances
202
- return {
203
- obstacle_uuid: this.id,
204
- version: this.version,
205
- obstacle_outline: this.outline.map((v) => [v.x, v.y, v.z]),
206
- obstacle_margins_mm: margins_obstacle_mm,
207
- obstacle_height_above_ground_mm: this.height,
208
- obstacle_slope_is_parallel_to_roof: this.isParallel
209
- }
210
- } else if (['moduleField', 'tmpModuleField'].includes(this.layer)) {
211
- if (!this.panels) {
212
- this.panels = []
213
- }
214
- if (!this.userDeactivatedPanels) {
215
- this.userDeactivatedPanels = []
216
- }
217
- const modules = this.data.modules
218
- return {
219
- module_field_uuid: this.id,
220
- roof_uuid: this.roof.id,
221
- version: this.version,
222
- module_field_outline: this.outline.map((v) => [v.x, v.y, v.z]),
223
- col_spacing_mm: this.data.col_spacing_mm,
224
- row_spacing_mm: this.data.row_spacing_mm,
225
- offset_percent: this.data.offset_percent,
226
- panel_orientation: this.data.panel_orientation,
227
- panel_direction_degrees: this.data.panel_direction_degrees,
228
- component_id_pv_module: this.data.component_id_pv_module,
229
- component_id_mounting_system: this.data.component_id_mounting_system,
230
- modules: modules,
231
- module_tilt_degrees: this.data.module_tilt_degrees,
232
- base_line_index: this.data.base_line_index,
233
- justify_percent: this.data.justify_percent,
117
+ return [toCanvasRef(D), toCanvasRef(F), toCanvasRef(E)]
118
+ }
119
+ rotate(deltaAngle, center = { x: 0, y: 0 }) {
120
+ this.version++
121
+ this.angleOffset += deltaAngle
122
+ this.outline.forEach((point) => {
123
+ const newPoint = rotateTransformation(point, deltaAngle, center)
124
+ point.x = newPoint.x
125
+ point.y = newPoint.y
126
+ })
127
+ if (this.layer == 'moduleField' && this.data && this.data.modules) {
128
+ if (this.data.panel_direction_degrees != null) {
129
+ this.data.panel_direction_degrees += (deltaAngle * 180) / Math.PI
130
+ }
234
131
 
235
- number_of_panels_override:!!this.data.number_of_panels_override,
236
- number_of_panels:this.data.number_of_panels,
237
- yearly_yield_manual_entry:this.data.yearly_yield_manual_entry,
238
- yearly_yield_kwh_kwp:this.data.yearly_yield_kwh_kwp,
239
- yearly_yield_kwh_kwp_2:this.data.yearly_yield_kwh_kwp_2
240
- }
241
- } else if (['panel', 'user_deactivated_panel'].includes(this.layer)) {
242
- return {
243
- id: this.id,
244
- index: this.index,
245
- outline: this.outline.map((v) => [v.x, v.y, v.z]),
246
- moduleField: {
247
- id: this.moduleField.id
248
- }
249
- }
250
- }
251
- }
252
- }
132
+ this.data.modules.forEach((module) => {
133
+ module.outline.forEach((p) => {
134
+ const newPoint = rotateTransformation(
135
+ { x: p[0], y: p[1] },
136
+ deltaAngle,
137
+ center
138
+ )
139
+ p[0] = newPoint.x
140
+ p[1] = newPoint.y
141
+ })
142
+ })
143
+ }
144
+ updateComputedGeometryPolygon(this)
145
+ }
146
+ rotateOffset(newAngleOffset, center = { x: 0, y: 0 }) {
147
+ const deltaAngle = newAngleOffset - this.angleOffset
148
+ this.rotate(deltaAngle, center)
149
+ }
150
+ translate(vectorInMm) {
151
+ this.version++
152
+ if(this.roofs){
153
+ this.roofs.forEach(r=>r.version++)
154
+ }
155
+ this.positionOffset.x += vectorInMm.x
156
+ this.positionOffset.y += vectorInMm.y
157
+ if (vectorInMm.z) {
158
+ this.positionOffset.z += vectorInMm.z
159
+ }
160
+ this.outline = this.outline.map((point) => {
161
+ return translate2D(point, vectorInMm)
162
+ })
163
+ if (this.layer == 'moduleField' && this.data && this.data.modules) {
164
+ this.data.modules.forEach((module) => {
165
+ module.outline.forEach((p) => {
166
+ p[0] += vectorInMm.x
167
+ p[1] += vectorInMm.y
168
+ })
169
+ })
170
+ }
171
+ updateComputedGeometryPolygon(this)
172
+ }
173
+ translateOffset(newPositionOffset, center = { x: 0, y: 0 }) {
174
+ const shift = substractVector(newPositionOffset, this.positionOffset)
175
+ this.translate(shift)
176
+ }
177
+ serialize() {
178
+ const baseSerialization = {
179
+ id: this.id,
180
+ version: this.version,
181
+ name: this.name,
182
+ type: this.type,
183
+ outline: this.outline.map((p) => {
184
+ return {
185
+ x: p.x,
186
+ y: p.y,
187
+ z: p.z,
188
+ selected: p.selected,
189
+ locked: p.locked,
190
+ open: p.open
191
+ }
192
+ }),
193
+ holes: this.holes.map((p) => p.id),
194
+ layer: this.layer,
195
+ highlight: this.highlight,
196
+ visible: this.visible,
197
+ margins: {
198
+ isSameMargin: this.margins.isSameMargin,
199
+ sameMargin: this.margins.sameMargin,
200
+ distances: JSON.parse(JSON.stringify(this.margins.distances))
201
+ }
202
+ }
203
+ const extraSerialization = {}
204
+ if (this.layer == 'roof') {
205
+ extraSerialization.moduleFields = this.moduleFields.map((m) => {
206
+ return {
207
+ id: m.id
208
+ }
209
+ })
210
+ } else if (this.layer == 'obstacle') {
211
+ extraSerialization.isParallel = this.isParallel
212
+ extraSerialization.height = this.height
213
+ extraSerialization.roofs = this.roofs.map((roof) => {
214
+ return { id: roof.id }
215
+ })
216
+ } else if (this.layer == 'moduleField') {
217
+ const modules = []
218
+ if (this.panels) {
219
+ this.panels.forEach((p) => {
220
+ modules.push({
221
+ id: p.id,
222
+ index: p.index,
223
+ outline: p.outline,
224
+ status: p.status || 'active',
225
+ clipped: p.clipped || false
226
+ })
227
+ })
228
+ }
229
+ if (this.userDeactivatedPanels) {
230
+ this.userDeactivatedPanels.forEach((p) => {
231
+ modules.push({
232
+ id: p.id,
233
+ index: p.index,
234
+ outline: p.outline,
235
+ status: p.status || 'user_deactivated',
236
+ clipped: p.clipped || false
237
+ })
238
+ })
239
+ }
240
+ extraSerialization.data = {
241
+ ...this.data,
242
+ modules: [...this.data.modules]
243
+ }
244
+ extraSerialization.roof = { id: this.roof.id }
245
+ extraSerialization.pvData = this.pvData ? { ...this.pvData } : null
246
+ extraSerialization.mountingData = this.mountingData
247
+ ? { ...this.mountingData }
248
+ : null
249
+ extraSerialization.modules = modules
250
+ extraSerialization.needsOptimisation = this.needsOptimisation
251
+ extraSerialization.priority = this.priority
252
+ } else if (this.layer == 'panel') {
253
+ extraSerialization.index = this.index
254
+ extraSerialization.moduleField = { id: this.moduleField.id }
255
+ } else if (this.layer == 'user_deactivated_panel') {
256
+ extraSerialization.index = this.index
257
+ extraSerialization.moduleField = { id: this.moduleField.id }
258
+ }
259
+ return JSON.parse(
260
+ JSON.stringify({ ...baseSerialization, ...extraSerialization })
261
+ )
262
+ }
263
+ serializeForBEStorage() {
264
+ if (this.layer == 'roof') {
265
+ const margins_roof_mm = this.margins.isSameMargin
266
+ ? Array(this.outline.length).fill(this.margins.sameMargin)
267
+ : this.margins.distances
268
+ return {
269
+ roof_uuid: this.id,
270
+ version: this.version,
271
+ roof_outline: this.outline.map((v) => [v.x, v.y, v.z]),
272
+ roof_margins_mm: margins_roof_mm,
273
+ roof_slope_degrees: this.incline,
274
+ roof_orientation_degrees: this.direction,
275
+ roof_name: this.name,
276
+ shading_data: this.shadingData ? this.shadingData : undefined
277
+ }
278
+ } else if (this.layer == 'obstacle') {
279
+ const margins_obstacle_mm = this.margins.isSameMargin
280
+ ? Array(this.outline.length).fill(this.margins.sameMargin)
281
+ : this.margins.distances
282
+ return {
283
+ obstacle_uuid: this.id,
284
+ version: this.version,
285
+ obstacle_outline: this.outline.map((v) => [v.x, v.y, v.z]),
286
+ obstacle_margins_mm: margins_obstacle_mm,
287
+ obstacle_height_above_ground_mm: this.height,
288
+ obstacle_slope_is_parallel_to_roof: this.isParallel
289
+ }
290
+ } else if (['moduleField', 'tmpModuleField'].includes(this.layer)) {
291
+ if (!this.panels) {
292
+ this.panels = []
293
+ }
294
+ if (!this.userDeactivatedPanels) {
295
+ this.userDeactivatedPanels = []
296
+ }
297
+ const modules = this.data.modules
298
+ return {
299
+ module_field_uuid: this.id,
300
+ roof_uuid: this.roof.id,
301
+ version: this.version,
302
+ module_field_outline: this.outline.map((v) => [v.x, v.y, v.z]),
303
+ col_spacing_mm: this.data.col_spacing_mm,
304
+ row_spacing_mm: this.data.row_spacing_mm,
305
+ offset_percent: this.data.offset_percent,
306
+ panel_orientation: this.data.panel_orientation,
307
+ panel_direction_degrees: this.data.panel_direction_degrees,
308
+ component_id_pv_module: this.data.component_id_pv_module,
309
+ component_id_mounting_system: this.data.component_id_mounting_system,
310
+ modules: modules,
311
+ module_tilt_degrees: this.data.module_tilt_degrees,
312
+ base_line_index: this.data.base_line_index,
313
+ justify_percent: this.data.justify_percent,
314
+ pv_module_id: this.pvModuleId,
315
+ mounting_system_id: this.mountingSystemId,
316
+ number_of_panels_override: !!this.data.number_of_panels_override,
317
+ number_of_panels: this.data.number_of_panels,
318
+ yearly_yield_manual_entry: this.data.yearly_yield_manual_entry,
319
+ yearly_yield_kwh_kwp: this.data.yearly_yield_kwh_kwp,
320
+ yearly_yield_kwh_kwp_2: this.data.yearly_yield_kwh_kwp_2
321
+ }
322
+ } else if (['panel', 'user_deactivated_panel'].includes(this.layer)) {
323
+ return {
324
+ id: this.id,
325
+ index: this.index,
326
+ outline: this.outline.map((v) => [v.x, v.y, v.z]),
327
+ moduleField: {
328
+ id: this.moduleField.id
329
+ }
330
+ }
331
+ }
332
+ }
333
+ }