@eturnity/eturnity_maths 1.0.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.
@@ -0,0 +1,51 @@
1
+
2
+ import {
3
+ getDistanceBetweenPoints,
4
+ translate2D,
5
+ } from '../geometry'
6
+ import { v4 as uuidv4 } from 'uuid'
7
+ import {Point} from './Point'
8
+
9
+
10
+
11
+ export class Circle {
12
+ constructor(center, radius, layer) {
13
+ this.id = uuidv4()
14
+ this.type = 'circle'
15
+ this.version = 0
16
+ this.center = center
17
+ this.radius = radius
18
+ this.layer = layer
19
+ this.visible = true
20
+ }
21
+
22
+ clone() {
23
+ const circle = new Circle(
24
+ new Point(this.point.x, this.point.y),
25
+ this.radius,
26
+ this.layer
27
+ )
28
+ circle.id = uuidv4()
29
+ return circle
30
+ }
31
+ getPxDistanceTo(point, canvasContext) {
32
+ const toCanvasRef = canvasContext.toCanvasRef
33
+ let pxCenter = toCanvasRef(this.center)
34
+ let pxRadius = this.radius / canvasContext.mmPerPx
35
+ return Math.abs(getDistanceBetweenPoints(point, pxCenter) - pxRadius)
36
+ }
37
+ getProjectedPoint(point) {
38
+ let distance = getDistanceBetweenPoints(point, this.center)
39
+ if (distance == 0) {
40
+ console.error("can't project center to cercle", this)
41
+ return null
42
+ }
43
+ let x = this.center.x + (point.x - this.center.x) * (this.radius / distance)
44
+ let y = this.center.y + (point.y - this.center.y) * (this.radius / distance)
45
+ let P = new Point(x, y, 0, this.layer)
46
+ return P
47
+ }
48
+ translate(vectorInMm) {
49
+ this.center = translate2D(this.center, vectorInMm)
50
+ }
51
+ }
@@ -0,0 +1,110 @@
1
+
2
+ import {
3
+ getDistanceBetweenPoints,
4
+ getPointOnLine,
5
+ isSamePoint3D,
6
+ translate2D,
7
+ midPoint,
8
+ } from '../geometry'
9
+ import { v4 as uuidv4 } from 'uuid'
10
+ import {Point} from './Point'
11
+
12
+
13
+ export class Line {
14
+ constructor(firstPoint, lastPoint, layer, infiniteLine = false) {
15
+ this.id = uuidv4()
16
+ this.version = 0
17
+ this.type = 'line'
18
+ if (firstPoint && lastPoint) {
19
+ this.outline = [firstPoint, lastPoint]
20
+ } else {
21
+ this.outline = []
22
+ }
23
+ this.layer = layer
24
+ this.visible = true
25
+ this.infiniteLine = infiniteLine
26
+ }
27
+ clone() {
28
+ const line = new Line()
29
+ this.outline.forEach((p) => {
30
+ line.outline.push(new Point(p.x, p.y, p.z))
31
+ })
32
+ line.id = uuidv4()
33
+ line.layer = this.layer
34
+ line.infiniteLine = this.infiniteLine
35
+ return line
36
+ }
37
+ pxLength(canvasContext) {
38
+ return (
39
+ getDistanceBetweenPoints(this.outline[0], this.outline[1]) /
40
+ canvasContext.mmPerPx
41
+ )
42
+ }
43
+ mmLength(canvasContext) {
44
+ return getDistanceBetweenPoints(this.outline[0], this.outline[1])
45
+ }
46
+
47
+ getMidPoint() {
48
+ return midPoint(this.outline[0], this.outline[1])
49
+ }
50
+ getAngleDeg(){
51
+ const angle = Math.atan2(
52
+ this.outline[1].y - this.outline[0].y,
53
+ this.outline[0].x - this.outline[1].x
54
+ )
55
+ return (angle * 180) / Math.PI
56
+ }
57
+ getPxDistanceTo(point, canvasContext) {
58
+ const toCanvasRef = canvasContext.toCanvasRef
59
+ const toRealityRef = canvasContext.toRealityRef
60
+
61
+ if (this.outline.length < 2) {
62
+ console.error('line undefined')
63
+ return null
64
+ }
65
+ if (isSamePoint3D(this.outline[0], this.outline[1])) {
66
+ console.error('line undefined')
67
+ return null
68
+ }
69
+
70
+ let realityRefPoint = toRealityRef(point)
71
+ let P = getPointOnLine(realityRefPoint, this.outline[0], this.outline[1])
72
+ return getDistanceBetweenPoints(realityRefPoint, P) / canvasContext.mmPerPx
73
+ }
74
+ getProjectedPoint(point) {
75
+ if (this.outline.length == 0) {
76
+ return null
77
+ }
78
+ if (isSamePoint3D(this.outline[0], this.outline[1])) {
79
+ console.error("A == B Can't project point on Line")
80
+ return null
81
+ }
82
+ let P = getPointOnLine(point, this.outline[0], this.outline[1])
83
+ return P
84
+ }
85
+ translate(vectorInMm) {
86
+ this.outline = this.outline.map((point) => {
87
+ return translate2D(point, vectorInMm)
88
+ })
89
+ }
90
+ serialize() {
91
+ if (!this.belongsTo) {
92
+ this.belongsTo = []
93
+ }
94
+ const belongsTo = this.belongsTo.map((item) => {
95
+ return {
96
+ polygon: item.polygon.serialize(),
97
+ polygonId: item.polygonId,
98
+ index: item.index
99
+ }
100
+ })
101
+ const serializedEdge={
102
+ outline: [this.outline[0], this.outline[1]],
103
+ layer: this.layer,
104
+ type: this.type,
105
+ belongsTo: belongsTo || [],
106
+ itemType: this.itemType
107
+ }
108
+ return JSON.parse(JSON.stringify(serializedEdge))
109
+ }
110
+ }
@@ -0,0 +1,57 @@
1
+
2
+ import {
3
+ getDistanceBetweenPoints,
4
+ translate2D,
5
+ } from '../geometry'
6
+ import { v4 as uuidv4 } from 'uuid'
7
+
8
+ export class Point {
9
+ constructor(x = 0, y = 0, z = 0, layer = null) {
10
+ this.id = uuidv4()
11
+ this.type = 'point'
12
+ this.version = 0
13
+ this.x = x
14
+ this.y = y
15
+ this.z = z
16
+ this.layer = layer
17
+ this.open = false
18
+ this.selected = false
19
+ this.visible = true
20
+ this.hasWarning = false
21
+ }
22
+ getPxDistanceTo(point, canvasContext) {
23
+ const toRealityRef = canvasContext.toRealityRef
24
+ const realityPoint = toRealityRef(point)
25
+ return getDistanceBetweenPoints(realityPoint, this) / canvasContext.mmPerPx
26
+ }
27
+ getProjectedPoint(point) {
28
+ return this
29
+ }
30
+ translate(vectorInMm) {
31
+ let translatedPoint = translate2D({ x: this.x, y: this.y }, vectorInMm)
32
+ this.x = translatedPoint.x
33
+ this.y = translatedPoint.y
34
+ }
35
+ serialize() {
36
+ if (!this.belongsTo) {
37
+ this.belongsTo = []
38
+ }
39
+ const belongsTo = this.belongsTo.map((item) => {
40
+ return {
41
+ polygon: item.polygon.serialize(),
42
+ polygonId: item.polygonId,
43
+ index: item.index
44
+ }
45
+ })
46
+
47
+ return {
48
+ x: this.x,
49
+ y: this.y,
50
+ z: this.z,
51
+ layer: this.layer,
52
+ type: this.type,
53
+ belongsTo: belongsTo || [],
54
+ itemType: this.itemType
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,240 @@
1
+
2
+ import {
3
+ translate2D,
4
+ getDegree
5
+ } from '../geometry'
6
+ import {
7
+ substractVector,
8
+ normalizeVector,
9
+ multiplyVector,
10
+ addVector
11
+ } from '../vector'
12
+
13
+ import { v4 as uuidv4 } from 'uuid'
14
+ import {updateComputedGeometryPolygon} from './derivedState/updateComputedGeometryPolygon'
15
+
16
+ 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.margins = {
30
+ isSameMargin: true,
31
+ sameMargin: 200,
32
+ innerOutline: [],
33
+ outterOutline: [],
34
+ distances: []
35
+ }
36
+ this.isLoading = false
37
+ if (this.layer == 'obstacle') {
38
+ this.isParallel = true
39
+ }
40
+ }
41
+
42
+ clone() {
43
+ const polygon = new Polygon()
44
+ this.outline.forEach((p) => {
45
+ polygon.outline.push({ ...p })
46
+ })
47
+ this.holes.forEach((h) => {
48
+ polygon.holes.push({ ...h })
49
+ })
50
+ polygon.id = uuidv4()
51
+ polygon.layer = this.layer
52
+ polygon.isClosed = this.isClosed
53
+ return polygon
54
+ }
55
+ getAngle(i){
56
+ const length=this.outline.length
57
+ const firstPoint = this.outline[(i-1+length) % length]
58
+ const secondPoint = this.outline[i % length]
59
+ const thirdPoint = this.outline[(i + 1) % length]
60
+ return getDegree(firstPoint, secondPoint, thirdPoint)
61
+ }
62
+ getDirectionSnapAngle(){
63
+ let angles=[]
64
+ for(let k=0;k<this.outline.length;k++){
65
+ let p_0=this.outline[k]
66
+ let p_1=this.outline[(k+1)%this.outline.length]
67
+ let angle = Math.atan2(
68
+ p_0.x - p_1.x,
69
+ p_0.y - p_1.y
70
+ )
71
+ angle*=180/Math.PI
72
+ angles.push(angle+90%360)
73
+ angles.push(angle+270%360)
74
+ }
75
+ return angles
76
+ }
77
+ getBounds(){
78
+ let bounds=this.outline.reduce((acc,cur)=>{
79
+ return{
80
+ xMin:Math.min(acc.xMin,cur.x),
81
+ yMin:Math.min(acc.yMin,cur.y),
82
+ xMax:Math.max(acc.xMax,cur.x),
83
+ yMax:Math.max(acc.yMax,cur.y),
84
+ }
85
+ },{xMin:Infinity,xMax:-Infinity,yMin:Infinity,yMax:-Infinity})
86
+ let center={
87
+ x:(bounds.xMax+bounds.xMin)/2,
88
+ y:(bounds.yMax+bounds.yMin)/2,
89
+ }
90
+ let radius=Math.hypot(bounds.xMax-bounds.xMin,bounds.yMax-bounds.yMin)/2
91
+ return {...bounds,radius,center}
92
+ }
93
+ get4pointsSquareAngle(i,canvasContext){
94
+ const length=this.outline.length
95
+ const A = this.outline[(i-1+length) % length]
96
+ const B = this.outline[i % length]
97
+ const C = this.outline[(i + 1) % length]
98
+ const toCanvasRef = canvasContext.toCanvasRef
99
+ //angle in B
100
+ const BD=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(A,B)))
101
+ const BE=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(C,B)))
102
+ const D=addVector(B,BD)
103
+ const E=addVector(B,BE)
104
+ const F=addVector(D,BE)
105
+
106
+ return [toCanvasRef(D),toCanvasRef(F),toCanvasRef(E)]
107
+ }
108
+
109
+ translate(vectorInMm) {
110
+ this.version++
111
+ this.outline = this.outline.map((point) => {
112
+ return translate2D(point, vectorInMm)
113
+ })
114
+ updateComputedGeometryPolygon(this)
115
+ }
116
+ serialize() {
117
+ const baseSerialization = {
118
+ id: this.id,
119
+ version: this.version,
120
+ name: this.name,
121
+ type: this.type,
122
+ outline: this.outline.map(p=>{return {...p}}),
123
+ holes: this.holes.map((p) => p.id),
124
+ layer: this.layer,
125
+ highlight: this.highlight,
126
+ visible: this.visible,
127
+ margins: JSON.parse(JSON.stringify(this.margins))
128
+ }
129
+ const extraSerialization = {}
130
+ if (this.layer == 'roof') {
131
+ extraSerialization.moduleFields = this.moduleFields.map((m) => {
132
+ return {
133
+ id: m.id
134
+ }
135
+ })
136
+ } else if (this.layer == 'obstacle') {
137
+ extraSerialization.isParallel = this.isParallel
138
+ extraSerialization.height = this.height
139
+ extraSerialization.roofs = this.roofs.map((roof) => {
140
+ return { id: roof.id }
141
+ })
142
+ } else if (this.layer == 'moduleField') {
143
+ extraSerialization.data = {...this.data}
144
+ extraSerialization.roof = { id: this.roof.id }
145
+ extraSerialization.pvData = this.pvData?{...this.pvData}:null
146
+ extraSerialization.mountingData = this.mountingData?{...this.mountingData}:null
147
+ extraSerialization.panels = this.panels
148
+ ? this.panels.map((p) => {
149
+ return { id: p.id, index: p.index, outline: p.outline }
150
+ })
151
+ : []
152
+ extraSerialization.userDeactivatedPanels = this.userDeactivatedPanels
153
+ ? this.userDeactivatedPanels.map((p) => {
154
+ return { id: p.id, index: p.index, outline: p.outline }
155
+ })
156
+ : []
157
+ } else if (this.layer == 'panel') {
158
+ extraSerialization.index = this.index
159
+ extraSerialization.moduleField = { id: this.moduleField.id }
160
+ } else if (this.layer == 'user_deactivated_panel') {
161
+ extraSerialization.index = this.index
162
+ extraSerialization.moduleField = { id: this.moduleField.id }
163
+ }
164
+ return JSON.parse(JSON.stringify({ ...baseSerialization, ...extraSerialization }))
165
+ }
166
+ serializeForBEStorage() {
167
+ if (this.layer == 'roof') {
168
+ const margins_roof_mm = this.margins.isSameMargin
169
+ ? Array(this.outline.length).fill(this.margins.sameMargin)
170
+ : this.margins.distances
171
+ return {
172
+ roof_uuid: this.id,
173
+ version: this.version,
174
+ roof_outline: this.outline.map((v) => [v.x, v.y, v.z]),
175
+ roof_margins_mm: margins_roof_mm,
176
+ roof_slope_degrees: this.incline,
177
+ roof_orientation_degrees: this.direction,
178
+ roof_name: this.name
179
+ }
180
+ } else if (this.layer == 'obstacle') {
181
+ const margins_obstacle_mm = this.margins.isSameMargin
182
+ ? Array(this.outline.length).fill(this.margins.sameMargin)
183
+ : this.margins.distances
184
+ return {
185
+ obstacle_uuid: this.id,
186
+ version: this.version,
187
+ obstacle_outline: this.outline.map((v) => [v.x, v.y, v.z]),
188
+ obstacle_margins_mm: margins_obstacle_mm,
189
+ obstacle_height_above_ground_mm: this.height,
190
+ obstacle_slope_is_parallel_to_roof: this.isParallel
191
+ }
192
+ } else if (['moduleField', 'tmpModuleField'].includes(this.layer)) {
193
+ if (!this.panels) {
194
+ this.panels = []
195
+ }
196
+ if (!this.userDeactivatedPanels) {
197
+ this.userDeactivatedPanels = []
198
+ }
199
+ const modules = this.panels.map((p) => {
200
+ return {
201
+ index: p.index,
202
+ outline: p.outline.map((v) => [v.x, v.y, v.z])
203
+ }
204
+ })
205
+ const user_deactivated_modules = this.userDeactivatedPanels.map((p) => {
206
+ return {
207
+ index: p.index,
208
+ outline: p.outline.map((v) => [v.x, v.y, v.z])
209
+ }
210
+ })
211
+ return {
212
+ module_field_uuid: this.id,
213
+ roof_uuid: this.roof.id,
214
+ version: this.version,
215
+ module_field_outline: this.outline.map((v) => [v.x, v.y, v.z]),
216
+ col_spacing_mm: this.data.col_spacing_mm,
217
+ row_spacing_mm: this.data.row_spacing_mm,
218
+ offset_percent: this.data.offset_percent,
219
+ panel_orientation: this.data.panel_orientation,
220
+ panel_direction_degrees: this.data.panel_direction_degrees,
221
+ component_id_pv_module: this.data.component_id_pv_module,
222
+ component_id_mounting_system: this.data.component_id_mounting_system,
223
+ modules: modules,
224
+ module_tilt_degrees: this.data.module_tilt_degrees,
225
+ user_deactivated_modules: user_deactivated_modules,
226
+ base_line_index: this.data.base_line_index,
227
+ justify_percent: this.data.justify_percent
228
+ }
229
+ } else if (['panel', 'user_deactivated_panel'].includes(this.layer)) {
230
+ return {
231
+ id: this.id,
232
+ index: this.index,
233
+ outline: this.outline.map((v) => [v.x, v.y, v.z]),
234
+ moduleField: {
235
+ id: this.moduleField.id
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ multiplyVector
3
+ } from '../../vector'
4
+ import {getMarginPoint} from '../../geometry'
5
+ //update and calculate margins for all polygon
6
+ export function updateMarginsOutline(state){
7
+ state.polygons.forEach(polygon=>{
8
+ polygon=updateMarginOutlinePolygon(polygon)
9
+ })
10
+ return state
11
+ }
12
+
13
+ export function updateMarginOutlinePolygon(polygon){
14
+ if(polygon.layer=="roof"){
15
+ return getRoofMarginOutline(polygon)
16
+ }else if(polygon.layer=='obstacle'){
17
+ return getObstacleMarginOutline(polygon)
18
+ }else{
19
+ return polygon
20
+ }
21
+ }
22
+ //FYI: polygon.margins={
23
+ // distances:array[#point] //primitives
24
+ // isSameMargin:Boolean //primitives
25
+ // sameMargin:Number //primitives
26
+ // outterOutline:array[Vector3D] //derived (for roof this is the wall)
27
+ // innerOutline:array[Vector3D] //derived (for obstacle this is the wall)
28
+ // }
29
+
30
+ export function getRoofMarginOutline(polygon){
31
+ let clockwiseNormalVector=polygon.normalVector
32
+ if(!polygon.isClockwise){
33
+ clockwiseNormalVector=multiplyVector(-1,clockwiseNormalVector)
34
+ }
35
+ //outterOutline is the outline close to the wall
36
+ polygon.margins.outterOutline=polygon.flatOutline
37
+ polygon.margins.innerOutline=[]
38
+ const length=polygon.outline.length
39
+ for(let index=0;index<length;index++){
40
+ const B=polygon.margins.outterOutline[index]
41
+ //A,B,C three consecutive points on the polygon.
42
+ //n is a vector in the plan normal to AB of length margin[A] directed towards the inside
43
+ //m is a vector in the plan normal to BC of length margin[B] directed towards the inside
44
+ //K is the margin outline linked with B
45
+ const A=polygon.margins.outterOutline[(index-1+length)%length]
46
+ const C=polygon.margins.outterOutline[(index+1)%length]
47
+ //filling the margins values if not initiated.
48
+ let marginA
49
+ let marginB
50
+ if(polygon.margins.isSameMargin){
51
+ marginA=polygon.margins.sameMargin
52
+ marginB=polygon.margins.sameMargin
53
+ polygon.margins.distances[index]=polygon.margins.sameMargin
54
+ }else{
55
+ if(!polygon.margins.distances[(index-1+length)%length]===undefined){
56
+ polygon.margins.distances[(index-1+length)%length]=polygon.margins.sameMargin
57
+ }
58
+ if(!polygon.margins.distances[index]===undefined){
59
+ polygon.margins.distances[index]=polygon.margins.sameMargin
60
+ }
61
+ marginA=polygon.margins.distances[(index-1+length)%length]
62
+ marginB=polygon.margins.distances[index]
63
+ }
64
+ let K=getMarginPoint(A,B,C,marginA,marginB,clockwiseNormalVector)
65
+ polygon.margins.innerOutline.push(K)
66
+ }
67
+ return polygon
68
+ }
69
+
70
+
71
+ export function getObstacleMarginOutline(polygon){
72
+ let clockwiseNormalVector=polygon.normalVector
73
+ if(polygon.isClockwise){
74
+ clockwiseNormalVector=multiplyVector(-1,clockwiseNormalVector)
75
+ }
76
+ //outterOutline is the outline close to the wall
77
+ polygon.margins.innerOutline=polygon.flatOutline
78
+ polygon.margins.outterOutline=[]
79
+ const length=polygon.margins.innerOutline.length
80
+ for(let index=0;index<length;index++){
81
+ const B=polygon.margins.innerOutline[index]
82
+ //A,B,C three consecutive points on the polygon.
83
+ //n is a vector in the plan normal to AB of length margin[A] directed towards the inside
84
+ //m is a vector in the plan normal to BC of length margin[B] directed towards the inside
85
+ //K is the margin outline linked with B
86
+ const A=polygon.margins.innerOutline[(index-1+length)%length]
87
+ const C=polygon.margins.innerOutline[(index+1)%length]
88
+ //filling the margins values if not initiated.
89
+ let marginA
90
+ let marginB
91
+ if(polygon.margins.isSameMargin){
92
+ marginA=polygon.margins.sameMargin
93
+ marginB=polygon.margins.sameMargin
94
+ polygon.margins.distances[index]=polygon.margins.sameMargin
95
+ }else{
96
+ if(polygon.margins.distances[(index-1+length)%length]===undefined){
97
+ polygon.margins.distances[(index-1+length)%length]=polygon.margins.sameMargin
98
+ }
99
+ if(polygon.margins.distances[index]===undefined){
100
+ polygon.margins.distances[index]=polygon.margins.sameMargin
101
+ }
102
+ marginA=polygon.margins.distances[(index-1+length)%length]
103
+ marginB=polygon.margins.distances[index]
104
+ }
105
+ let K=getMarginPoint(A,B,C,marginA,marginB,clockwiseNormalVector)
106
+ polygon.margins.outterOutline.push(K)
107
+ }
108
+ return polygon
109
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ isInsidePolygon,
3
+ verticalProjectionOnPlane,
4
+ } from '../../geometry'
5
+ //this function just update the "roof" and "moduleField(s)" properties of panels,moduleFields and roofs
6
+ export function UpdateRoofModuleFieldRelations(state) {
7
+ const roofPolygons = state.polygons.filter((poly) => poly.layer == 'roof')
8
+ const moduleFieldPolygons = state.polygons.filter(
9
+ (poly) => poly.layer == 'moduleField'
10
+ )
11
+ const panelPolygons = state.polygons.filter((poly) =>
12
+ ['panel', 'user_deactivated_panel'].includes(poly.layer)
13
+ )
14
+ //let's remove all moduleFields from roofs
15
+ roofPolygons.forEach((roofPolygon) => {
16
+ //breaking circular reference for memory management
17
+ for (let k in roofPolygon.moduleFields) {
18
+ roofPolygon.moduleFields[k] = null
19
+ }
20
+ roofPolygon.moduleFields = []
21
+ })
22
+ moduleFieldPolygons.forEach((moduleFieldPolygon) => {
23
+ moduleFieldPolygon.roof = null
24
+ })
25
+ panelPolygons.forEach((panelPolygon) => {
26
+ panelPolygon.roof = null
27
+ })
28
+
29
+ for (let k in moduleFieldPolygons) {
30
+ const moduleField = moduleFieldPolygons[k]
31
+ //check if everypoint is in the same roof and if they are not too far from the flat surface.(at least above)
32
+ const roofPolygonCandidates = roofPolygons.filter((roof) =>
33
+ {
34
+ return moduleField.outline.every(p=>isInsidePolygon(p, roof.margins.innerOutline))
35
+ }
36
+ )
37
+ //roofs on roof can lead to multiple roof under a point. moduleField is in the smallest
38
+ let smallestRoof = null
39
+ for (let roof of roofPolygonCandidates) {
40
+ if (!smallestRoof || smallestRoof.area > roof.area) {
41
+ smallestRoof = roof
42
+ }
43
+ }
44
+ let roofPolygon = smallestRoof
45
+
46
+ if (!roofPolygon) {
47
+ //no roof found under first node of moduleField => moduleField will be deleted
48
+ continue
49
+ }
50
+ let moduleFieldBelongsToRoof = true
51
+ moduleField.outline.forEach((p) => {
52
+ const distanceToFlatRoof =
53
+ p.z -
54
+ verticalProjectionOnPlane(
55
+ p,
56
+ roofPolygon.normalVector,
57
+ roofPolygon.flatOutline[0]
58
+ ).z
59
+ if (
60
+ !isInsidePolygon(p, roofPolygon.margins.innerOutline) ||
61
+ distanceToFlatRoof < -1 ||
62
+ distanceToFlatRoof > 50
63
+ ) {
64
+ // one point from moduleField is not inside the roof or too far from it's flat surface => moduleField will be deleted
65
+ moduleFieldBelongsToRoof = false
66
+ }
67
+ })
68
+ if (moduleFieldBelongsToRoof) {
69
+ roofPolygon.moduleFields.push(moduleField)
70
+ moduleField.roof = roofPolygon
71
+ moduleField.panels.forEach((panel) => (panel.roof = roofPolygon))
72
+ panelPolygons.forEach((p) => {
73
+ if (p.moduleField.id == moduleField.id) {
74
+ p.roof = roofPolygon
75
+ }
76
+ })
77
+ }
78
+ }
79
+
80
+ let newPolygons = state.polygons.map((p) => {
81
+ if (
82
+ !['roof', 'moduleField', 'panel', 'user_deactivated_panel'].includes(
83
+ p.layer
84
+ )
85
+ )
86
+ return p
87
+ if (p.layer == 'roof') return roofPolygons.find((r) => r.id == p.id)
88
+ if (p.layer == 'moduleField')
89
+ return moduleFieldPolygons.find((mf) => mf.id == p.id)
90
+ if (['panel', 'user_deactivated_panel'].includes(p.layer))
91
+ return panelPolygons.find((panel) => panel.id == p.id)
92
+ })
93
+ state = { ...state, polygons: newPolygons }
94
+ state = removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state)
95
+ return state
96
+ }
97
+
98
+ export function removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state) {
99
+ const remainingPolygons = state.polygons.filter((polygon) =>
100
+ ['roof', 'obstacle'].includes(polygon.layer)
101
+ )
102
+ const remainingModuleFields = state.polygons.filter(
103
+ (polygon) => polygon.layer == 'moduleField' && !!polygon.roof
104
+ )
105
+ const remainingPanels = state.polygons.filter(
106
+ (polygon) => polygon.layer == 'panel' && !!polygon.roof
107
+ )
108
+ const remainingUserDeactivatedPanels = state.polygons.filter(
109
+ (polygon) => polygon.layer == 'user_deactivated_panel' && !!polygon.roof
110
+ )
111
+
112
+ state.polygons = [
113
+ ...remainingPolygons,
114
+ ...remainingModuleFields,
115
+ ...remainingPanels,
116
+ ...remainingUserDeactivatedPanels
117
+ ]
118
+ return state
119
+ }