blue-chestnut-solar-expert 0.0.67 → 0.0.69

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.
Files changed (90) hide show
  1. package/dist/cjs/eraser-icon_19.cjs.entry.js +1 -1
  2. package/dist/cjs/{lerc-v64rYVDy.js → lerc-Bdx5y-yw.js} +3 -3
  3. package/dist/cjs/{lerc-v64rYVDy.js.map → lerc-Bdx5y-yw.js.map} +1 -1
  4. package/dist/cjs/loader.cjs.js +1 -1
  5. package/dist/cjs/loading-widget.cjs.entry.js +1 -1
  6. package/dist/cjs/stencil-library.cjs.js +1 -1
  7. package/dist/collection/components/map-draw/map-draw.js +54 -25
  8. package/dist/collection/components/map-draw/map-draw.js.map +1 -1
  9. package/dist/collection/components/map-draw/map-selector.js +3 -3
  10. package/dist/collection/components/map-draw/polygon-buttons.js +1 -1
  11. package/dist/collection/components/map-draw/polygon-information.js +2 -2
  12. package/dist/collection/components/map-draw/tool-box.js +3 -3
  13. package/dist/collection/components/map-draw/tutorial-component.js +72 -4
  14. package/dist/collection/components/map-draw/tutorial-component.js.map +1 -1
  15. package/dist/collection/components/settings/settings.js +8 -8
  16. package/dist/collection/components/solar-expert/solar-expert.js +1 -1
  17. package/dist/collection/components/solar-system-form/solar-system-form.js +57 -36
  18. package/dist/collection/components/solar-system-form/solar-system-form.js.map +1 -1
  19. package/dist/collection/components/widgets/loading-widget.js +1 -1
  20. package/dist/collection/types/lang.js.map +1 -1
  21. package/dist/collection/utils/api.js +3 -2
  22. package/dist/collection/utils/api.js.map +1 -1
  23. package/dist/collection/utils/lang/english.js +7 -0
  24. package/dist/collection/utils/lang/english.js.map +1 -1
  25. package/dist/collection/utils/lang/german.js +7 -0
  26. package/dist/collection/utils/lang/german.js.map +1 -1
  27. package/dist/collection/utils/lang/spanish.js +7 -0
  28. package/dist/collection/utils/lang/spanish.js.map +1 -1
  29. package/dist/collection/utils/solar.js +0 -1
  30. package/dist/collection/utils/solar.js.map +1 -1
  31. package/dist/components/loading-widget.js +1 -1
  32. package/dist/components/map-draw.js +1 -1
  33. package/dist/components/map-selector.js +1 -1
  34. package/dist/components/{p-i1uLweD0.js → p-26DHwbCE.js} +4 -4
  35. package/dist/components/{p-i1uLweD0.js.map → p-26DHwbCE.js.map} +1 -1
  36. package/dist/components/{p-DlNHVTGB.js → p-BOe-Z8rz.js} +1462 -1088
  37. package/dist/components/p-BOe-Z8rz.js.map +1 -0
  38. package/dist/components/{p-Bopprtc7.js → p-Bduzzygj.js} +6 -6
  39. package/dist/components/{p-Bopprtc7.js.map → p-Bduzzygj.js.map} +1 -1
  40. package/dist/components/p-C3ZXE525.js +855 -0
  41. package/dist/components/p-C3ZXE525.js.map +1 -0
  42. package/dist/components/{p-DWirjxpO.js → p-Cx7fffWb.js} +6 -6
  43. package/dist/components/{p-DWirjxpO.js.map → p-Cx7fffWb.js.map} +1 -1
  44. package/dist/components/{p-Dzl6kfPI.js → p-D2AHNjbG.js} +39 -7
  45. package/dist/components/p-D2AHNjbG.js.map +1 -0
  46. package/dist/components/{p-BcVa4_YP.js → p-DLWzgdrw.js} +3 -3
  47. package/dist/components/{p-BcVa4_YP.js.map → p-DLWzgdrw.js.map} +1 -1
  48. package/dist/components/{p-B4X-RCW0.js → p-Ddk3b30j.js} +11 -11
  49. package/dist/components/{p-B4X-RCW0.js.map → p-Ddk3b30j.js.map} +1 -1
  50. package/dist/components/{p-eDwaXClX.js → p-DfzSejIb.js} +23 -2
  51. package/dist/components/p-DfzSejIb.js.map +1 -0
  52. package/dist/components/{p-BFJHTJPM.js → p-OxUYjaAL.js} +6 -6
  53. package/dist/components/{p-BFJHTJPM.js.map → p-OxUYjaAL.js.map} +1 -1
  54. package/dist/components/polygon-buttons.js +1 -1
  55. package/dist/components/polygon-information.js +1 -1
  56. package/dist/components/settings-modal.js +1 -1
  57. package/dist/components/solar-expert.js +10 -10
  58. package/dist/components/solar-system-form.js +1 -1
  59. package/dist/components/tool-box.js +1 -1
  60. package/dist/components/tutorial-component.js +1 -1
  61. package/dist/esm/eraser-icon_19.entry.js +1 -1
  62. package/dist/esm/{lerc-CAiDQjTu.js → lerc-DF5Lrv5A.js} +3 -3
  63. package/dist/esm/{lerc-CAiDQjTu.js.map → lerc-DF5Lrv5A.js.map} +1 -1
  64. package/dist/esm/loader.js +1 -1
  65. package/dist/esm/loading-widget.entry.js +1 -1
  66. package/dist/esm/stencil-library.js +1 -1
  67. package/dist/stencil-library/assets/tutorial4.mp4 +0 -0
  68. package/dist/stencil-library/{p-ab0f2031.entry.js → p-9b4e52c3.entry.js} +2 -2
  69. package/dist/stencil-library/p-CymYGBJH.js +2 -0
  70. package/dist/stencil-library/p-CymYGBJH.js.map +1 -0
  71. package/dist/stencil-library/{p-cAuhy_jT.js → p-DHgjDQwz.js} +4 -4
  72. package/dist/stencil-library/{p-cAuhy_jT.js.map → p-DHgjDQwz.js.map} +1 -1
  73. package/dist/stencil-library/{p-e7b94dbb.entry.js → p-ba64c5e4.entry.js} +2 -2
  74. package/dist/stencil-library/stencil-library.esm.js +1 -1
  75. package/dist/types/components/map-draw/map-draw.d.ts +5 -0
  76. package/dist/types/components/map-draw/tutorial-component.d.ts +7 -0
  77. package/dist/types/components/solar-system-form/solar-system-form.d.ts +1 -0
  78. package/dist/types/components.d.ts +6 -0
  79. package/dist/types/types/lang.d.ts +7 -0
  80. package/dist/types/utils/api.d.ts +1 -0
  81. package/package.json +1 -1
  82. package/dist/components/p-DlNHVTGB.js.map +0 -1
  83. package/dist/components/p-Dzl6kfPI.js.map +0 -1
  84. package/dist/components/p-PkMjF2if.js +0 -1985
  85. package/dist/components/p-PkMjF2if.js.map +0 -1
  86. package/dist/components/p-eDwaXClX.js.map +0 -1
  87. package/dist/stencil-library/p-BBtmpSUK.js +0 -2
  88. package/dist/stencil-library/p-BBtmpSUK.js.map +0 -1
  89. /package/dist/stencil-library/{p-ab0f2031.entry.js.map → p-9b4e52c3.entry.js.map} +0 -0
  90. /package/dist/stencil-library/{p-e7b94dbb.entry.js.map → p-ba64c5e4.entry.js.map} +0 -0
@@ -1,24 +1,472 @@
1
1
 
2
2
  if (typeof global === "undefined") { var global = globalThis || window || self; }
3
3
  import { p as proxyCustomElement, H, h, getAssetPath } from './index.js';
4
- import { r as requireDist$1, d as defineCustomElement$5, a as DEFAULT_SOLAR_EXPERT_CONFIG, b as DEFAULT_SOLAR_PANEL_TYPE, c as distExports$1, B as BORDER_INSET } from './p-PkMjF2if.js';
5
- import { d as defineCustomElement$3, r as roofTool, m as moveTool, a as markRoofEdgeTool, o as obstructionTool } from './p-DWirjxpO.js';
6
- import { d as defineCustomElement$9, g as getPixelInMeters } from './p-BFJHTJPM.js';
7
- import { s as state, o as onChange, g as getLanguageStrings } from './p-eDwaXClX.js';
4
+ import { d as defineCustomElement$3, r as roofTool, m as moveTool, a as markRoofEdgeTool, o as obstructionTool } from './p-Cx7fffWb.js';
5
+ import { d as defineCustomElement$5, a as DEFAULT_SOLAR_EXPERT_CONFIG, b as DEFAULT_SOLAR_PANEL_TYPE, B as BORDER_INSET } from './p-C3ZXE525.js';
6
+ import { d as defineCustomElement$9, g as getPixelInMeters } from './p-OxUYjaAL.js';
7
+ import { s as state, o as onChange, g as getLanguageStrings } from './p-DfzSejIb.js';
8
8
  import { d as defineCustomElement$g } from './p-CL74Q4VR.js';
9
9
  import { d as defineCustomElement$f } from './p-D8w3bTPO.js';
10
10
  import { d as defineCustomElement$e } from './p-C5QieOat.js';
11
11
  import { d as defineCustomElement$d } from './p-Umz6nJIv.js';
12
12
  import { d as defineCustomElement$c } from './p-FdEV2qPo.js';
13
13
  import { d as defineCustomElement$b } from './p-BJLO76Yi.js';
14
- import { d as defineCustomElement$a } from './p-i1uLweD0.js';
14
+ import { d as defineCustomElement$a } from './p-26DHwbCE.js';
15
15
  import { d as defineCustomElement$8 } from './p-DBwr8xSB.js';
16
16
  import { d as defineCustomElement$7 } from './p-DTXeHbuh.js';
17
- import { d as defineCustomElement$6 } from './p-B4X-RCW0.js';
17
+ import { d as defineCustomElement$6 } from './p-Ddk3b30j.js';
18
18
  import { d as defineCustomElement$4 } from './p-P28NBglk.js';
19
- import { d as defineCustomElement$2 } from './p-Dzl6kfPI.js';
19
+ import { d as defineCustomElement$2 } from './p-D2AHNjbG.js';
20
20
  import { d as defineCustomElement$1 } from './p-waOPoUcA.js';
21
21
 
22
+ class Point {
23
+ constructor(x, y) {
24
+ this.x = x;
25
+ this.y = y;
26
+ }
27
+ equals(other) {
28
+ return this.x === other.x && this.y === other.y;
29
+ }
30
+ }
31
+ var PolygonType;
32
+ (function (PolygonType) {
33
+ PolygonType["ROOF"] = "roof";
34
+ PolygonType["OBSTRUCTION"] = "obstruction";
35
+ })(PolygonType || (PolygonType = {}));
36
+
37
+ function isPointInPolygon(point, polygon) {
38
+ if (!polygon.closed || polygon.points.length < 3)
39
+ return false;
40
+ const num_vertices = polygon.points.length;
41
+ const x = point.x;
42
+ const y = point.y;
43
+ let inside = false;
44
+ let p1 = polygon.points[0];
45
+ let p2;
46
+ for (let i = 1; i <= num_vertices; i++) {
47
+ p2 = polygon.points[i % num_vertices];
48
+ if (y > Math.min(p1.y, p2.y)) {
49
+ if (y <= Math.max(p1.y, p2.y)) {
50
+ if (x <= Math.max(p1.x, p2.x)) {
51
+ const x_intersection = ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
52
+ if (p1.x === p2.x || x <= x_intersection) {
53
+ inside = !inside;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ p1 = p2;
59
+ }
60
+ return inside;
61
+ }
62
+
63
+ async function fetchSolarData(latitude, longitude, apiKey) {
64
+ if (latitude === 0 || longitude === 0) {
65
+ return null;
66
+ }
67
+ try {
68
+ const args = {
69
+ "location.latitude": latitude.toFixed(5),
70
+ "location.longitude": longitude.toFixed(5),
71
+ };
72
+ const params = new URLSearchParams({
73
+ ...args,
74
+ key: apiKey,
75
+ });
76
+ const response = await fetch(`https://solar.googleapis.com/v1/buildingInsights:findClosest?${params}`);
77
+ if (!response.ok) {
78
+ const errorText = await response.text();
79
+ throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`);
80
+ }
81
+ const contentType = response.headers.get("content-type");
82
+ if (!contentType || !contentType.includes("application/json")) {
83
+ const text = await response.text();
84
+ console.error("Unexpected content type:", contentType, "Response:", text);
85
+ return null;
86
+ }
87
+ const rawData = await response.json();
88
+ return rawData;
89
+ }
90
+ catch (error) {
91
+ console.error("Error fetching solar data:", error);
92
+ if (error instanceof TypeError &&
93
+ error.message === "Failed to fetch") {
94
+ throw new Error("Unable to connect to the solar data API.");
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ function latLngToPixel(bounds, canvas, latLng) {
101
+ const ctx = canvas.getContext("2d");
102
+ if (!ctx) {
103
+ throw new Error("Canvas context not found");
104
+ }
105
+ const latToPixel = (lat) => {
106
+ return canvas.height *
107
+ (1 - (lat - bounds.south) / (bounds.north - bounds.south));
108
+ };
109
+ const lngToPixel = (lng) => {
110
+ return canvas.width * (lng - bounds.west) / (bounds.east - bounds.west);
111
+ };
112
+ return new Point(lngToPixel(latLng.longitude), latToPixel(latLng.latitude));
113
+ }
114
+
115
+ function projectPoint(point, azimuth) {
116
+ const angle = azimuth * (Math.PI / 180);
117
+ const x = point.x * Math.cos(angle) - point.y * Math.sin(angle);
118
+ const y = point.x * Math.sin(angle) + point.y * Math.cos(angle);
119
+ return new Point(x, y);
120
+ }
121
+ function projectPolygon(polygon, azimuth) {
122
+ return {
123
+ points: polygon.points.map((point) => projectPoint(point, azimuth)),
124
+ type: polygon.type,
125
+ closed: polygon.closed,
126
+ id: polygon.id,
127
+ };
128
+ }
129
+ function intersects(line1, line2) {
130
+ const p1 = line1.start;
131
+ const p2 = line1.end;
132
+ const p3 = line2.start;
133
+ const p4 = line2.end;
134
+ if (p1.equals(p3) ||
135
+ p1.equals(p4) ||
136
+ p2.equals(p3) ||
137
+ p2.equals(p4)) {
138
+ return false;
139
+ }
140
+ const d1 = (p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x);
141
+ const d2 = (p4.x - p3.x) * (p2.y - p3.y) - (p4.y - p3.y) * (p2.x - p3.x);
142
+ const d3 = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
143
+ const d4 = (p2.x - p1.x) * (p4.y - p1.y) - (p2.y - p1.y) * (p4.x - p1.x);
144
+ return (d1 * d2 < 0) && (d3 * d4 < 0);
145
+ }
146
+ function offsetPolygon(polygon, offset) {
147
+ if (polygon.points.length === 0) {
148
+ return { polygon: polygon, offset: { x: 0, y: 0 } };
149
+ }
150
+ let offsetX = 0;
151
+ let offsetY = 0;
152
+ if (!offset) {
153
+ const minX = Math.min(...polygon.points.map((point) => point.x));
154
+ const minY = Math.min(...polygon.points.map((point) => point.y));
155
+ const maxX = Math.max(...polygon.points.map((point) => point.x));
156
+ const maxY = Math.max(...polygon.points.map((point) => point.y));
157
+ offsetX = minX + (maxX - minX) / 2;
158
+ offsetY = minY + (maxY - minY) / 2;
159
+ }
160
+ else {
161
+ offsetX = offset.x;
162
+ offsetY = offset.y;
163
+ }
164
+ const newPoints = polygon.points.map((point) => new Point(point.x - offsetX, point.y - offsetY));
165
+ return {
166
+ polygon: { ...polygon, points: newPoints },
167
+ offset: { x: offsetX, y: offsetY },
168
+ };
169
+ }
170
+ function getBestFittingRoofSegment(roof, roofSegmentStats, bounds, canvas) {
171
+ const polygonBounds = {
172
+ minX: Math.min(...roof.points.map((p) => p.x)),
173
+ maxX: Math.max(...roof.points.map((p) => p.x)),
174
+ minY: Math.min(...roof.points.map((p) => p.y)),
175
+ maxY: Math.max(...roof.points.map((p) => p.y)),
176
+ };
177
+ let bestSegment = null;
178
+ let bestIoU = 0;
179
+ for (const segment of roofSegmentStats) {
180
+ const sw = latLngToPixel(bounds, canvas, {
181
+ latitude: segment.boundingBox.sw.latitude,
182
+ longitude: segment.boundingBox.sw.longitude,
183
+ });
184
+ const ne = latLngToPixel(bounds, canvas, {
185
+ latitude: segment.boundingBox.ne.latitude,
186
+ longitude: segment.boundingBox.ne.longitude,
187
+ });
188
+ const segmentBounds = {
189
+ minX: sw.x,
190
+ maxX: ne.x,
191
+ minY: ne.y,
192
+ maxY: sw.y,
193
+ };
194
+ const interLeft = Math.max(polygonBounds.minX, segmentBounds.minX);
195
+ const interRight = Math.min(polygonBounds.maxX, segmentBounds.maxX);
196
+ const interTop = Math.max(polygonBounds.minY, segmentBounds.minY);
197
+ const interBottom = Math.min(polygonBounds.maxY, segmentBounds.maxY);
198
+ const interWidth = interRight - interLeft;
199
+ const interHeight = interBottom - interTop;
200
+ let intersectionArea = 0;
201
+ if (interWidth > 0 && interHeight > 0) {
202
+ intersectionArea = interWidth * interHeight;
203
+ }
204
+ const polygonArea = (polygonBounds.maxX - polygonBounds.minX) *
205
+ (polygonBounds.maxY - polygonBounds.minY);
206
+ const segmentArea = (segmentBounds.maxX - segmentBounds.minX) *
207
+ (segmentBounds.maxY - segmentBounds.minY);
208
+ const unionArea = polygonArea + segmentArea - intersectionArea;
209
+ const iou = unionArea > 0 ? intersectionArea / unionArea : 0;
210
+ if (iou > bestIoU) {
211
+ bestIoU = iou;
212
+ bestSegment = segment;
213
+ }
214
+ }
215
+ return bestSegment;
216
+ }
217
+
218
+ function generateGrid(minMax, solarPanel, horizontal, anker, rowSpacing, columnSpacing) {
219
+ const grid = [];
220
+ const width = horizontal ? solarPanel.heightMeters : solarPanel.widthMeters;
221
+ const widthCount = Math.ceil((minMax.maxX - minMax.minX) / width);
222
+ const height = horizontal
223
+ ? solarPanel.widthMeters
224
+ : solarPanel.heightMeters;
225
+ const heightCount = Math.ceil((minMax.maxY - minMax.minY) / height);
226
+ const widthOffset = width / 2;
227
+ const heightOffset = height / 2;
228
+ const startX = anker.x -
229
+ (Math.ceil((anker.x - minMax.minX) / width) * width);
230
+ const startY = anker.y -
231
+ (Math.ceil((anker.y - minMax.minY) / height) * height);
232
+ for (let i = 0; i < heightCount; i++) {
233
+ const row = [];
234
+ for (let j = 0; j < widthCount; j++) {
235
+ const x = startX + j * width + j * columnSpacing + widthOffset;
236
+ const y = startY + i * height + i * rowSpacing + heightOffset;
237
+ const positionedSolarPanel = {
238
+ panel: solarPanel,
239
+ pixelPosition: new Point(x, y),
240
+ horizontal: horizontal,
241
+ };
242
+ row.push(positionedSolarPanel);
243
+ }
244
+ grid.push(row);
245
+ }
246
+ return grid;
247
+ }
248
+ function getSolarPanelLines(positionedSolarPanel, horizontal) {
249
+ const width = horizontal
250
+ ? positionedSolarPanel.panel.heightMeters
251
+ : positionedSolarPanel.panel.widthMeters;
252
+ const height = horizontal
253
+ ? positionedSolarPanel.panel.widthMeters
254
+ : positionedSolarPanel.panel.heightMeters;
255
+ const p1 = new Point(positionedSolarPanel.pixelPosition.x - width / 2, positionedSolarPanel.pixelPosition.y - height / 2);
256
+ const p2 = new Point(positionedSolarPanel.pixelPosition.x - width / 2, positionedSolarPanel.pixelPosition.y + height / 2);
257
+ const p3 = new Point(positionedSolarPanel.pixelPosition.x + width / 2, positionedSolarPanel.pixelPosition.y + height / 2);
258
+ const p4 = new Point(positionedSolarPanel.pixelPosition.x + width / 2, positionedSolarPanel.pixelPosition.y - height / 2);
259
+ const lines = [
260
+ {
261
+ start: p1,
262
+ end: p2,
263
+ },
264
+ {
265
+ start: p2,
266
+ end: p3,
267
+ },
268
+ {
269
+ start: p3,
270
+ end: p4,
271
+ },
272
+ {
273
+ start: p4,
274
+ end: p1,
275
+ },
276
+ ];
277
+ return lines;
278
+ }
279
+ function solarPanelInObstacle(positionedSolarPanel, obstacle, horizontal) {
280
+ const lines = getSolarPanelLines(positionedSolarPanel, horizontal);
281
+ if (isPointInPolygon(positionedSolarPanel.pixelPosition, obstacle)) {
282
+ return true;
283
+ }
284
+ for (let i = 0; i < obstacle.points.length; i++) {
285
+ const point = obstacle.points[i];
286
+ const nextPoint = obstacle.points[(i + 1) % obstacle.points.length];
287
+ const border = {
288
+ start: point,
289
+ end: nextPoint,
290
+ };
291
+ for (const line of lines) {
292
+ if (intersects(line, border)) {
293
+ return true;
294
+ }
295
+ }
296
+ }
297
+ return false;
298
+ }
299
+ function solarPanelInPolygon(positionedSolarPanel, polygon, horizontal) {
300
+ const lines = getSolarPanelLines(positionedSolarPanel, horizontal);
301
+ for (let i = 0; i < polygon.points.length; i++) {
302
+ const point = polygon.points[i];
303
+ const nextPoint = polygon.points[(i + 1) % polygon.points.length];
304
+ const border = {
305
+ start: point,
306
+ end: nextPoint,
307
+ };
308
+ for (const line of lines) {
309
+ if (intersects(line, border)) {
310
+ return false;
311
+ }
312
+ }
313
+ }
314
+ return isPointInPolygon(positionedSolarPanel.pixelPosition, polygon);
315
+ }
316
+ function isPanelObstructed(positionedSolarPanel, obstacles, horizontal) {
317
+ for (const obstacle of obstacles) {
318
+ if (solarPanelInObstacle(positionedSolarPanel, obstacle.polygon, horizontal)) {
319
+ return true;
320
+ }
321
+ }
322
+ return false;
323
+ }
324
+ function getSolarPanelsInPolygon(grid, polygon, horizontal, obstacles) {
325
+ const solarPanels = [];
326
+ for (const row of grid) {
327
+ let newRow = [];
328
+ for (const positionedSolarPanel of row) {
329
+ const isObstructed = isPanelObstructed(positionedSolarPanel, obstacles, horizontal);
330
+ if (solarPanelInPolygon(positionedSolarPanel, polygon, horizontal) && !isObstructed) {
331
+ newRow.push(positionedSolarPanel);
332
+ }
333
+ }
334
+ solarPanels.push(newRow);
335
+ }
336
+ return solarPanels;
337
+ }
338
+ function getOptimalSolarPosition(roof, obstacles, solarPanel, angle, columnSpacing, rowSpacing) {
339
+ const minX = Math.min(...roof.points.map((p) => p.x));
340
+ const maxX = Math.max(...roof.points.map((p) => p.x));
341
+ const minY = Math.min(...roof.points.map((p) => p.y));
342
+ const maxY = Math.max(...roof.points.map((p) => p.y));
343
+ const minMax = {
344
+ minX: minX,
345
+ maxX: maxX,
346
+ minY: minY,
347
+ maxY: maxY,
348
+ };
349
+ const verticalSolarPanel = {
350
+ ...solarPanel,
351
+ widthMeters: solarPanel.widthMeters,
352
+ heightMeters: solarPanel.heightMeters * Math.cos(angle * Math.PI / 180),
353
+ };
354
+ let solarPanels = [];
355
+ let numberOfPanels = 0;
356
+ for (const point of roof.points) {
357
+ for (const horizontal of [true]) {
358
+ const grid = generateGrid(minMax, verticalSolarPanel, horizontal, point, rowSpacing, columnSpacing);
359
+ const solarPanelsInPolygon = getSolarPanelsInPolygon(grid, roof, horizontal, obstacles);
360
+ const numberOfPanelsInPolygon = solarPanelsInPolygon.reduce((acc, row) => acc + row.length, 0);
361
+ if (numberOfPanelsInPolygon > numberOfPanels) {
362
+ numberOfPanels = numberOfPanelsInPolygon;
363
+ solarPanels = solarPanelsInPolygon;
364
+ }
365
+ }
366
+ break;
367
+ }
368
+ return solarPanels;
369
+ }
370
+ function projectObstacles(obstacles, azimuth, offset, inset) {
371
+ const result = [];
372
+ for (const obstacle of obstacles) {
373
+ const offsetObstacle = offsetPolygon(obstacle, offset);
374
+ const projectedObstacle = projectPolygon(offsetObstacle.polygon, -azimuth);
375
+ const insetObstacle = insetPolygon(projectedObstacle, -inset);
376
+ const minX = Math.min(...insetObstacle.points.map((p) => p.x));
377
+ const maxX = Math.max(...insetObstacle.points.map((p) => p.x));
378
+ const minY = Math.min(...insetObstacle.points.map((p) => p.y));
379
+ const maxY = Math.max(...insetObstacle.points.map((p) => p.y));
380
+ const boundingBox = {
381
+ minX: minX,
382
+ maxX: maxX,
383
+ minY: minY,
384
+ maxY: maxY,
385
+ };
386
+ result.push({
387
+ polygon: projectedObstacle,
388
+ boundingBox: boundingBox,
389
+ });
390
+ }
391
+ return result;
392
+ }
393
+ function isClockwise(points) {
394
+ let sum = 0;
395
+ for (let i = 0; i < points.length; i++) {
396
+ const curr = points[i];
397
+ const next = points[(i + 1) % points.length];
398
+ sum += (next.x - curr.x) * (next.y + curr.y);
399
+ }
400
+ return sum > 0;
401
+ }
402
+ function insetPolygon(polygon, inset) {
403
+ const points = polygon.points;
404
+ const isClockwisePolygon = isClockwise(points);
405
+ inset = inset * Math.sqrt(2) * (isClockwisePolygon ? -1 : 1);
406
+ const insetPoints = [];
407
+ for (let i = 0; i < points.length; i++) {
408
+ const prev = points[(i - 1 + points.length) % points.length];
409
+ const curr = points[i];
410
+ const next = points[(i + 1) % points.length];
411
+ const v1 = {
412
+ x: curr.x - prev.x,
413
+ y: curr.y - prev.y,
414
+ };
415
+ const v2 = {
416
+ x: next.x - curr.x,
417
+ y: next.y - curr.y,
418
+ };
419
+ const perp1 = {
420
+ x: -v1.y,
421
+ y: v1.x,
422
+ };
423
+ const perp2 = {
424
+ x: -v2.y,
425
+ y: v2.x,
426
+ };
427
+ const length1 = Math.sqrt(perp1.x * perp1.x + perp1.y * perp1.y);
428
+ const length2 = Math.sqrt(perp2.x * perp2.x + perp2.y * perp2.y);
429
+ perp1.x /= length1;
430
+ perp1.y /= length1;
431
+ perp2.x /= length2;
432
+ perp2.y /= length2;
433
+ const bisector = {
434
+ x: perp1.x + perp2.x,
435
+ y: perp1.y + perp2.y,
436
+ };
437
+ const bisectorLength = Math.sqrt(bisector.x * bisector.x + bisector.y * bisector.y);
438
+ bisector.x /= bisectorLength;
439
+ bisector.y /= bisectorLength;
440
+ const insetPoint = new Point(curr.x + bisector.x * inset, curr.y + bisector.y * inset);
441
+ insetPoints.push(insetPoint);
442
+ }
443
+ return {
444
+ points: insetPoints,
445
+ type: polygon.type,
446
+ closed: polygon.closed,
447
+ id: polygon.id,
448
+ area: polygon.area,
449
+ };
450
+ }
451
+ function getOptimalSolarPositionFully(roof, obstacles, solarPanel, azimuth, inset, angle, columnSpacing, rowSpacing) {
452
+ const insetRoof = insetPolygon(roof, inset);
453
+ const offset = offsetPolygon(insetRoof);
454
+ const projectedOffset = projectPolygon(offset.polygon, -azimuth);
455
+ const projectedObstacles = projectObstacles(obstacles, azimuth, offset.offset, inset);
456
+ const horizontalSolarPanels = getOptimalSolarPosition(projectedOffset, projectedObstacles, solarPanel, angle, columnSpacing, rowSpacing);
457
+ const unprojectedPanels = [];
458
+ for (const panel of horizontalSolarPanels.flat()) {
459
+ const unprojectedPanel = projectPoint(panel.pixelPosition, azimuth);
460
+ const offsetPosition = new Point(unprojectedPanel.x + offset.offset.x, unprojectedPanel.y + offset.offset.y);
461
+ unprojectedPanels.push({
462
+ panel: panel.panel,
463
+ pixelPosition: offsetPosition,
464
+ horizontal: panel.horizontal,
465
+ });
466
+ }
467
+ return unprojectedPanels;
468
+ }
469
+
22
470
  const THIS_IS_NOT_AN_OBJECT = "This is not an object";
23
471
  const THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT = "This is not a Float16Array object";
24
472
  const THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY =
@@ -2515,7 +2963,7 @@ addDecoder(6, () => {
2515
2963
  addDecoder(7, () => import('./p-BGoSj_DR.js').then((m) => m.default));
2516
2964
  addDecoder([8, 32946], () => import('./p-B54Ul0nf.js').then((m) => m.default));
2517
2965
  addDecoder(32773, () => import('./p-ByX6QP-E.js').then((m) => m.default));
2518
- addDecoder(34887, () => import('./p-BcVa4_YP.js')
2966
+ addDecoder(34887, () => import('./p-DLWzgdrw.js')
2519
2967
  .then(async (m) => {
2520
2968
  await m.zstd.init();
2521
2969
  return m;
@@ -14095,7 +14543,6 @@ async function downloadGeoTIFF(url, apiKey) {
14095
14543
  }
14096
14544
  // [END solar_api_download_geotiff]
14097
14545
  async function getBuildingImages(latitude, longitude, apiKey, radiusMeters = 15) {
14098
- console.log("getBuildingImages", latitude, longitude, radiusMeters);
14099
14546
  if (latitude === 0 || longitude === 0) {
14100
14547
  return null;
14101
14548
  }
@@ -14124,1052 +14571,950 @@ async function getBuildingImages(latitude, longitude, apiKey, radiusMeters = 15)
14124
14571
  }
14125
14572
  }
14126
14573
 
14127
- var dist = {};
14128
-
14129
- var types = {};
14130
-
14131
- var hasRequiredTypes;
14132
-
14133
- function requireTypes () {
14134
- if (hasRequiredTypes) return types;
14135
- hasRequiredTypes = 1;
14136
- Object.defineProperty(types, "__esModule", { value: true });
14137
- types.Tool = void 0;
14138
- var Tool;
14139
- (function (Tool) {
14140
- Tool["ROOF_POLYGON"] = "roof_polygon";
14141
- Tool["OBSTRUCTION_POLYGON"] = "obstruction_polygon";
14142
- Tool["MOVE"] = "move";
14143
- Tool["MARK_ROOF_EDGE"] = "mark_roof_edge";
14144
- })(Tool || (types.Tool = Tool = {}));
14145
-
14146
- return types;
14147
- }
14148
-
14149
- var canvasUpdate = {};
14150
-
14151
- var drawingUtils = {};
14152
-
14153
- var hasRequiredDrawingUtils;
14154
-
14155
- function requireDrawingUtils () {
14156
- if (hasRequiredDrawingUtils) return drawingUtils;
14157
- hasRequiredDrawingUtils = 1;
14158
- (function (exports) {
14159
- Object.defineProperty(exports, "__esModule", { value: true });
14160
- exports.CanvasUtils = exports.PREVIEW_POINT_RADIUS = exports.CLOSE_DISTANCE = exports.POINT_HOVER_RADIUS = exports.POINT_RADIUS = exports.CANVAS_COLORS = void 0;
14161
- const types_1 = requireTypes();
14162
- const harmonia_types_1 = requireDist$1();
14163
- exports.CANVAS_COLORS = {
14164
- ROOF: "#4A90E2",
14165
- OBSTRUCTION: "#E74C3C",
14166
- ROOF_FILL: "rgba(74, 144, 226, 0.2)",
14167
- OBSTRUCTION_FILL: "rgba(231, 76, 60, 0.2)",
14168
- POINT: "#2C3E50",
14169
- POINT_HOVER: "#E67E22",
14170
- GRID: "#E5E5E5",
14171
- PREVIEW_POINT: "rgba(74, 144, 226, 0.6)",
14172
- PREVIEW_LINE: "rgba(74, 144, 226, 0.4)",
14173
- START_POINT_HIGHLIGHT: "#27AE60",
14174
- HIGHLIGHTED_EDGE: "#FF6B35",
14175
- SOLAR_PANEL: "#2E86C1",
14176
- SOLAR_PANEL_FILL: "rgba(46, 134, 193, 0.3)",
14177
- SOLAR_PANEL_BORDER: "#1B4F72",
14178
- };
14179
- exports.POINT_RADIUS = 6;
14180
- exports.POINT_HOVER_RADIUS = 8;
14181
- exports.CLOSE_DISTANCE = 7;
14182
- exports.PREVIEW_POINT_RADIUS = 5;
14183
- class CanvasUtils {
14184
- static screenToCanvas(screenPoint, canvasRect, coordinateSystem) {
14185
- const scaleX = coordinateSystem.width / canvasRect.width;
14186
- const scaleY = coordinateSystem.height / canvasRect.height;
14187
- return new harmonia_types_1.Point((screenPoint.x - canvasRect.left) * scaleX, (screenPoint.y - canvasRect.top) * scaleY);
14188
- }
14189
- static canvasToScreen(canvasPoint, canvasRect, coordinateSystem) {
14190
- const scaleX = canvasRect.width / coordinateSystem.width;
14191
- const scaleY = canvasRect.height / coordinateSystem.height;
14192
- return new harmonia_types_1.Point(canvasPoint.x * scaleX, canvasPoint.y * scaleY);
14193
- }
14194
- static getDistance(point1, point2) {
14195
- const dx = point1.x - point2.x;
14196
- const dy = point1.y - point2.y;
14197
- return Math.sqrt(dx * dx + dy * dy);
14198
- }
14199
- static findNearestPoint(clickPoint, polygons, threshold = exports.CLOSE_DISTANCE) {
14200
- for (const polygon of polygons) {
14201
- for (let i = 0; i < polygon.points.length; i++) {
14202
- const distance = this.getDistance(clickPoint, polygon.points[i]);
14203
- if (distance <= threshold) {
14204
- return { polygon, pointIndex: i };
14205
- }
14206
- }
14207
- }
14208
- return null;
14209
- }
14210
- static isNearFirstPoint(polygon, point, threshold = exports.CLOSE_DISTANCE) {
14211
- if (polygon.points.length < 3)
14212
- return false;
14213
- return this.getDistance(point, polygon.points[0]) <= threshold;
14214
- }
14215
- static findPolygonAtPoint(point, polygons) {
14216
- for (let i = polygons.length - 1; i >= 0; i--) {
14217
- const polygon = polygons[i];
14218
- if (!polygon.closed || polygon.points.length < 3) {
14219
- continue;
14220
- }
14221
- if (this.isPointInPolygon(point, polygon.points)) {
14222
- return polygon;
14223
- }
14224
- }
14225
- return null;
14226
- }
14227
- static isPointInPolygon(point, polygonPoints) {
14228
- const x = point.x;
14229
- const y = point.y;
14230
- let inside = false;
14231
- for (let i = 0, j = polygonPoints.length - 1; i < polygonPoints.length; j = i++) {
14232
- const xi = polygonPoints[i].x;
14233
- const yi = polygonPoints[i].y;
14234
- const xj = polygonPoints[j].x;
14235
- const yj = polygonPoints[j].y;
14236
- if (((yi > y) !== (yj > y)) &&
14237
- (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
14238
- inside = !inside;
14239
- }
14240
- }
14241
- return inside;
14242
- }
14243
- static drawGrid(ctx, canvasSize, coordinateSystem, gridSize = 50) {
14244
- ctx.save();
14245
- ctx.strokeStyle = exports.CANVAS_COLORS.GRID;
14246
- ctx.lineWidth = 1;
14247
- const scaleX = canvasSize.width / coordinateSystem.width;
14248
- const scaleY = canvasSize.height / coordinateSystem.height;
14249
- for (let x = 0; x <= coordinateSystem.width; x += gridSize) {
14250
- const screenX = x * scaleX;
14251
- ctx.beginPath();
14252
- ctx.moveTo(screenX, 0);
14253
- ctx.lineTo(screenX, canvasSize.height);
14254
- ctx.stroke();
14255
- }
14256
- for (let y = 0; y <= coordinateSystem.height; y += gridSize) {
14257
- const screenY = y * scaleY;
14258
- ctx.beginPath();
14259
- ctx.moveTo(0, screenY);
14260
- ctx.lineTo(canvasSize.width, screenY);
14261
- ctx.stroke();
14262
- }
14263
- ctx.restore();
14264
- }
14265
- static drawPolygon(ctx, polygon, canvasSize, coordinateSystem, isActive = false) {
14266
- if (polygon.points.length === 0)
14267
- return;
14268
- const scaleX = canvasSize.width / coordinateSystem.width;
14269
- const scaleY = canvasSize.height / coordinateSystem.height;
14270
- const color = polygon.type === harmonia_types_1.PolygonType.ROOF
14271
- ? exports.CANVAS_COLORS.ROOF
14272
- : exports.CANVAS_COLORS.OBSTRUCTION;
14273
- const fillColor = polygon.type === harmonia_types_1.PolygonType.ROOF
14274
- ? exports.CANVAS_COLORS.ROOF_FILL
14275
- : exports.CANVAS_COLORS.OBSTRUCTION_FILL;
14276
- ctx.save();
14277
- if (polygon.closed && polygon.points.length >= 3) {
14278
- ctx.fillStyle = fillColor;
14279
- ctx.beginPath();
14280
- ctx.moveTo(polygon.points[0].x * scaleX, polygon.points[0].y * scaleY);
14281
- for (let i = 1; i < polygon.points.length; i++) {
14282
- ctx.lineTo(polygon.points[i].x * scaleX, polygon.points[i].y * scaleY);
14283
- }
14284
- ctx.closePath();
14285
- ctx.fill();
14286
- }
14287
- ctx.strokeStyle = color;
14288
- ctx.lineWidth = isActive ? 3 : 2;
14289
- ctx.beginPath();
14290
- if (polygon.points.length > 0) {
14291
- ctx.moveTo(polygon.points[0].x * scaleX, polygon.points[0].y * scaleY);
14292
- for (let i = 1; i < polygon.points.length; i++) {
14293
- ctx.lineTo(polygon.points[i].x * scaleX, polygon.points[i].y * scaleY);
14294
- }
14295
- if (polygon.closed) {
14296
- ctx.closePath();
14297
- }
14298
- }
14299
- ctx.stroke();
14300
- ctx.restore();
14301
- }
14302
- static drawPoints(ctx, polygon, canvasSize, coordinateSystem, hoveredPointIndex = -1, showPoints = true) {
14303
- if (!showPoints || polygon.points.length === 0)
14304
- return;
14305
- const scaleX = canvasSize.width / coordinateSystem.width;
14306
- const scaleY = canvasSize.height / coordinateSystem.height;
14307
- ctx.save();
14308
- polygon.points.forEach((point, index) => {
14309
- const screenX = point.x * scaleX;
14310
- const screenY = point.y * scaleY;
14311
- const isHovered = index === hoveredPointIndex;
14312
- const radius = isHovered ? exports.POINT_HOVER_RADIUS : exports.POINT_RADIUS;
14313
- ctx.fillStyle = isHovered
14314
- ? exports.CANVAS_COLORS.POINT_HOVER
14315
- : exports.CANVAS_COLORS.POINT;
14316
- ctx.beginPath();
14317
- ctx.arc(screenX, screenY, radius, 0, 2 * Math.PI);
14318
- ctx.fill();
14319
- ctx.strokeStyle = "#FFFFFF";
14320
- ctx.lineWidth = 2;
14321
- ctx.stroke();
14322
- });
14323
- ctx.restore();
14324
- }
14325
- static clearCanvas(ctx, width, height) {
14326
- ctx.clearRect(0, 0, width, height);
14327
- }
14328
- static drawPreviewPoint(ctx, point, canvasSize, coordinateSystem, polygonType) {
14329
- const scaleX = canvasSize.width / coordinateSystem.width;
14330
- const scaleY = canvasSize.height / coordinateSystem.height;
14331
- const screenX = point.x * scaleX;
14332
- const screenY = point.y * scaleY;
14333
- ctx.save();
14334
- const color = polygonType === harmonia_types_1.PolygonType.ROOF
14335
- ? exports.CANVAS_COLORS.ROOF
14336
- : exports.CANVAS_COLORS.OBSTRUCTION;
14337
- ctx.fillStyle = color;
14338
- ctx.globalAlpha = 0.6;
14339
- ctx.beginPath();
14340
- ctx.arc(screenX, screenY, exports.PREVIEW_POINT_RADIUS, 0, 2 * Math.PI);
14341
- ctx.fill();
14342
- ctx.globalAlpha = 1;
14343
- ctx.strokeStyle = "#FFFFFF";
14344
- ctx.lineWidth = 1;
14345
- ctx.stroke();
14346
- ctx.restore();
14347
- }
14348
- static drawPreviewLine(ctx, fromPoint, toPoint, canvasSize, coordinateSystem, polygonType) {
14349
- const scaleX = canvasSize.width / coordinateSystem.width;
14350
- const scaleY = canvasSize.height / coordinateSystem.height;
14351
- const fromScreenX = fromPoint.x * scaleX;
14352
- const fromScreenY = fromPoint.y * scaleY;
14353
- const toScreenX = toPoint.x * scaleX;
14354
- const toScreenY = toPoint.y * scaleY;
14355
- ctx.save();
14356
- const color = polygonType === harmonia_types_1.PolygonType.ROOF
14357
- ? exports.CANVAS_COLORS.ROOF
14358
- : exports.CANVAS_COLORS.OBSTRUCTION;
14359
- ctx.strokeStyle = color;
14360
- ctx.globalAlpha = 0.4;
14361
- ctx.lineWidth = 2;
14362
- ctx.setLineDash([5, 5]);
14363
- ctx.beginPath();
14364
- ctx.moveTo(fromScreenX, fromScreenY);
14365
- ctx.lineTo(toScreenX, toScreenY);
14366
- ctx.stroke();
14367
- ctx.restore();
14368
- }
14369
- static drawHighlightedStartPoint(ctx, point, canvasSize, coordinateSystem) {
14370
- const scaleX = canvasSize.width / coordinateSystem.width;
14371
- const scaleY = canvasSize.height / coordinateSystem.height;
14372
- const screenX = point.x * scaleX;
14373
- const screenY = point.y * scaleY;
14374
- ctx.save();
14375
- ctx.strokeStyle = exports.CANVAS_COLORS.START_POINT_HIGHLIGHT;
14376
- ctx.lineWidth = 3;
14377
- ctx.globalAlpha = 0.8;
14378
- ctx.beginPath();
14379
- ctx.arc(screenX, screenY, exports.POINT_HOVER_RADIUS + 3, 0, 2 * Math.PI);
14380
- ctx.stroke();
14381
- ctx.fillStyle = exports.CANVAS_COLORS.START_POINT_HIGHLIGHT;
14382
- ctx.globalAlpha = 1;
14383
- ctx.beginPath();
14384
- ctx.arc(screenX, screenY, exports.POINT_RADIUS, 0, 2 * Math.PI);
14385
- ctx.fill();
14386
- ctx.strokeStyle = "#FFFFFF";
14387
- ctx.lineWidth = 2;
14388
- ctx.stroke();
14389
- ctx.restore();
14390
- }
14391
- static drawHighlightedEdge(ctx, pointA, pointB, canvasSize, coordinateSystem) {
14392
- const scaleX = canvasSize.width / coordinateSystem.width;
14393
- const scaleY = canvasSize.height / coordinateSystem.height;
14394
- const screenAX = pointA.x * scaleX;
14395
- const screenAY = pointA.y * scaleY;
14396
- const screenBX = pointB.x * scaleX;
14397
- const screenBY = pointB.y * scaleY;
14398
- ctx.save();
14399
- ctx.strokeStyle = exports.CANVAS_COLORS.HIGHLIGHTED_EDGE;
14400
- ctx.lineWidth = 4;
14401
- ctx.globalAlpha = 0.9;
14402
- ctx.beginPath();
14403
- ctx.moveTo(screenAX, screenAY);
14404
- ctx.lineTo(screenBX, screenBY);
14405
- ctx.stroke();
14406
- ctx.restore();
14407
- }
14408
- static drawSolarPanel(ctx, positionedPanel, canvasSize, coordinateSystem, azimuth = 0, pitch = 0) {
14409
- const scaleX = canvasSize.width / coordinateSystem.width;
14410
- const scaleY = canvasSize.height / coordinateSystem.height;
14411
- const centerX = positionedPanel.pixelPosition.x * scaleX;
14412
- const centerY = positionedPanel.pixelPosition.y * scaleY;
14413
- const panelWidth = positionedPanel.horizontal
14414
- ? positionedPanel.panel.heightMeters
14415
- : positionedPanel.panel.widthMeters;
14416
- const panelHeight = positionedPanel.horizontal
14417
- ? positionedPanel.panel.widthMeters
14418
- : positionedPanel.panel.heightMeters;
14419
- let scaledWidth = panelWidth * scaleX;
14420
- let scaledHeight = panelHeight * scaleY;
14421
- const azimuthRad = (azimuth * Math.PI) / 180;
14422
- ctx.save();
14423
- ctx.translate(centerX, centerY);
14424
- ctx.rotate(azimuthRad);
14425
- ctx.fillStyle = exports.CANVAS_COLORS.SOLAR_PANEL_FILL;
14426
- ctx.fillRect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
14427
- ctx.strokeStyle = exports.CANVAS_COLORS.SOLAR_PANEL_BORDER;
14428
- ctx.lineWidth = 1;
14429
- ctx.strokeRect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
14430
- ctx.strokeStyle = exports.CANVAS_COLORS.SOLAR_PANEL;
14431
- ctx.lineWidth = 0.5;
14432
- ctx.globalAlpha = 1;
14433
- const cellSize = Math.min(scaledWidth / 6, scaledHeight / 10);
14434
- for (let x = -scaledWidth / 2 + cellSize; x < scaledWidth / 2; x += cellSize) {
14435
- ctx.beginPath();
14436
- ctx.moveTo(x, -scaledHeight / 2);
14437
- ctx.lineTo(x, scaledHeight / 2);
14438
- ctx.stroke();
14439
- }
14440
- for (let y = -scaledHeight / 2 + cellSize; y < scaledHeight / 2; y += cellSize) {
14441
- ctx.beginPath();
14442
- ctx.moveTo(-scaledWidth / 2, y);
14443
- ctx.lineTo(scaledWidth / 2, y);
14444
- ctx.stroke();
14445
- }
14446
- ctx.restore();
14447
- }
14448
- static drawPositionedSolarPanels(ctx, positionedPanels, canvasSize, coordinateSystem, polygon) {
14449
- if (!positionedPanels || positionedPanels.length === 0)
14450
- return;
14451
- const azimuth = polygon.azimuth || 0;
14452
- const pitch = polygon.pitch || 0;
14453
- positionedPanels.forEach((panel) => {
14454
- this.drawSolarPanel(ctx, panel, canvasSize, coordinateSystem, azimuth, pitch);
14455
- });
14456
- }
14457
- static drawState(ctx, state, solarPanelType, options = {}) {
14458
- const { showGrid = true, showPoints = true, showPreview = true, showSolarPanels = true, hoveredPointIndex = -1, hoveredPolygonId = null, } = options;
14459
- this.clearCanvas(ctx, state.canvasSize.width, state.canvasSize.height);
14460
- if (showGrid) {
14461
- this.drawGrid(ctx, state.canvasSize, state.coordinateSystem);
14462
- }
14463
- state.polygons.forEach((polygon) => {
14464
- var _a;
14465
- const isActive = polygon.id === (((_a = state.currentPolygon) === null || _a === void 0 ? void 0 : _a.id) || hoveredPolygonId);
14466
- this.drawPolygon(ctx, polygon, state.canvasSize, state.coordinateSystem, isActive);
14467
- if (showPoints) {
14468
- const pointHoverIndex = polygon.id === hoveredPolygonId
14469
- ? hoveredPointIndex
14470
- : -1;
14471
- this.drawPoints(ctx, polygon, state.canvasSize, state.coordinateSystem, pointHoverIndex, showPoints);
14472
- }
14473
- });
14474
- if (state.currentPolygon &&
14475
- !state.polygons.some((p) => { var _a; return p.id === ((_a = state.currentPolygon) === null || _a === void 0 ? void 0 : _a.id); })) {
14476
- this.drawPolygon(ctx, state.currentPolygon, state.canvasSize, state.coordinateSystem, true);
14477
- if (showPoints) {
14478
- this.drawPoints(ctx, state.currentPolygon, state.canvasSize, state.coordinateSystem, hoveredPointIndex, showPoints);
14479
- }
14480
- if (state.isHoveringStartPoint &&
14481
- state.currentPolygon.points.length > 2) {
14482
- this.drawHighlightedStartPoint(ctx, state.currentPolygon.points[0], state.canvasSize, state.coordinateSystem);
14483
- }
14484
- }
14485
- if (state.highlightedEdge) {
14486
- this.drawHighlightedEdge(ctx, state.highlightedEdge.pointA, state.highlightedEdge.pointB, state.canvasSize, state.coordinateSystem);
14487
- }
14488
- if (showSolarPanels && state.positionedSolarPanels) {
14489
- state.polygons.forEach((polygon) => {
14490
- const panelsForPolygon = state.positionedSolarPanels[polygon.id];
14491
- if (panelsForPolygon && panelsForPolygon.length > 0) {
14492
- this.drawPositionedSolarPanels(ctx, panelsForPolygon, state.canvasSize, state.coordinateSystem, polygon);
14493
- }
14494
- });
14495
- }
14496
- if (state.mousePosition && state.currentPolygon &&
14497
- !state.currentPolygon.closed && showPreview) {
14498
- const polygonType = state.selectedTool === types_1.Tool.ROOF_POLYGON
14499
- ? harmonia_types_1.PolygonType.ROOF
14500
- : harmonia_types_1.PolygonType.OBSTRUCTION;
14501
- this.drawPreviewPoint(ctx, state.mousePosition, state.canvasSize, state.coordinateSystem, polygonType);
14502
- if (state.currentPolygon.points.length > 0) {
14503
- const lastPoint = state.currentPolygon
14504
- .points[state.currentPolygon.points.length - 1];
14505
- this.drawPreviewLine(ctx, lastPoint, state.mousePosition, state.canvasSize, state.coordinateSystem, polygonType);
14506
- }
14507
- }
14508
- }
14509
- }
14510
- exports.CanvasUtils = CanvasUtils;
14511
-
14512
- } (drawingUtils));
14513
- return drawingUtils;
14514
- }
14515
-
14516
- var polygonUtils = {};
14517
-
14518
- var hasRequiredPolygonUtils;
14519
-
14520
- function requirePolygonUtils () {
14521
- if (hasRequiredPolygonUtils) return polygonUtils;
14522
- hasRequiredPolygonUtils = 1;
14523
- Object.defineProperty(polygonUtils, "__esModule", { value: true });
14524
- polygonUtils.calculatePolygonArea = calculatePolygonArea;
14525
- polygonUtils.azimuthToCardinal = azimuthToCardinal;
14526
- polygonUtils.calculatePolygonOrientation = calculatePolygonOrientation;
14527
- polygonUtils.calculatePolygonAngle = calculatePolygonAngle;
14528
- function calculatePolygonArea(points) {
14529
- let area = 0;
14530
- for (let i = 0; i < points.length; i++) {
14531
- const j = (i + 1) % points.length;
14532
- area += points[i].x * points[j].y;
14533
- area -= points[j].x * points[i].y;
14534
- }
14535
- return Math.abs(area / 2);
14536
- }
14537
- function azimuthToCardinal(azimuth) {
14538
- if (azimuth >= 337.5 || azimuth < 22.5)
14539
- return "North";
14540
- if (azimuth >= 22.5 && azimuth < 67.5)
14541
- return "Northeast";
14542
- if (azimuth >= 67.5 && azimuth < 112.5)
14543
- return "East";
14544
- if (azimuth >= 112.5 && azimuth < 157.5)
14545
- return "Southeast";
14546
- if (azimuth >= 157.5 && azimuth < 202.5)
14547
- return "South";
14548
- if (azimuth >= 202.5 && azimuth < 247.5)
14549
- return "Southwest";
14550
- if (azimuth >= 247.5 && azimuth < 292.5)
14551
- return "West";
14552
- return "Northwest";
14553
- }
14554
- function calculatePolygonOrientation(points) {
14555
- if (points.length < 3)
14556
- return 0;
14557
- let totalAngle = 0;
14558
- for (let i = 0; i < points.length; i++) {
14559
- const j = (i + 1) % points.length;
14560
- const angle = Math.atan2(points[j].y - points[i].y, points[j].x - points[i].x);
14561
- totalAngle += angle;
14562
- }
14563
- const avgAngle = (totalAngle / points.length) * (180 / Math.PI);
14564
- const normalizedAngle = (avgAngle + 360) % 360;
14565
- return normalizedAngle;
14566
- }
14567
- function calculatePolygonAngle(points) {
14568
- if (points.length < 3)
14569
- return 0;
14570
- let totalAngle = 0;
14571
- for (let i = 0; i < points.length; i++) {
14572
- const j = (i + 1) % points.length;
14573
- const k = (i + 2) % points.length;
14574
- const v1 = {
14575
- x: points[j].x - points[i].x,
14576
- y: points[j].y - points[i].y,
14577
- };
14578
- const v2 = {
14579
- x: points[k].x - points[j].x,
14580
- y: points[k].y - points[j].y,
14581
- };
14582
- const dot = v1.x * v2.x + v1.y * v2.y;
14583
- const mag1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
14584
- const mag2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
14585
- const angle = Math.acos(dot / (mag1 * mag2));
14586
- totalAngle += angle;
14587
- }
14588
- return (totalAngle / points.length) * (180 / Math.PI);
14589
- }
14590
-
14591
- return polygonUtils;
14574
+ var Tool;
14575
+ (function (Tool) {
14576
+ Tool["ROOF_POLYGON"] = "roof_polygon";
14577
+ Tool["OBSTRUCTION_POLYGON"] = "obstruction_polygon";
14578
+ Tool["MOVE"] = "move";
14579
+ Tool["MARK_ROOF_EDGE"] = "mark_roof_edge";
14580
+ })(Tool || (Tool = {}));
14581
+
14582
+ const CANVAS_COLORS = {
14583
+ ROOF: "#4A90E2",
14584
+ OBSTRUCTION: "#E74C3C",
14585
+ ROOF_FILL: "rgba(74, 144, 226, 0.2)",
14586
+ OBSTRUCTION_FILL: "rgba(231, 76, 60, 0.2)",
14587
+ POINT: "#2C3E50",
14588
+ POINT_HOVER: "#E67E22",
14589
+ GRID: "#E5E5E5",
14590
+ START_POINT_HIGHLIGHT: "#27AE60",
14591
+ HIGHLIGHTED_EDGE: "#FF6B35",
14592
+ SOLAR_PANEL: "#2E86C1",
14593
+ SOLAR_PANEL_FILL: "rgba(46, 134, 193, 0.3)",
14594
+ SOLAR_PANEL_BORDER: "#1B4F72",
14595
+ };
14596
+ const POINT_RADIUS = 6;
14597
+ const POINT_HOVER_RADIUS = 8;
14598
+ const CLOSE_DISTANCE = 7;
14599
+ const PREVIEW_POINT_RADIUS = 5;
14600
+ class CanvasUtils {
14601
+ static screenToCanvas(screenPoint, canvasRect, coordinateSystem) {
14602
+ const scaleX = coordinateSystem.width / canvasRect.width;
14603
+ const scaleY = coordinateSystem.height / canvasRect.height;
14604
+ return new Point((screenPoint.x - canvasRect.left) * scaleX, (screenPoint.y - canvasRect.top) * scaleY);
14605
+ }
14606
+ static canvasToScreen(canvasPoint, canvasRect, coordinateSystem) {
14607
+ const scaleX = canvasRect.width / coordinateSystem.width;
14608
+ const scaleY = canvasRect.height / coordinateSystem.height;
14609
+ return new Point(canvasPoint.x * scaleX, canvasPoint.y * scaleY);
14610
+ }
14611
+ static getDistance(point1, point2) {
14612
+ const dx = point1.x - point2.x;
14613
+ const dy = point1.y - point2.y;
14614
+ return Math.sqrt(dx * dx + dy * dy);
14615
+ }
14616
+ static findNearestPoint(clickPoint, polygons, threshold = CLOSE_DISTANCE) {
14617
+ for (const polygon of polygons) {
14618
+ for (let i = 0; i < polygon.points.length; i++) {
14619
+ const distance = this.getDistance(clickPoint, polygon.points[i]);
14620
+ if (distance <= threshold) {
14621
+ return { polygon, pointIndex: i };
14622
+ }
14623
+ }
14624
+ }
14625
+ return null;
14626
+ }
14627
+ static isNearFirstPoint(polygon, point, threshold = CLOSE_DISTANCE) {
14628
+ if (polygon.points.length < 3)
14629
+ return false;
14630
+ return this.getDistance(point, polygon.points[0]) <= threshold;
14631
+ }
14632
+ static findPolygonAtPoint(point, polygons) {
14633
+ for (let i = polygons.length - 1; i >= 0; i--) {
14634
+ const polygon = polygons[i];
14635
+ if (!polygon.closed || polygon.points.length < 3) {
14636
+ continue;
14637
+ }
14638
+ if (this.isPointInPolygon(point, polygon.points)) {
14639
+ return polygon;
14640
+ }
14641
+ }
14642
+ return null;
14643
+ }
14644
+ static isPointInPolygon(point, polygonPoints) {
14645
+ const x = point.x;
14646
+ const y = point.y;
14647
+ let inside = false;
14648
+ for (let i = 0, j = polygonPoints.length - 1; i < polygonPoints.length; j = i++) {
14649
+ const xi = polygonPoints[i].x;
14650
+ const yi = polygonPoints[i].y;
14651
+ const xj = polygonPoints[j].x;
14652
+ const yj = polygonPoints[j].y;
14653
+ if (((yi > y) !== (yj > y)) &&
14654
+ (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
14655
+ inside = !inside;
14656
+ }
14657
+ }
14658
+ return inside;
14659
+ }
14660
+ static drawGrid(ctx, canvasSize, coordinateSystem, gridSize = 50) {
14661
+ ctx.save();
14662
+ ctx.strokeStyle = CANVAS_COLORS.GRID;
14663
+ ctx.lineWidth = 1;
14664
+ const scaleX = canvasSize.width / coordinateSystem.width;
14665
+ const scaleY = canvasSize.height / coordinateSystem.height;
14666
+ for (let x = 0; x <= coordinateSystem.width; x += gridSize) {
14667
+ const screenX = x * scaleX;
14668
+ ctx.beginPath();
14669
+ ctx.moveTo(screenX, 0);
14670
+ ctx.lineTo(screenX, canvasSize.height);
14671
+ ctx.stroke();
14672
+ }
14673
+ for (let y = 0; y <= coordinateSystem.height; y += gridSize) {
14674
+ const screenY = y * scaleY;
14675
+ ctx.beginPath();
14676
+ ctx.moveTo(0, screenY);
14677
+ ctx.lineTo(canvasSize.width, screenY);
14678
+ ctx.stroke();
14679
+ }
14680
+ ctx.restore();
14681
+ }
14682
+ static drawPolygon(ctx, polygon, canvasSize, coordinateSystem, isActive = false) {
14683
+ if (polygon.points.length === 0)
14684
+ return;
14685
+ const scaleX = canvasSize.width / coordinateSystem.width;
14686
+ const scaleY = canvasSize.height / coordinateSystem.height;
14687
+ const color = polygon.type === PolygonType.ROOF
14688
+ ? CANVAS_COLORS.ROOF
14689
+ : CANVAS_COLORS.OBSTRUCTION;
14690
+ const fillColor = polygon.type === PolygonType.ROOF
14691
+ ? CANVAS_COLORS.ROOF_FILL
14692
+ : CANVAS_COLORS.OBSTRUCTION_FILL;
14693
+ ctx.save();
14694
+ if (polygon.closed && polygon.points.length >= 3) {
14695
+ ctx.fillStyle = fillColor;
14696
+ ctx.beginPath();
14697
+ ctx.moveTo(polygon.points[0].x * scaleX, polygon.points[0].y * scaleY);
14698
+ for (let i = 1; i < polygon.points.length; i++) {
14699
+ ctx.lineTo(polygon.points[i].x * scaleX, polygon.points[i].y * scaleY);
14700
+ }
14701
+ ctx.closePath();
14702
+ ctx.fill();
14703
+ }
14704
+ ctx.strokeStyle = color;
14705
+ ctx.lineWidth = isActive ? 3 : 2;
14706
+ ctx.beginPath();
14707
+ if (polygon.points.length > 0) {
14708
+ ctx.moveTo(polygon.points[0].x * scaleX, polygon.points[0].y * scaleY);
14709
+ for (let i = 1; i < polygon.points.length; i++) {
14710
+ ctx.lineTo(polygon.points[i].x * scaleX, polygon.points[i].y * scaleY);
14711
+ }
14712
+ if (polygon.closed) {
14713
+ ctx.closePath();
14714
+ }
14715
+ }
14716
+ ctx.stroke();
14717
+ ctx.restore();
14718
+ }
14719
+ static drawPoints(ctx, polygon, canvasSize, coordinateSystem, hoveredPointIndex = -1, showPoints = true) {
14720
+ if (!showPoints || polygon.points.length === 0)
14721
+ return;
14722
+ const scaleX = canvasSize.width / coordinateSystem.width;
14723
+ const scaleY = canvasSize.height / coordinateSystem.height;
14724
+ ctx.save();
14725
+ polygon.points.forEach((point, index) => {
14726
+ const screenX = point.x * scaleX;
14727
+ const screenY = point.y * scaleY;
14728
+ const isHovered = index === hoveredPointIndex;
14729
+ const radius = isHovered ? POINT_HOVER_RADIUS : POINT_RADIUS;
14730
+ ctx.fillStyle = isHovered
14731
+ ? CANVAS_COLORS.POINT_HOVER
14732
+ : CANVAS_COLORS.POINT;
14733
+ ctx.beginPath();
14734
+ ctx.arc(screenX, screenY, radius, 0, 2 * Math.PI);
14735
+ ctx.fill();
14736
+ ctx.strokeStyle = "#FFFFFF";
14737
+ ctx.lineWidth = 2;
14738
+ ctx.stroke();
14739
+ });
14740
+ ctx.restore();
14741
+ }
14742
+ static clearCanvas(ctx, width, height) {
14743
+ ctx.clearRect(0, 0, width, height);
14744
+ }
14745
+ static drawPreviewPoint(ctx, point, canvasSize, coordinateSystem, polygonType) {
14746
+ const scaleX = canvasSize.width / coordinateSystem.width;
14747
+ const scaleY = canvasSize.height / coordinateSystem.height;
14748
+ const screenX = point.x * scaleX;
14749
+ const screenY = point.y * scaleY;
14750
+ ctx.save();
14751
+ const color = polygonType === PolygonType.ROOF
14752
+ ? CANVAS_COLORS.ROOF
14753
+ : CANVAS_COLORS.OBSTRUCTION;
14754
+ ctx.fillStyle = color;
14755
+ ctx.globalAlpha = 0.6;
14756
+ ctx.beginPath();
14757
+ ctx.arc(screenX, screenY, PREVIEW_POINT_RADIUS, 0, 2 * Math.PI);
14758
+ ctx.fill();
14759
+ ctx.globalAlpha = 1;
14760
+ ctx.strokeStyle = "#FFFFFF";
14761
+ ctx.lineWidth = 1;
14762
+ ctx.stroke();
14763
+ ctx.restore();
14764
+ }
14765
+ static drawPreviewLine(ctx, fromPoint, toPoint, canvasSize, coordinateSystem, polygonType) {
14766
+ const scaleX = canvasSize.width / coordinateSystem.width;
14767
+ const scaleY = canvasSize.height / coordinateSystem.height;
14768
+ const fromScreenX = fromPoint.x * scaleX;
14769
+ const fromScreenY = fromPoint.y * scaleY;
14770
+ const toScreenX = toPoint.x * scaleX;
14771
+ const toScreenY = toPoint.y * scaleY;
14772
+ ctx.save();
14773
+ const color = polygonType === PolygonType.ROOF
14774
+ ? CANVAS_COLORS.ROOF
14775
+ : CANVAS_COLORS.OBSTRUCTION;
14776
+ ctx.strokeStyle = color;
14777
+ ctx.globalAlpha = 0.4;
14778
+ ctx.lineWidth = 2;
14779
+ ctx.setLineDash([5, 5]);
14780
+ ctx.beginPath();
14781
+ ctx.moveTo(fromScreenX, fromScreenY);
14782
+ ctx.lineTo(toScreenX, toScreenY);
14783
+ ctx.stroke();
14784
+ ctx.restore();
14785
+ }
14786
+ static drawHighlightedStartPoint(ctx, point, canvasSize, coordinateSystem) {
14787
+ const scaleX = canvasSize.width / coordinateSystem.width;
14788
+ const scaleY = canvasSize.height / coordinateSystem.height;
14789
+ const screenX = point.x * scaleX;
14790
+ const screenY = point.y * scaleY;
14791
+ ctx.save();
14792
+ ctx.strokeStyle = CANVAS_COLORS.START_POINT_HIGHLIGHT;
14793
+ ctx.lineWidth = 3;
14794
+ ctx.globalAlpha = 0.8;
14795
+ ctx.beginPath();
14796
+ ctx.arc(screenX, screenY, POINT_HOVER_RADIUS + 3, 0, 2 * Math.PI);
14797
+ ctx.stroke();
14798
+ ctx.fillStyle = CANVAS_COLORS.START_POINT_HIGHLIGHT;
14799
+ ctx.globalAlpha = 1;
14800
+ ctx.beginPath();
14801
+ ctx.arc(screenX, screenY, POINT_RADIUS, 0, 2 * Math.PI);
14802
+ ctx.fill();
14803
+ ctx.strokeStyle = "#FFFFFF";
14804
+ ctx.lineWidth = 2;
14805
+ ctx.stroke();
14806
+ ctx.restore();
14807
+ }
14808
+ static drawHighlightedEdge(ctx, pointA, pointB, canvasSize, coordinateSystem) {
14809
+ const scaleX = canvasSize.width / coordinateSystem.width;
14810
+ const scaleY = canvasSize.height / coordinateSystem.height;
14811
+ const screenAX = pointA.x * scaleX;
14812
+ const screenAY = pointA.y * scaleY;
14813
+ const screenBX = pointB.x * scaleX;
14814
+ const screenBY = pointB.y * scaleY;
14815
+ ctx.save();
14816
+ ctx.strokeStyle = CANVAS_COLORS.HIGHLIGHTED_EDGE;
14817
+ ctx.lineWidth = 4;
14818
+ ctx.globalAlpha = 0.9;
14819
+ ctx.beginPath();
14820
+ ctx.moveTo(screenAX, screenAY);
14821
+ ctx.lineTo(screenBX, screenBY);
14822
+ ctx.stroke();
14823
+ ctx.restore();
14824
+ }
14825
+ static drawSolarPanel(ctx, positionedPanel, canvasSize, coordinateSystem, azimuth = 0, pitch = 0) {
14826
+ const scaleX = canvasSize.width / coordinateSystem.width;
14827
+ const scaleY = canvasSize.height / coordinateSystem.height;
14828
+ const centerX = positionedPanel.pixelPosition.x * scaleX;
14829
+ const centerY = positionedPanel.pixelPosition.y * scaleY;
14830
+ const panelWidth = positionedPanel.horizontal
14831
+ ? positionedPanel.panel.heightMeters
14832
+ : positionedPanel.panel.widthMeters;
14833
+ const panelHeight = positionedPanel.horizontal
14834
+ ? positionedPanel.panel.widthMeters
14835
+ : positionedPanel.panel.heightMeters;
14836
+ let scaledWidth = panelWidth * scaleX;
14837
+ let scaledHeight = panelHeight * scaleY;
14838
+ const azimuthRad = (azimuth * Math.PI) / 180;
14839
+ ctx.save();
14840
+ ctx.translate(centerX, centerY);
14841
+ ctx.rotate(azimuthRad);
14842
+ ctx.fillStyle = CANVAS_COLORS.SOLAR_PANEL_FILL;
14843
+ ctx.fillRect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
14844
+ ctx.strokeStyle = CANVAS_COLORS.SOLAR_PANEL_BORDER;
14845
+ ctx.lineWidth = 1;
14846
+ ctx.strokeRect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
14847
+ ctx.strokeStyle = CANVAS_COLORS.SOLAR_PANEL;
14848
+ ctx.lineWidth = 0.5;
14849
+ ctx.globalAlpha = 1;
14850
+ const cellSize = Math.min(scaledWidth / 6, scaledHeight / 10);
14851
+ for (let x = -scaledWidth / 2 + cellSize; x < scaledWidth / 2; x += cellSize) {
14852
+ ctx.beginPath();
14853
+ ctx.moveTo(x, -scaledHeight / 2);
14854
+ ctx.lineTo(x, scaledHeight / 2);
14855
+ ctx.stroke();
14856
+ }
14857
+ for (let y = -scaledHeight / 2 + cellSize; y < scaledHeight / 2; y += cellSize) {
14858
+ ctx.beginPath();
14859
+ ctx.moveTo(-scaledWidth / 2, y);
14860
+ ctx.lineTo(scaledWidth / 2, y);
14861
+ ctx.stroke();
14862
+ }
14863
+ ctx.restore();
14864
+ }
14865
+ static drawPositionedSolarPanels(ctx, positionedPanels, canvasSize, coordinateSystem, polygon) {
14866
+ if (!positionedPanels || positionedPanels.length === 0)
14867
+ return;
14868
+ const azimuth = polygon.azimuth || 0;
14869
+ const pitch = polygon.pitch || 0;
14870
+ positionedPanels.forEach((panel) => {
14871
+ this.drawSolarPanel(ctx, panel, canvasSize, coordinateSystem, azimuth, pitch);
14872
+ });
14873
+ }
14874
+ static drawState(ctx, state, solarPanelType, options = {}) {
14875
+ const { showGrid = true, showPoints = true, showPreview = true, showSolarPanels = true, hoveredPointIndex = -1, hoveredPolygonId = null, } = options;
14876
+ this.clearCanvas(ctx, state.canvasSize.width, state.canvasSize.height);
14877
+ if (showGrid) {
14878
+ this.drawGrid(ctx, state.canvasSize, state.coordinateSystem);
14879
+ }
14880
+ state.polygons.forEach((polygon) => {
14881
+ const isActive = polygon.id === (state.currentPolygon?.id || hoveredPolygonId);
14882
+ this.drawPolygon(ctx, polygon, state.canvasSize, state.coordinateSystem, isActive);
14883
+ if (showPoints) {
14884
+ const pointHoverIndex = polygon.id === hoveredPolygonId
14885
+ ? hoveredPointIndex
14886
+ : -1;
14887
+ this.drawPoints(ctx, polygon, state.canvasSize, state.coordinateSystem, pointHoverIndex, showPoints);
14888
+ }
14889
+ });
14890
+ if (state.currentPolygon &&
14891
+ !state.polygons.some((p) => p.id === state.currentPolygon?.id)) {
14892
+ this.drawPolygon(ctx, state.currentPolygon, state.canvasSize, state.coordinateSystem, true);
14893
+ if (showPoints) {
14894
+ this.drawPoints(ctx, state.currentPolygon, state.canvasSize, state.coordinateSystem, hoveredPointIndex, showPoints);
14895
+ }
14896
+ if (state.isHoveringStartPoint &&
14897
+ state.currentPolygon.points.length > 2) {
14898
+ this.drawHighlightedStartPoint(ctx, state.currentPolygon.points[0], state.canvasSize, state.coordinateSystem);
14899
+ }
14900
+ }
14901
+ if (state.highlightedEdge) {
14902
+ this.drawHighlightedEdge(ctx, state.highlightedEdge.pointA, state.highlightedEdge.pointB, state.canvasSize, state.coordinateSystem);
14903
+ }
14904
+ if (showSolarPanels && state.positionedSolarPanels) {
14905
+ state.polygons.forEach((polygon) => {
14906
+ const panelsForPolygon = state.positionedSolarPanels[polygon.id];
14907
+ if (panelsForPolygon && panelsForPolygon.length > 0) {
14908
+ this.drawPositionedSolarPanels(ctx, panelsForPolygon, state.canvasSize, state.coordinateSystem, polygon);
14909
+ }
14910
+ });
14911
+ }
14912
+ if (state.mousePosition && state.currentPolygon &&
14913
+ !state.currentPolygon.closed && showPreview) {
14914
+ const polygonType = state.selectedTool === Tool.ROOF_POLYGON
14915
+ ? PolygonType.ROOF
14916
+ : PolygonType.OBSTRUCTION;
14917
+ this.drawPreviewPoint(ctx, state.mousePosition, state.canvasSize, state.coordinateSystem, polygonType);
14918
+ if (state.currentPolygon.points.length > 0) {
14919
+ const lastPoint = state.currentPolygon
14920
+ .points[state.currentPolygon.points.length - 1];
14921
+ this.drawPreviewLine(ctx, lastPoint, state.mousePosition, state.canvasSize, state.coordinateSystem, polygonType);
14922
+ }
14923
+ }
14924
+ }
14592
14925
  }
14593
14926
 
14594
- var hasRequiredCanvasUpdate;
14595
-
14596
- function requireCanvasUpdate () {
14597
- if (hasRequiredCanvasUpdate) return canvasUpdate;
14598
- hasRequiredCanvasUpdate = 1;
14599
- (function (exports) {
14600
- Object.defineProperty(exports, "__esModule", { value: true });
14601
- exports.useCanvasManager = exports.createCanvasManager = exports.CanvasManager = void 0;
14602
- const types_1 = requireTypes();
14603
- const drawingUtils_1 = requireDrawingUtils();
14604
- const polygonUtils_1 = requirePolygonUtils();
14605
- const harmonia_types_1 = requireDist$1();
14606
- class CanvasManager {
14607
- constructor(config) {
14608
- this.config = config;
14609
- this.state = {
14610
- polygons: [],
14611
- currentPolygon: null,
14612
- selectedTool: types_1.Tool.ROOF_POLYGON,
14613
- draggedPointIndex: null,
14614
- draggedPolygonId: null,
14615
- canvasSize: { width: 800, height: 600 },
14616
- coordinateSystem: config.coordinateSystem,
14617
- mousePosition: null,
14618
- isHoveringStartPoint: false,
14619
- highlightedEdge: null,
14620
- positionedSolarPanels: {},
14621
- };
14622
- this.interaction = {
14623
- isDrawing: false,
14624
- isDragging: false,
14625
- lastTouchTime: 0,
14626
- };
14627
- this.history = [{
14628
- polygons: [],
14629
- currentPolygon: null,
14630
- changedPolygon: null,
14631
- positionedSolarPanels: {},
14632
- }];
14633
- this.historyIndex = 0;
14634
- this.meterToPixelRatio = null;
14635
- }
14636
- notifyStateChange() {
14637
- var _a, _b;
14638
- (_b = (_a = this.config).onStateChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.state);
14639
- }
14640
- saveToHistory(polygons, currentPolygon = null, changedPolygon = null, positionedSolarPanels = {}) {
14641
- const newHistory = this.history.slice(0, this.historyIndex + 1);
14642
- newHistory.push({
14643
- polygons: [...polygons],
14644
- currentPolygon: currentPolygon ? Object.assign({}, currentPolygon) : null,
14645
- changedPolygon: changedPolygon ? Object.assign({}, changedPolygon) : null,
14646
- positionedSolarPanels: positionedSolarPanels,
14647
- });
14648
- this.history = newHistory;
14649
- this.historyIndex = newHistory.length - 1;
14650
- }
14651
- generateId() {
14652
- return `polygon_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
14653
- }
14654
- setMeterToPixelRatio(meterToPixelRatio) {
14655
- this.meterToPixelRatio = meterToPixelRatio;
14656
- }
14657
- getCurrentPolygon() {
14658
- return this.state.currentPolygon;
14659
- }
14660
- updateOnPolygonChangeCallback(callback) {
14661
- this.config.onPolygonChange = callback;
14662
- }
14663
- getObstacles() {
14664
- return this.state.polygons.filter((p) => p.type === harmonia_types_1.PolygonType.OBSTRUCTION);
14665
- }
14666
- getHistory() {
14667
- return this.history;
14668
- }
14669
- getDebugString() {
14670
- return "0.0.1";
14671
- }
14672
- undo() {
14673
- var _a, _b;
14674
- if (this.historyIndex > 0) {
14675
- const newIndex = this.historyIndex - 1;
14676
- this.historyIndex = newIndex;
14677
- const previousState = this.history[newIndex];
14678
- this.state = Object.assign(Object.assign({}, this.state), { polygons: [...previousState.polygons], currentPolygon: previousState.currentPolygon
14679
- ? Object.assign({}, previousState.currentPolygon) : null, positionedSolarPanels: Object.assign({}, previousState.positionedSolarPanels) });
14680
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: previousState.currentPolygon !== null });
14681
- if (!((_a = this.state.currentPolygon) === null || _a === void 0 ? void 0 : _a.closed)) {
14682
- if (((_b = this.state.currentPolygon) === null || _b === void 0 ? void 0 : _b.type) === harmonia_types_1.PolygonType.ROOF) {
14683
- this.setSelectedTool(types_1.Tool.ROOF_POLYGON);
14684
- }
14685
- else {
14686
- this.state.selectedTool = types_1.Tool.OBSTRUCTION_POLYGON;
14687
- }
14688
- }
14689
- this.notifyStateChange();
14690
- return true;
14691
- }
14692
- return false;
14693
- }
14694
- setPositionedSolarPanel(polygonId, positionedSolarPanels) {
14695
- const newPositionedSolarPanels = Object.assign(Object.assign({}, this.state.positionedSolarPanels), { [polygonId]: positionedSolarPanels });
14696
- this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.currentPolygon, newPositionedSolarPanels);
14697
- this.state.positionedSolarPanels = newPositionedSolarPanels;
14698
- this.notifyStateChange();
14699
- }
14700
- getPositionedSolarPanels(polygonId) {
14701
- return this.state.positionedSolarPanels[polygonId] || null;
14702
- }
14703
- getAllPositionedSolarPanels() {
14704
- return this.state.positionedSolarPanels;
14705
- }
14706
- canUndo() {
14707
- return this.historyIndex > 0;
14708
- }
14709
- getState() {
14710
- return Object.assign({}, this.state);
14711
- }
14712
- getInteraction() {
14713
- return Object.assign({}, this.interaction);
14714
- }
14715
- updateCanvasSize(size) {
14716
- this.state = Object.assign(Object.assign({}, this.state), { canvasSize: size });
14717
- this.notifyStateChange();
14718
- }
14719
- getEventPoint(event, canvasRect) {
14720
- let clientX, clientY;
14721
- if ("touches" in event && event.touches.length > 0) {
14722
- clientX = event.touches[0].clientX;
14723
- clientY = event.touches[0].clientY;
14724
- }
14725
- else if ("changedTouches" in event && event.changedTouches.length > 0) {
14726
- clientX = event.changedTouches[0].clientX;
14727
- clientY = event.changedTouches[0].clientY;
14728
- }
14729
- else {
14730
- clientX = event.clientX;
14731
- clientY = event.clientY;
14732
- }
14733
- return drawingUtils_1.CanvasUtils.screenToCanvas(new harmonia_types_1.Point(clientX, clientY), canvasRect, this.config.coordinateSystem);
14734
- }
14735
- getPolygon(id) {
14736
- return this.state.polygons.find((p) => p.id === id) || null;
14737
- }
14738
- calculatePolygonArea(polygon) {
14739
- const pixelsRatio = this.meterToPixelRatio || 1;
14740
- const area = (0, polygonUtils_1.calculatePolygonArea)(polygon.points) * pixelsRatio *
14741
- pixelsRatio;
14742
- return area;
14743
- }
14744
- findClosestEdge(point, polygon, threshold = 10) {
14745
- if (!polygon || !polygon.closed || polygon.points.length < 3) {
14746
- return null;
14747
- }
14748
- let closestEdge = null;
14749
- let minDistance = Infinity;
14750
- for (let i = 0; i < polygon.points.length; i++) {
14751
- const pointA = polygon.points[i];
14752
- const pointB = polygon.points[(i + 1) % polygon.points.length];
14753
- const distanceA = Math.sqrt(Math.pow(point.x - pointA.x, 2) +
14754
- Math.pow(point.y - pointA.y, 2));
14755
- const distanceB = Math.sqrt(Math.pow(point.x - pointB.x, 2) +
14756
- Math.pow(point.y - pointB.y, 2));
14757
- const edgeLength = Math.sqrt(Math.pow(pointA.x - pointB.x, 2) +
14758
- Math.pow(pointA.y - pointB.y, 2));
14759
- const distance = distanceA + distanceB - edgeLength;
14760
- if (distance < minDistance && distance < threshold) {
14761
- minDistance = distance;
14762
- closestEdge = { pointA, pointB, distance };
14763
- }
14764
- }
14765
- return closestEdge;
14766
- }
14767
- calculateEdgeAzimuth(pointA, pointB) {
14768
- const dx = pointB.x - pointA.x;
14769
- const dy = pointB.y - pointA.y;
14770
- let angle = Math.atan2(dy, dx) * (180 / Math.PI);
14771
- angle += 90;
14772
- if (angle < 0) {
14773
- angle += 360;
14774
- }
14775
- else if (angle >= 360) {
14776
- angle -= 360;
14777
- }
14778
- return angle;
14779
- }
14780
- updatePolygon(polygon) {
14781
- const newPolygons = this.state.polygons.map((p) => {
14782
- if (p.id === polygon.id) {
14783
- return polygon;
14784
- }
14785
- return p;
14786
- });
14787
- this.state = Object.assign(Object.assign({}, this.state), { polygons: newPolygons, currentPolygon: polygon });
14788
- }
14789
- convertMetersToPixels(meters) {
14790
- return meters / (this.meterToPixelRatio || 1);
14791
- }
14792
- closePolygon(polygon) {
14793
- var _a, _b;
14794
- if (!polygon) {
14795
- return;
14796
- }
14797
- const closedPolygon = Object.assign(Object.assign({}, polygon), { area: this.calculatePolygonArea(polygon), closed: true });
14798
- const newPolygons = [...this.state.polygons, closedPolygon];
14799
- this.state = Object.assign(Object.assign({}, this.state), { polygons: newPolygons, currentPolygon: closedPolygon });
14800
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: false });
14801
- this.saveToHistory(newPolygons, closedPolygon, closedPolygon, this.state.positionedSolarPanels);
14802
- this.notifyStateChange();
14803
- (_b = (_a = this.config).onPolygonChange) === null || _b === void 0 ? void 0 : _b.call(_a, closedPolygon);
14804
- }
14805
- handleCanvasClick(point) {
14806
- var _a, _b, _c, _d;
14807
- if (this.state.selectedTool === types_1.Tool.MOVE) {
14808
- const nearestPoint = drawingUtils_1.CanvasUtils.findNearestPoint(point, this.state.polygons, drawingUtils_1.CLOSE_DISTANCE);
14809
- if (nearestPoint) {
14810
- this.state = Object.assign(Object.assign({}, this.state), { draggedPointIndex: nearestPoint.pointIndex, draggedPolygonId: nearestPoint.polygon.id, currentPolygon: nearestPoint.polygon });
14811
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDragging: true });
14812
- this.notifyStateChange();
14813
- return;
14814
- }
14815
- const clickedPolygon = drawingUtils_1.CanvasUtils.findPolygonAtPoint(point, this.state.polygons);
14816
- if (clickedPolygon) {
14817
- this.state = Object.assign(Object.assign({}, this.state), { currentPolygon: clickedPolygon });
14818
- this.notifyStateChange();
14819
- return;
14820
- }
14821
- this.state = Object.assign(Object.assign({}, this.state), { currentPolygon: null });
14822
- this.notifyStateChange();
14823
- return;
14824
- }
14825
- if (this.state.selectedTool === types_1.Tool.MARK_ROOF_EDGE) {
14826
- if (this.state.highlightedEdge) {
14827
- const polygonId = this.state.highlightedEdge.polygonId;
14828
- const edge = this.state.highlightedEdge;
14829
- const azimuth = this.calculateEdgeAzimuth(edge.pointA, edge.pointB);
14830
- const polygon = this.state.polygons.find((p) => p.id === polygonId);
14831
- if (!polygon) {
14832
- return;
14833
- }
14834
- const newPolygon = Object.assign(Object.assign({}, polygon), { azimuth });
14835
- const updatedPolygons = this.state.polygons.map((polygon) => {
14836
- if (polygon.id === polygonId) {
14837
- return newPolygon;
14838
- }
14839
- return polygon;
14840
- });
14841
- this.state = Object.assign(Object.assign({}, this.state), { polygons: updatedPolygons, currentPolygon: newPolygon || null });
14842
- this.saveToHistory(updatedPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
14843
- this.notifyStateChange();
14844
- (_b = (_a = this.config).onPolygonChange) === null || _b === void 0 ? void 0 : _b.call(_a, newPolygon);
14845
- (_d = (_c = this.config).onLowerRoofEdgeMarked) === null || _d === void 0 ? void 0 : _d.call(_c, newPolygon);
14846
- }
14847
- return;
14848
- }
14849
- const polygonType = this.state.selectedTool === types_1.Tool.ROOF_POLYGON
14850
- ? harmonia_types_1.PolygonType.ROOF
14851
- : harmonia_types_1.PolygonType.OBSTRUCTION;
14852
- if (!this.state.currentPolygon || this.state.currentPolygon.closed) {
14853
- const newPolygon = {
14854
- id: this.generateId(),
14855
- points: [point],
14856
- type: polygonType,
14857
- closed: false,
14858
- };
14859
- this.state = Object.assign(Object.assign({}, this.state), { currentPolygon: newPolygon });
14860
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: true });
14861
- this.saveToHistory(this.state.polygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
14862
- this.notifyStateChange();
14863
- }
14864
- else {
14865
- const shouldClose = drawingUtils_1.CanvasUtils.isNearFirstPoint(this.state.currentPolygon, point, drawingUtils_1.CLOSE_DISTANCE);
14866
- if (shouldClose && this.state.currentPolygon.points.length >= 3) {
14867
- this.closePolygon(this.state.currentPolygon);
14868
- }
14869
- else {
14870
- const updatedPolygon = Object.assign(Object.assign({}, this.state.currentPolygon), { points: [...this.state.currentPolygon.points, point] });
14871
- this.state = Object.assign(Object.assign({}, this.state), { currentPolygon: updatedPolygon });
14872
- this.saveToHistory(this.state.polygons, updatedPolygon, updatedPolygon, this.state.positionedSolarPanels);
14873
- this.notifyStateChange();
14874
- }
14875
- }
14876
- }
14877
- handleMouseDown(event, canvasRect) {
14878
- const point = this.getEventPoint(event, canvasRect);
14879
- this.handleCanvasClick(point);
14880
- }
14881
- handleTouchStart(event, canvasRect) {
14882
- const now = Date.now();
14883
- if (now - this.interaction.lastTouchTime < 300) {
14884
- return;
14885
- }
14886
- this.interaction = Object.assign(Object.assign({}, this.interaction), { lastTouchTime: now });
14887
- const point = this.getEventPoint(event, canvasRect);
14888
- if (this.state.selectedTool === types_1.Tool.MARK_ROOF_EDGE) {
14889
- let highlightedEdge = null;
14890
- let minDistance = Infinity;
14891
- for (const polygon of this.state.polygons) {
14892
- if (polygon.closed) {
14893
- const closestEdge = this.findClosestEdge(point, polygon);
14894
- if (closestEdge && closestEdge.distance < minDistance) {
14895
- minDistance = closestEdge.distance;
14896
- highlightedEdge = {
14897
- pointA: closestEdge.pointA,
14898
- pointB: closestEdge.pointB,
14899
- polygonId: polygon.id,
14900
- };
14901
- }
14902
- }
14903
- }
14904
- this.state = Object.assign(Object.assign({}, this.state), { highlightedEdge });
14905
- }
14906
- this.handleCanvasClick(point);
14907
- }
14908
- handleMouseMove(event, canvasRect) {
14909
- const point = this.getEventPoint(event, canvasRect);
14910
- if (this.interaction.isDragging && this.state.draggedPolygonId &&
14911
- this.state.draggedPointIndex !== null) {
14912
- const draggedPolygon = this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId);
14913
- if (!draggedPolygon || this.state.draggedPointIndex === null) {
14914
- return;
14915
- }
14916
- const newPoints = [...draggedPolygon.points];
14917
- newPoints[this.state.draggedPointIndex] = point;
14918
- const newPolygon = Object.assign(Object.assign({}, draggedPolygon), { points: newPoints });
14919
- newPolygon.area = this.calculatePolygonArea(newPolygon);
14920
- const newPolygons = this.state.polygons.map((polygon) => {
14921
- if (polygon.id === this.state.draggedPolygonId) {
14922
- return newPolygon;
14923
- }
14924
- return polygon;
14925
- });
14926
- this.state = Object.assign(Object.assign({}, this.state), { polygons: newPolygons, currentPolygon: newPolygon });
14927
- this.saveToHistory(newPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
14928
- this.notifyStateChange();
14929
- return;
14930
- }
14931
- if (this.state.selectedTool === types_1.Tool.MARK_ROOF_EDGE) {
14932
- let highlightedEdge = null;
14933
- let minDistance = Infinity;
14934
- for (const polygon of this.state.polygons) {
14935
- if (polygon.closed) {
14936
- const closestEdge = this.findClosestEdge(point, polygon);
14937
- if (closestEdge && closestEdge.distance < minDistance) {
14938
- minDistance = closestEdge.distance;
14939
- highlightedEdge = {
14940
- pointA: closestEdge.pointA,
14941
- pointB: closestEdge.pointB,
14942
- polygonId: polygon.id,
14943
- };
14944
- }
14945
- }
14946
- }
14947
- this.state = Object.assign(Object.assign({}, this.state), { mousePosition: point, highlightedEdge, isHoveringStartPoint: false });
14948
- this.notifyStateChange();
14949
- return;
14950
- }
14951
- if (this.state.selectedTool !== types_1.Tool.MOVE) {
14952
- const isHoveringStartPoint = this.state.currentPolygon &&
14953
- this.state.currentPolygon.points.length >= 3 &&
14954
- drawingUtils_1.CanvasUtils.isNearFirstPoint(this.state.currentPolygon, point, drawingUtils_1.CLOSE_DISTANCE);
14955
- this.state = Object.assign(Object.assign({}, this.state), { mousePosition: point, isHoveringStartPoint: !!isHoveringStartPoint, highlightedEdge: null });
14956
- this.notifyStateChange();
14957
- }
14958
- else {
14959
- this.state = Object.assign(Object.assign({}, this.state), { mousePosition: null, isHoveringStartPoint: false, highlightedEdge: null });
14960
- this.notifyStateChange();
14961
- }
14962
- }
14963
- handleTouchMove(event, canvasRect) {
14964
- if (!this.interaction.isDragging || !this.state.draggedPolygonId ||
14965
- this.state.draggedPointIndex === null) {
14966
- return;
14967
- }
14968
- const point = this.getEventPoint(event, canvasRect);
14969
- const draggedPolygon = this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId);
14970
- if (!draggedPolygon || this.state.draggedPointIndex === null) {
14971
- return;
14972
- }
14973
- const newPoints = [...draggedPolygon.points];
14974
- newPoints[this.state.draggedPointIndex] = point;
14975
- const newPolygon = Object.assign(Object.assign({}, draggedPolygon), { points: newPoints });
14976
- newPolygon.area = this.calculatePolygonArea(newPolygon);
14977
- const newPolygons = this.state.polygons.map((polygon) => {
14978
- if (polygon.id === this.state.draggedPolygonId) {
14979
- return newPolygon;
14980
- }
14981
- return polygon;
14982
- });
14983
- this.saveToHistory(newPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
14984
- this.state = Object.assign(Object.assign({}, this.state), { polygons: newPolygons, currentPolygon: newPolygon });
14985
- this.notifyStateChange();
14986
- }
14987
- handleMouseUp(event) {
14988
- if (this.interaction.isDragging) {
14989
- this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId) || null, this.state.positionedSolarPanels);
14990
- }
14991
- this.state = Object.assign(Object.assign({}, this.state), { draggedPointIndex: null, draggedPolygonId: null });
14992
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDragging: false });
14993
- this.notifyStateChange();
14994
- }
14995
- handleTouchEnd(event) {
14996
- if (this.interaction.isDragging) {
14997
- this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId) || null, this.state.positionedSolarPanels);
14998
- }
14999
- this.state = Object.assign(Object.assign({}, this.state), { draggedPointIndex: null, draggedPolygonId: null });
15000
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDragging: false });
15001
- this.notifyStateChange();
15002
- }
15003
- handleMouseLeave() {
15004
- this.state = Object.assign(Object.assign({}, this.state), { mousePosition: null, isHoveringStartPoint: false, highlightedEdge: null });
15005
- this.notifyStateChange();
15006
- }
15007
- setSelectedTool(tool) {
15008
- this.state = Object.assign(Object.assign({}, this.state), { selectedTool: tool, mousePosition: null, isHoveringStartPoint: false, highlightedEdge: null });
15009
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: false });
15010
- this.notifyStateChange();
15011
- }
15012
- setPolygons(polygons) {
15013
- this.state = Object.assign(Object.assign({}, this.state), { polygons: [...polygons], currentPolygon: null, highlightedEdge: null });
15014
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: false });
15015
- this.saveToHistory(polygons, null, null, this.state.positionedSolarPanels);
15016
- this.notifyStateChange();
15017
- }
15018
- getPolygons() {
15019
- return [...this.state.polygons];
15020
- }
15021
- clearPolygons() {
15022
- this.state = Object.assign(Object.assign({}, this.state), { polygons: [], currentPolygon: null, highlightedEdge: null });
15023
- this.interaction = Object.assign(Object.assign({}, this.interaction), { isDrawing: false });
15024
- this.saveToHistory([], null, null, this.state.positionedSolarPanels);
15025
- this.notifyStateChange();
15026
- }
15027
- getSelectedPolygon() {
15028
- if (!this.state.currentPolygon)
15029
- return null;
15030
- return this.state.polygons.find((p) => { var _a; return p.id === ((_a = this.state.currentPolygon) === null || _a === void 0 ? void 0 : _a.id); }) || null;
15031
- }
15032
- clearSelection() {
15033
- this.state = Object.assign(Object.assign({}, this.state), { currentPolygon: null });
15034
- this.notifyStateChange();
15035
- }
15036
- getHighlightedEdge() {
15037
- return this.state.highlightedEdge;
15038
- }
15039
- }
15040
- exports.CanvasManager = CanvasManager;
15041
- const createCanvasManager = (config) => {
15042
- return new CanvasManager(config);
15043
- };
15044
- exports.createCanvasManager = createCanvasManager;
15045
- const useCanvasManager = (config) => {
15046
- return (0, exports.createCanvasManager)(config);
15047
- };
15048
- exports.useCanvasManager = useCanvasManager;
15049
-
15050
- } (canvasUpdate));
15051
- return canvasUpdate;
14927
+ function calculatePolygonArea(points) {
14928
+ let area = 0;
14929
+ for (let i = 0; i < points.length; i++) {
14930
+ const j = (i + 1) % points.length;
14931
+ area += points[i].x * points[j].y;
14932
+ area -= points[j].x * points[i].y;
14933
+ }
14934
+ return Math.abs(area / 2);
15052
14935
  }
15053
14936
 
15054
- var rgbTiff = {};
15055
-
15056
- var hasRequiredRgbTiff;
15057
-
15058
- function requireRgbTiff () {
15059
- if (hasRequiredRgbTiff) return rgbTiff;
15060
- hasRequiredRgbTiff = 1;
15061
- Object.defineProperty(rgbTiff, "__esModule", { value: true });
15062
- rgbTiff.renderRGB = renderRGB;
15063
- rgbTiff.renderCombinedWithZoom = renderCombinedWithZoom;
15064
- function renderRGB(rgb, mask, canvas) {
15065
- if (!canvas) {
15066
- canvas = document.createElement("canvas");
15067
- }
15068
- canvas.width = mask ? mask.width : rgb.width;
15069
- canvas.height = mask ? mask.height : rgb.height;
15070
- const dw = rgb.width / canvas.width;
15071
- const dh = rgb.height / canvas.height;
15072
- const ctx = canvas.getContext("2d");
15073
- const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
15074
- for (let y = 0; y < canvas.height; y++) {
15075
- for (let x = 0; x < canvas.width; x++) {
15076
- const rgbIdx = Math.floor(y * dh) * rgb.width + Math.floor(x * dw);
15077
- const maskIdx = y * canvas.width + x;
15078
- const imgIdx = y * canvas.width * 4 + x * 4;
15079
- const factor = 0.8;
15080
- img.data[imgIdx + 0] = Math.round(rgb.rasters[0][rgbIdx] * factor);
15081
- img.data[imgIdx + 1] = Math.round(rgb.rasters[1][rgbIdx] * factor);
15082
- img.data[imgIdx + 2] = Math.round(rgb.rasters[2][rgbIdx] * factor);
15083
- img.data[imgIdx + 3] = mask
15084
- ? mask.rasters[0][maskIdx] * 255
15085
- : 255;
15086
- }
15087
- }
15088
- ctx.putImageData(img, 0, 0);
15089
- return canvas;
15090
- }
15091
- function renderCombinedWithZoom({ rgb, zoom = 1, canvas, }) {
15092
- const rgbCanvas = renderRGB(rgb, undefined, canvas);
15093
- const ctx = rgbCanvas.getContext("2d");
15094
- const imgData = ctx.getImageData(0, 0, rgbCanvas.width, rgbCanvas.height);
15095
- const width = rgbCanvas.width;
15096
- const height = rgbCanvas.height;
15097
- const zoomedWidth = Math.floor(width * zoom);
15098
- const zoomedHeight = Math.floor(height * zoom);
15099
- const startX = Math.floor((width - zoomedWidth) / 2);
15100
- const startY = Math.floor((height - zoomedHeight) / 2);
15101
- const zoomedImgData = new ImageData(zoomedWidth, zoomedHeight);
15102
- for (let y = 0; y < height; y++) {
15103
- for (let x = 0; x < width; x++) {
15104
- const i = (y * width + x) * 4;
15105
- if (x >= startX && x < startX + zoomedWidth &&
15106
- y >= startY && y < startY + zoomedHeight) {
15107
- const zoomedX = x - startX;
15108
- const zoomedY = y - startY;
15109
- const zoomedI = (zoomedY * zoomedWidth + zoomedX) * 4;
15110
- zoomedImgData.data[zoomedI] = imgData.data[i];
15111
- zoomedImgData.data[zoomedI + 1] = imgData.data[i + 1];
15112
- zoomedImgData.data[zoomedI + 2] = imgData.data[i + 2];
15113
- zoomedImgData.data[zoomedI + 3] = 255;
15114
- }
15115
- }
15116
- }
15117
- rgbCanvas.width = zoomedWidth;
15118
- rgbCanvas.height = zoomedHeight;
15119
- ctx.putImageData(zoomedImgData, 0, 0);
15120
- return rgbCanvas;
15121
- }
15122
-
15123
- return rgbTiff;
14937
+ const VERSION = "0.0.4";
14938
+ const DEFAULT_MANAGER_CONFIG = {
14939
+ closeDistance: 7,
14940
+ pointHoverRadius: 8,
14941
+ previewPointRadius: 5,
14942
+ closestEdgeThreshold: 10,
14943
+ };
14944
+ class CanvasManager {
14945
+ constructor(canvasConfig, managerConfig) {
14946
+ this.canvasConfig = canvasConfig;
14947
+ this.managerConfig = {
14948
+ ...DEFAULT_MANAGER_CONFIG,
14949
+ ...managerConfig,
14950
+ };
14951
+ this.state = {
14952
+ polygons: [],
14953
+ currentPolygon: null,
14954
+ selectedTool: Tool.ROOF_POLYGON,
14955
+ draggedPointIndex: null,
14956
+ draggedPolygonId: null,
14957
+ canvasSize: { width: 800, height: 600 },
14958
+ coordinateSystem: canvasConfig.coordinateSystem,
14959
+ mousePosition: null,
14960
+ isHoveringStartPoint: false,
14961
+ highlightedEdge: null,
14962
+ positionedSolarPanels: {},
14963
+ };
14964
+ this.interaction = {
14965
+ isDrawing: false,
14966
+ isDragging: false,
14967
+ lastTouchTime: 0,
14968
+ };
14969
+ this.history = [{
14970
+ polygons: [],
14971
+ currentPolygon: null,
14972
+ changedPolygon: null,
14973
+ positionedSolarPanels: {},
14974
+ }];
14975
+ this.historyIndex = 0;
14976
+ this.meterToPixelRatio = null;
14977
+ }
14978
+ notifyStateChange() {
14979
+ this.canvasConfig.onStateChange?.(this.state);
14980
+ }
14981
+ saveToHistory(polygons, currentPolygon = null, changedPolygon = null, positionedSolarPanels = {}) {
14982
+ const newHistory = this.history.slice(0, this.historyIndex + 1);
14983
+ newHistory.push({
14984
+ polygons: [...polygons],
14985
+ currentPolygon: currentPolygon ? { ...currentPolygon } : null,
14986
+ changedPolygon: changedPolygon ? { ...changedPolygon } : null,
14987
+ positionedSolarPanels: positionedSolarPanels,
14988
+ });
14989
+ this.history = newHistory;
14990
+ this.historyIndex = newHistory.length - 1;
14991
+ }
14992
+ generateId() {
14993
+ return `polygon_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
14994
+ }
14995
+ setMeterToPixelRatio(meterToPixelRatio) {
14996
+ this.meterToPixelRatio = meterToPixelRatio;
14997
+ }
14998
+ getCurrentPolygon() {
14999
+ return this.state.currentPolygon;
15000
+ }
15001
+ updateOnPolygonChangeCallback(callback) {
15002
+ this.canvasConfig.onPolygonChange = callback;
15003
+ }
15004
+ getObstacles() {
15005
+ return this.state.polygons.filter((p) => p.type === PolygonType.OBSTRUCTION);
15006
+ }
15007
+ getHistory() {
15008
+ return this.history;
15009
+ }
15010
+ getDebugString() {
15011
+ return VERSION;
15012
+ }
15013
+ undo() {
15014
+ if (this.historyIndex > 0) {
15015
+ const newIndex = this.historyIndex - 1;
15016
+ this.historyIndex = newIndex;
15017
+ const previousState = this.history[newIndex];
15018
+ this.state = {
15019
+ ...this.state,
15020
+ polygons: [...previousState.polygons],
15021
+ currentPolygon: previousState.currentPolygon
15022
+ ? { ...previousState.currentPolygon }
15023
+ : null,
15024
+ positionedSolarPanels: {
15025
+ ...previousState.positionedSolarPanels,
15026
+ },
15027
+ };
15028
+ this.interaction = {
15029
+ ...this.interaction,
15030
+ isDrawing: previousState.currentPolygon !== null,
15031
+ };
15032
+ if (!this.state.currentPolygon?.closed) {
15033
+ if (this.state.currentPolygon?.type === PolygonType.ROOF) {
15034
+ this.setSelectedTool(Tool.ROOF_POLYGON);
15035
+ }
15036
+ else {
15037
+ this.state.selectedTool = Tool.OBSTRUCTION_POLYGON;
15038
+ }
15039
+ }
15040
+ this.notifyStateChange();
15041
+ return true;
15042
+ }
15043
+ return false;
15044
+ }
15045
+ setPositionedSolarPanel(polygonId, positionedSolarPanels) {
15046
+ const newPositionedSolarPanels = {
15047
+ ...this.state.positionedSolarPanels,
15048
+ [polygonId]: positionedSolarPanels,
15049
+ };
15050
+ this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.currentPolygon, newPositionedSolarPanels);
15051
+ this.state.positionedSolarPanels = newPositionedSolarPanels;
15052
+ this.notifyStateChange();
15053
+ }
15054
+ getPositionedSolarPanels(polygonId) {
15055
+ return this.state.positionedSolarPanels[polygonId] || null;
15056
+ }
15057
+ getAllPositionedSolarPanels() {
15058
+ return this.state.positionedSolarPanels;
15059
+ }
15060
+ canUndo() {
15061
+ return this.historyIndex > 0;
15062
+ }
15063
+ getState() {
15064
+ return { ...this.state };
15065
+ }
15066
+ getInteraction() {
15067
+ return { ...this.interaction };
15068
+ }
15069
+ updateCanvasSize(size) {
15070
+ this.state = {
15071
+ ...this.state,
15072
+ canvasSize: size,
15073
+ };
15074
+ this.notifyStateChange();
15075
+ }
15076
+ getEventPoint(event, canvasRect) {
15077
+ let clientX, clientY;
15078
+ if ("touches" in event && event.touches.length > 0) {
15079
+ clientX = event.touches[0].clientX;
15080
+ clientY = event.touches[0].clientY;
15081
+ }
15082
+ else if ("changedTouches" in event && event.changedTouches.length > 0) {
15083
+ clientX = event.changedTouches[0].clientX;
15084
+ clientY = event.changedTouches[0].clientY;
15085
+ }
15086
+ else {
15087
+ clientX = event.clientX;
15088
+ clientY = event.clientY;
15089
+ }
15090
+ return CanvasUtils.screenToCanvas(new Point(clientX, clientY), canvasRect, this.canvasConfig.coordinateSystem);
15091
+ }
15092
+ getPolygon(id) {
15093
+ return this.state.polygons.find((p) => p.id === id) || null;
15094
+ }
15095
+ calculatePolygonArea(polygon) {
15096
+ const pixelsRatio = this.meterToPixelRatio || 1;
15097
+ const area = calculatePolygonArea(polygon.points) * pixelsRatio *
15098
+ pixelsRatio;
15099
+ return area;
15100
+ }
15101
+ findClosestEdge(point, polygon, threshold) {
15102
+ if (!polygon || !polygon.closed || polygon.points.length < 3) {
15103
+ return null;
15104
+ }
15105
+ let closestEdge = null;
15106
+ let minDistance = Infinity;
15107
+ for (let i = 0; i < polygon.points.length; i++) {
15108
+ const pointA = polygon.points[i];
15109
+ const pointB = polygon.points[(i + 1) % polygon.points.length];
15110
+ const distanceA = Math.sqrt(Math.pow(point.x - pointA.x, 2) +
15111
+ Math.pow(point.y - pointA.y, 2));
15112
+ const distanceB = Math.sqrt(Math.pow(point.x - pointB.x, 2) +
15113
+ Math.pow(point.y - pointB.y, 2));
15114
+ const edgeLength = Math.sqrt(Math.pow(pointA.x - pointB.x, 2) +
15115
+ Math.pow(pointA.y - pointB.y, 2));
15116
+ const distance = distanceA + distanceB - edgeLength;
15117
+ if (distance < minDistance && distance < threshold) {
15118
+ minDistance = distance;
15119
+ closestEdge = { pointA, pointB, distance };
15120
+ }
15121
+ }
15122
+ return closestEdge;
15123
+ }
15124
+ calculateEdgeAzimuth(pointA, pointB) {
15125
+ const dx = pointB.x - pointA.x;
15126
+ const dy = pointB.y - pointA.y;
15127
+ let angle = Math.atan2(dy, dx) * (180 / Math.PI);
15128
+ angle += 90;
15129
+ if (angle < 0) {
15130
+ angle += 360;
15131
+ }
15132
+ else if (angle >= 360) {
15133
+ angle -= 360;
15134
+ }
15135
+ return angle;
15136
+ }
15137
+ updatePolygon(polygon) {
15138
+ const newPolygons = this.state.polygons.map((p) => {
15139
+ if (p.id === polygon.id) {
15140
+ return polygon;
15141
+ }
15142
+ return p;
15143
+ });
15144
+ this.state = {
15145
+ ...this.state,
15146
+ polygons: newPolygons,
15147
+ currentPolygon: polygon,
15148
+ };
15149
+ }
15150
+ convertMetersToPixels(meters) {
15151
+ return meters / (this.meterToPixelRatio || 1);
15152
+ }
15153
+ closePolygon(polygon) {
15154
+ if (!polygon) {
15155
+ return;
15156
+ }
15157
+ const closedPolygon = {
15158
+ ...polygon,
15159
+ area: this.calculatePolygonArea(polygon),
15160
+ closed: true,
15161
+ };
15162
+ const newPolygons = [...this.state.polygons, closedPolygon];
15163
+ this.state = {
15164
+ ...this.state,
15165
+ polygons: newPolygons,
15166
+ currentPolygon: closedPolygon,
15167
+ };
15168
+ this.interaction = { ...this.interaction, isDrawing: false };
15169
+ this.saveToHistory(newPolygons, closedPolygon, closedPolygon, this.state.positionedSolarPanels);
15170
+ this.notifyStateChange();
15171
+ this.canvasConfig.onPolygonChange?.(closedPolygon);
15172
+ }
15173
+ handleCanvasClick(point) {
15174
+ if (this.state.selectedTool === Tool.MOVE) {
15175
+ const nearestPoint = CanvasUtils.findNearestPoint(point, this.state.polygons, this.managerConfig.closeDistance);
15176
+ if (nearestPoint) {
15177
+ this.state = {
15178
+ ...this.state,
15179
+ draggedPointIndex: nearestPoint.pointIndex,
15180
+ draggedPolygonId: nearestPoint.polygon.id,
15181
+ currentPolygon: nearestPoint.polygon,
15182
+ };
15183
+ this.interaction = { ...this.interaction, isDragging: true };
15184
+ this.notifyStateChange();
15185
+ return;
15186
+ }
15187
+ const clickedPolygon = CanvasUtils.findPolygonAtPoint(point, this.state.polygons);
15188
+ if (clickedPolygon) {
15189
+ this.state = {
15190
+ ...this.state,
15191
+ currentPolygon: clickedPolygon,
15192
+ };
15193
+ this.notifyStateChange();
15194
+ return;
15195
+ }
15196
+ this.state = {
15197
+ ...this.state,
15198
+ currentPolygon: null,
15199
+ };
15200
+ this.notifyStateChange();
15201
+ return;
15202
+ }
15203
+ if (this.state.selectedTool === Tool.MARK_ROOF_EDGE) {
15204
+ if (this.state.highlightedEdge) {
15205
+ const polygonId = this.state.highlightedEdge.polygonId;
15206
+ const edge = this.state.highlightedEdge;
15207
+ const azimuth = this.calculateEdgeAzimuth(edge.pointA, edge.pointB);
15208
+ const polygon = this.state.polygons.find((p) => p.id === polygonId);
15209
+ if (!polygon) {
15210
+ return;
15211
+ }
15212
+ const newPolygon = {
15213
+ ...polygon,
15214
+ azimuth,
15215
+ };
15216
+ const updatedPolygons = this.state.polygons.map((polygon) => {
15217
+ if (polygon.id === polygonId) {
15218
+ return newPolygon;
15219
+ }
15220
+ return polygon;
15221
+ });
15222
+ this.state = {
15223
+ ...this.state,
15224
+ polygons: updatedPolygons,
15225
+ currentPolygon: newPolygon || null,
15226
+ };
15227
+ this.saveToHistory(updatedPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
15228
+ this.notifyStateChange();
15229
+ this.canvasConfig.onPolygonChange?.(newPolygon);
15230
+ this.canvasConfig.onLowerRoofEdgeMarked?.(newPolygon);
15231
+ }
15232
+ return;
15233
+ }
15234
+ const polygonType = this.state.selectedTool === Tool.ROOF_POLYGON
15235
+ ? PolygonType.ROOF
15236
+ : PolygonType.OBSTRUCTION;
15237
+ if (!this.state.currentPolygon || this.state.currentPolygon.closed) {
15238
+ const newPolygon = {
15239
+ id: this.generateId(),
15240
+ points: [point],
15241
+ type: polygonType,
15242
+ closed: false,
15243
+ };
15244
+ this.state = { ...this.state, currentPolygon: newPolygon };
15245
+ this.interaction = { ...this.interaction, isDrawing: true };
15246
+ this.saveToHistory(this.state.polygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
15247
+ this.notifyStateChange();
15248
+ }
15249
+ else {
15250
+ const shouldClose = CanvasUtils.isNearFirstPoint(this.state.currentPolygon, point, this.managerConfig.closeDistance);
15251
+ if (shouldClose && this.state.currentPolygon.points.length >= 3) {
15252
+ this.closePolygon(this.state.currentPolygon);
15253
+ }
15254
+ else {
15255
+ const updatedPolygon = {
15256
+ ...this.state.currentPolygon,
15257
+ points: [...this.state.currentPolygon.points, point],
15258
+ };
15259
+ this.state = { ...this.state, currentPolygon: updatedPolygon };
15260
+ this.saveToHistory(this.state.polygons, updatedPolygon, updatedPolygon, this.state.positionedSolarPanels);
15261
+ this.notifyStateChange();
15262
+ }
15263
+ }
15264
+ }
15265
+ handleMouseDown(event, canvasRect) {
15266
+ const point = this.getEventPoint(event, canvasRect);
15267
+ this.handleCanvasClick(point);
15268
+ }
15269
+ handleTouchStart(event, canvasRect) {
15270
+ const now = Date.now();
15271
+ if (now - this.interaction.lastTouchTime < 300) {
15272
+ return;
15273
+ }
15274
+ this.interaction = { ...this.interaction, lastTouchTime: now };
15275
+ const point = this.getEventPoint(event, canvasRect);
15276
+ if (this.state.selectedTool === Tool.MARK_ROOF_EDGE) {
15277
+ let highlightedEdge = null;
15278
+ let minDistance = Infinity;
15279
+ for (const polygon of this.state.polygons) {
15280
+ if (polygon.closed) {
15281
+ const closestEdge = this.findClosestEdge(point, polygon, this.managerConfig.closestEdgeThreshold);
15282
+ if (closestEdge && closestEdge.distance < minDistance) {
15283
+ minDistance = closestEdge.distance;
15284
+ highlightedEdge = {
15285
+ pointA: closestEdge.pointA,
15286
+ pointB: closestEdge.pointB,
15287
+ polygonId: polygon.id,
15288
+ };
15289
+ }
15290
+ }
15291
+ }
15292
+ this.state = {
15293
+ ...this.state,
15294
+ highlightedEdge,
15295
+ };
15296
+ }
15297
+ this.handleCanvasClick(point);
15298
+ }
15299
+ handleMouseMove(event, canvasRect) {
15300
+ const point = this.getEventPoint(event, canvasRect);
15301
+ if (this.interaction.isDragging && this.state.draggedPolygonId &&
15302
+ this.state.draggedPointIndex !== null) {
15303
+ const draggedPolygon = this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId);
15304
+ if (!draggedPolygon || this.state.draggedPointIndex === null) {
15305
+ return;
15306
+ }
15307
+ const newPoints = [...draggedPolygon.points];
15308
+ newPoints[this.state.draggedPointIndex] = point;
15309
+ const newPolygon = {
15310
+ ...draggedPolygon,
15311
+ points: newPoints,
15312
+ };
15313
+ newPolygon.area = this.calculatePolygonArea(newPolygon);
15314
+ const newPolygons = this.state.polygons.map((polygon) => {
15315
+ if (polygon.id === this.state.draggedPolygonId) {
15316
+ return newPolygon;
15317
+ }
15318
+ return polygon;
15319
+ });
15320
+ this.state = {
15321
+ ...this.state,
15322
+ polygons: newPolygons,
15323
+ currentPolygon: newPolygon,
15324
+ };
15325
+ this.saveToHistory(newPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
15326
+ this.notifyStateChange();
15327
+ return;
15328
+ }
15329
+ if (this.state.selectedTool === Tool.MARK_ROOF_EDGE) {
15330
+ let highlightedEdge = null;
15331
+ let minDistance = Infinity;
15332
+ for (const polygon of this.state.polygons) {
15333
+ if (polygon.closed) {
15334
+ const closestEdge = this.findClosestEdge(point, polygon, this.managerConfig.closestEdgeThreshold);
15335
+ if (closestEdge && closestEdge.distance < minDistance) {
15336
+ minDistance = closestEdge.distance;
15337
+ highlightedEdge = {
15338
+ pointA: closestEdge.pointA,
15339
+ pointB: closestEdge.pointB,
15340
+ polygonId: polygon.id,
15341
+ };
15342
+ }
15343
+ }
15344
+ }
15345
+ this.state = {
15346
+ ...this.state,
15347
+ mousePosition: point,
15348
+ highlightedEdge,
15349
+ isHoveringStartPoint: false,
15350
+ };
15351
+ this.notifyStateChange();
15352
+ return;
15353
+ }
15354
+ if (this.state.selectedTool !== Tool.MOVE) {
15355
+ const isHoveringStartPoint = this.state.currentPolygon &&
15356
+ this.state.currentPolygon.points.length >= 3 &&
15357
+ CanvasUtils.isNearFirstPoint(this.state.currentPolygon, point, this.managerConfig.closeDistance);
15358
+ this.state = {
15359
+ ...this.state,
15360
+ mousePosition: point,
15361
+ isHoveringStartPoint: !!isHoveringStartPoint,
15362
+ highlightedEdge: null,
15363
+ };
15364
+ this.notifyStateChange();
15365
+ }
15366
+ else {
15367
+ this.state = {
15368
+ ...this.state,
15369
+ mousePosition: null,
15370
+ isHoveringStartPoint: false,
15371
+ highlightedEdge: null,
15372
+ };
15373
+ this.notifyStateChange();
15374
+ }
15375
+ }
15376
+ handleTouchMove(event, canvasRect) {
15377
+ if (!this.interaction.isDragging || !this.state.draggedPolygonId ||
15378
+ this.state.draggedPointIndex === null) {
15379
+ return;
15380
+ }
15381
+ const point = this.getEventPoint(event, canvasRect);
15382
+ const draggedPolygon = this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId);
15383
+ if (!draggedPolygon || this.state.draggedPointIndex === null) {
15384
+ return;
15385
+ }
15386
+ const newPoints = [...draggedPolygon.points];
15387
+ newPoints[this.state.draggedPointIndex] = point;
15388
+ const newPolygon = {
15389
+ ...draggedPolygon,
15390
+ points: newPoints,
15391
+ };
15392
+ newPolygon.area = this.calculatePolygonArea(newPolygon);
15393
+ const newPolygons = this.state.polygons.map((polygon) => {
15394
+ if (polygon.id === this.state.draggedPolygonId) {
15395
+ return newPolygon;
15396
+ }
15397
+ return polygon;
15398
+ });
15399
+ this.saveToHistory(newPolygons, newPolygon, newPolygon, this.state.positionedSolarPanels);
15400
+ this.state = {
15401
+ ...this.state,
15402
+ polygons: newPolygons,
15403
+ currentPolygon: newPolygon,
15404
+ };
15405
+ this.notifyStateChange();
15406
+ }
15407
+ handleMouseUp(event) {
15408
+ if (this.interaction.isDragging) {
15409
+ this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId) || null, this.state.positionedSolarPanels);
15410
+ }
15411
+ this.state = {
15412
+ ...this.state,
15413
+ draggedPointIndex: null,
15414
+ draggedPolygonId: null,
15415
+ };
15416
+ this.interaction = { ...this.interaction, isDragging: false };
15417
+ this.notifyStateChange();
15418
+ }
15419
+ handleTouchEnd(event) {
15420
+ if (this.interaction.isDragging) {
15421
+ this.saveToHistory(this.state.polygons, this.state.currentPolygon, this.state.polygons.find((polygon) => polygon.id === this.state.draggedPolygonId) || null, this.state.positionedSolarPanels);
15422
+ }
15423
+ this.state = {
15424
+ ...this.state,
15425
+ draggedPointIndex: null,
15426
+ draggedPolygonId: null,
15427
+ };
15428
+ this.interaction = { ...this.interaction, isDragging: false };
15429
+ this.notifyStateChange();
15430
+ }
15431
+ handleMouseLeave() {
15432
+ this.state = {
15433
+ ...this.state,
15434
+ mousePosition: null,
15435
+ isHoveringStartPoint: false,
15436
+ highlightedEdge: null,
15437
+ };
15438
+ this.notifyStateChange();
15439
+ }
15440
+ setSelectedTool(tool) {
15441
+ this.state = {
15442
+ ...this.state,
15443
+ selectedTool: tool,
15444
+ mousePosition: null,
15445
+ isHoveringStartPoint: false,
15446
+ highlightedEdge: null,
15447
+ };
15448
+ this.interaction = { ...this.interaction, isDrawing: false };
15449
+ this.notifyStateChange();
15450
+ }
15451
+ setPolygons(polygons) {
15452
+ this.state = {
15453
+ ...this.state,
15454
+ polygons: [...polygons],
15455
+ currentPolygon: null,
15456
+ highlightedEdge: null,
15457
+ };
15458
+ this.interaction = { ...this.interaction, isDrawing: false };
15459
+ this.saveToHistory(polygons, null, null, this.state.positionedSolarPanels);
15460
+ this.notifyStateChange();
15461
+ }
15462
+ getPolygons() {
15463
+ return [...this.state.polygons];
15464
+ }
15465
+ clearPolygons() {
15466
+ this.state = {
15467
+ ...this.state,
15468
+ polygons: [],
15469
+ currentPolygon: null,
15470
+ highlightedEdge: null,
15471
+ };
15472
+ this.interaction = { ...this.interaction, isDrawing: false };
15473
+ this.saveToHistory([], null, null, this.state.positionedSolarPanels);
15474
+ this.notifyStateChange();
15475
+ }
15476
+ getSelectedPolygon() {
15477
+ if (!this.state.currentPolygon)
15478
+ return null;
15479
+ return this.state.polygons.find((p) => p.id === this.state.currentPolygon?.id) || null;
15480
+ }
15481
+ clearSelection() {
15482
+ this.state = {
15483
+ ...this.state,
15484
+ currentPolygon: null,
15485
+ };
15486
+ this.notifyStateChange();
15487
+ }
15488
+ getHighlightedEdge() {
15489
+ return this.state.highlightedEdge;
15490
+ }
15124
15491
  }
15125
15492
 
15126
- var hasRequiredDist;
15127
-
15128
- function requireDist () {
15129
- if (hasRequiredDist) return dist;
15130
- hasRequiredDist = 1;
15131
- (function (exports) {
15132
- var __createBinding = (dist && dist.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15133
- if (k2 === undefined) k2 = k;
15134
- var desc = Object.getOwnPropertyDescriptor(m, k);
15135
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15136
- desc = { enumerable: true, get: function() { return m[k]; } };
15137
- }
15138
- Object.defineProperty(o, k2, desc);
15139
- }) : (function(o, m, k, k2) {
15140
- if (k2 === undefined) k2 = k;
15141
- o[k2] = m[k];
15142
- }));
15143
- var __exportStar = (dist && dist.__exportStar) || function(m, exports) {
15144
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15145
- };
15146
- Object.defineProperty(exports, "__esModule", { value: true });
15147
- exports.calculatePolygonOrientation = exports.calculatePolygonArea = exports.calculatePolygonAngle = exports.PREVIEW_POINT_RADIUS = exports.POINT_RADIUS = exports.POINT_HOVER_RADIUS = exports.CLOSE_DISTANCE = exports.CanvasUtils = exports.CANVAS_COLORS = exports.useCanvasManager = exports.createCanvasManager = exports.CanvasManager = exports.Tool = void 0;
15148
- var types_1 = requireTypes();
15149
- Object.defineProperty(exports, "Tool", { enumerable: true, get: function () { return types_1.Tool; } });
15150
- var canvasUpdate_1 = requireCanvasUpdate();
15151
- Object.defineProperty(exports, "CanvasManager", { enumerable: true, get: function () { return canvasUpdate_1.CanvasManager; } });
15152
- Object.defineProperty(exports, "createCanvasManager", { enumerable: true, get: function () { return canvasUpdate_1.createCanvasManager; } });
15153
- Object.defineProperty(exports, "useCanvasManager", { enumerable: true, get: function () { return canvasUpdate_1.useCanvasManager; } });
15154
- var drawingUtils_1 = requireDrawingUtils();
15155
- Object.defineProperty(exports, "CANVAS_COLORS", { enumerable: true, get: function () { return drawingUtils_1.CANVAS_COLORS; } });
15156
- Object.defineProperty(exports, "CanvasUtils", { enumerable: true, get: function () { return drawingUtils_1.CanvasUtils; } });
15157
- Object.defineProperty(exports, "CLOSE_DISTANCE", { enumerable: true, get: function () { return drawingUtils_1.CLOSE_DISTANCE; } });
15158
- Object.defineProperty(exports, "POINT_HOVER_RADIUS", { enumerable: true, get: function () { return drawingUtils_1.POINT_HOVER_RADIUS; } });
15159
- Object.defineProperty(exports, "POINT_RADIUS", { enumerable: true, get: function () { return drawingUtils_1.POINT_RADIUS; } });
15160
- Object.defineProperty(exports, "PREVIEW_POINT_RADIUS", { enumerable: true, get: function () { return drawingUtils_1.PREVIEW_POINT_RADIUS; } });
15161
- var polygonUtils_1 = requirePolygonUtils();
15162
- Object.defineProperty(exports, "calculatePolygonAngle", { enumerable: true, get: function () { return polygonUtils_1.calculatePolygonAngle; } });
15163
- Object.defineProperty(exports, "calculatePolygonArea", { enumerable: true, get: function () { return polygonUtils_1.calculatePolygonArea; } });
15164
- Object.defineProperty(exports, "calculatePolygonOrientation", { enumerable: true, get: function () { return polygonUtils_1.calculatePolygonOrientation; } });
15165
- __exportStar(requireRgbTiff(), exports);
15166
-
15167
- } (dist));
15168
- return dist;
15493
+ function renderRGB(rgb, mask, canvas) {
15494
+ if (!canvas) {
15495
+ canvas = document.createElement("canvas");
15496
+ }
15497
+ canvas.width = rgb.width;
15498
+ canvas.height = rgb.height;
15499
+ const dw = rgb.width / canvas.width;
15500
+ const dh = rgb.height / canvas.height;
15501
+ const ctx = canvas.getContext("2d");
15502
+ const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
15503
+ for (let y = 0; y < canvas.height; y++) {
15504
+ for (let x = 0; x < canvas.width; x++) {
15505
+ const rgbIdx = Math.floor(y * dh) * rgb.width + Math.floor(x * dw);
15506
+ const imgIdx = y * canvas.width * 4 + x * 4;
15507
+ const factor = 0.8;
15508
+ img.data[imgIdx + 0] = Math.round(rgb.rasters[0][rgbIdx] * factor);
15509
+ img.data[imgIdx + 1] = Math.round(rgb.rasters[1][rgbIdx] * factor);
15510
+ img.data[imgIdx + 2] = Math.round(rgb.rasters[2][rgbIdx] * factor);
15511
+ img.data[imgIdx + 3] = 255;
15512
+ }
15513
+ }
15514
+ ctx.putImageData(img, 0, 0);
15515
+ return canvas;
15169
15516
  }
15170
15517
 
15171
- var distExports = requireDist();
15172
-
15173
15518
  const outputCss = "/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-100: oklch(93.6% 0.032 17.717);\n --color-red-300: oklch(80.8% 0.114 19.571);\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-md: 28rem;\n --container-2xl: 42rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --radius-lg: 0.5rem;\n --radius-4xl: 2rem;\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n --color-primary: hsl(41 51% 90%);\n --color-primary-foreground: hsl(0 0% 0%);\n --color-secondary: hsl(28 100% 8%);\n --color-secondary-foreground: hsl(0 0% 100%);\n --color-tertiary: hsl(28 100% 29%);\n --color-tertiary-foreground: hsl(0 0% 100%);\n --color-muted: hsl(0 0% 100%);\n --color-muted-foreground: hsl(0 0% 0%);\n --color-error: hsl(0 100% 50%);\n --color-error-foreground: hsl(0 0% 100%);\n --color-surface: hsl(0 0% 88%);\n --color-surface-hover: hsl(0 0% 82%);\n --color-surface-active: hsl(0 0% 64%);\n --color-text-muted: hsl(0 0% 45%);\n --color-text-secondary: hsl(0 0% 55%);\n --color-text-placeholder: hsl(0 0% 73%);\n --color-border: hsl(0 0% 82%);\n --color-border-light: hsl(0 0% 93%);\n --color-success: hsl(142 76% 36%);\n --color-info: hsl(221 83% 53%);\n --color-hover: hsl(0 0% 88%);\n --color-hover-dark: hsl(28 100% 8%);\n --color-overlay: rgba(0, 0, 0, 0.24);\n --color-tooltip: hsl(0 0% 13%);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .fixed {\n position: fixed;\n }\n .relative {\n position: relative;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .bottom-4 {\n bottom: calc(var(--spacing) * 4);\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-3 {\n left: calc(var(--spacing) * 3);\n }\n .z-10 {\n z-index: 10;\n }\n .z-20 {\n z-index: 20;\n }\n .z-50 {\n z-index: 50;\n }\n .container {\n width: 100%;\n @media (width >= 40rem) {\n max-width: 40rem;\n }\n @media (width >= 48rem) {\n max-width: 48rem;\n }\n @media (width >= 64rem) {\n max-width: 64rem;\n }\n @media (width >= 80rem) {\n max-width: 80rem;\n }\n @media (width >= 96rem) {\n max-width: 96rem;\n }\n }\n .m-auto {\n margin: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\n }\n .mt-4 {\n margin-top: calc(var(--spacing) * 4);\n }\n .mt-6 {\n margin-top: calc(var(--spacing) * 6);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .mb-6 {\n margin-bottom: calc(var(--spacing) * 6);\n }\n .block {\n display: block;\n }\n .flex {\n display: flex;\n }\n .grid {\n display: grid;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-16 {\n height: calc(var(--spacing) * 16);\n }\n .h-48 {\n height: calc(var(--spacing) * 48);\n }\n .h-64 {\n height: calc(var(--spacing) * 64);\n }\n .h-\\[400px\\] {\n height: 400px;\n }\n .h-full {\n height: 100%;\n }\n .max-h-full {\n max-height: 100%;\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-16 {\n width: calc(var(--spacing) * 16);\n }\n .w-20 {\n width: calc(var(--spacing) * 20);\n }\n .w-64 {\n width: calc(var(--spacing) * 64);\n }\n .w-full {\n width: 100%;\n }\n .max-w-2xl {\n max-width: var(--container-2xl);\n }\n .max-w-md {\n max-width: var(--container-md);\n }\n .flex-1 {\n flex: 1;\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .translate-y-0 {\n --tw-translate-y: calc(var(--spacing) * 0);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .translate-y-2 {\n --tw-translate-y: calc(var(--spacing) * 2);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .scale-95 {\n --tw-scale-x: 95%;\n --tw-scale-y: 95%;\n --tw-scale-z: 95%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n .scale-100 {\n --tw-scale-x: 100%;\n --tw-scale-y: 100%;\n --tw-scale-z: 100%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .resize {\n resize: both;\n }\n .appearance-none {\n appearance: none;\n }\n .grid-cols-1 {\n grid-template-columns: repeat(1, minmax(0, 1fr));\n }\n .grid-cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n .grid-rows-2 {\n grid-template-rows: repeat(2, minmax(0, 1fr));\n }\n .flex-col {\n flex-direction: column;\n }\n .flex-row {\n flex-direction: row;\n }\n .flex-wrap {\n flex-wrap: wrap;\n }\n .items-center {\n align-items: center;\n }\n .items-start {\n align-items: flex-start;\n }\n .items-stretch {\n align-items: stretch;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .justify-end {\n justify-content: flex-end;\n }\n .justify-start {\n justify-content: flex-start;\n }\n .gap-0 {\n gap: calc(var(--spacing) * 0);\n }\n .gap-1 {\n gap: calc(var(--spacing) * 1);\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-4 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-6 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-4xl {\n border-radius: var(--radius-4xl);\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-2 {\n border-style: var(--tw-border-style);\n border-width: 2px;\n }\n .border-t-2 {\n border-top-style: var(--tw-border-style);\n border-top-width: 2px;\n }\n .border-b-2 {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 2px;\n }\n .border-border {\n border-color: var(--color-border);\n }\n .border-current {\n border-color: currentcolor;\n }\n .border-error\\/20 {\n border-color: color-mix(in srgb, hsl(0 100% 50%) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-error) 20%, transparent);\n }\n }\n .border-info\\/20 {\n border-color: color-mix(in srgb, hsl(221 83% 53%) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-info) 20%, transparent);\n }\n }\n .border-red-300 {\n border-color: var(--color-red-300);\n }\n .border-secondary {\n border-color: var(--color-secondary);\n }\n .border-success\\/20 {\n border-color: color-mix(in srgb, hsl(142 76% 36%) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-success) 20%, transparent);\n }\n }\n .border-t-transparent {\n border-top-color: transparent;\n }\n .bg-black {\n background-color: var(--color-black);\n }\n .bg-error\\/10 {\n background-color: color-mix(in srgb, hsl(0 100% 50%) 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-error) 10%, transparent);\n }\n }\n .bg-error\\/90 {\n background-color: color-mix(in srgb, hsl(0 100% 50%) 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-error) 90%, transparent);\n }\n }\n .bg-info\\/90 {\n background-color: color-mix(in srgb, hsl(221 83% 53%) 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-info) 90%, transparent);\n }\n }\n .bg-muted {\n background-color: var(--color-muted);\n }\n .bg-overlay {\n background-color: var(--color-overlay);\n }\n .bg-primary {\n background-color: var(--color-primary);\n }\n .bg-red-100 {\n background-color: var(--color-red-100);\n }\n .bg-secondary {\n background-color: var(--color-secondary);\n }\n .bg-success\\/10 {\n background-color: color-mix(in srgb, hsl(142 76% 36%) 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-success) 10%, transparent);\n }\n }\n .bg-success\\/90 {\n background-color: color-mix(in srgb, hsl(142 76% 36%) 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-success) 90%, transparent);\n }\n }\n .bg-surface {\n background-color: var(--color-surface);\n }\n .bg-tertiary {\n background-color: var(--color-tertiary);\n }\n .bg-transparent {\n background-color: transparent;\n }\n .object-contain {\n object-fit: contain;\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-3 {\n padding: calc(var(--spacing) * 3);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-6 {\n padding: calc(var(--spacing) * 6);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .pt-3 {\n padding-top: calc(var(--spacing) * 3);\n }\n .pt-4 {\n padding-top: calc(var(--spacing) * 4);\n }\n .pt-7 {\n padding-top: calc(var(--spacing) * 7);\n }\n .pr-3 {\n padding-right: calc(var(--spacing) * 3);\n }\n .pr-4 {\n padding-right: calc(var(--spacing) * 4);\n }\n .pb-2 {\n padding-bottom: calc(var(--spacing) * 2);\n }\n .pb-3 {\n padding-bottom: calc(var(--spacing) * 3);\n }\n .pb-4 {\n padding-bottom: calc(var(--spacing) * 4);\n }\n .pl-3 {\n padding-left: calc(var(--spacing) * 3);\n }\n .pl-4 {\n padding-left: calc(var(--spacing) * 4);\n }\n .pl-10 {\n padding-left: calc(var(--spacing) * 10);\n }\n .text-center {\n text-align: center;\n }\n .text-right {\n text-align: right;\n }\n .text-2xl {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .text-error {\n color: var(--color-error);\n }\n .text-error-foreground {\n color: var(--color-error-foreground);\n }\n .text-muted {\n color: var(--color-muted);\n }\n .text-muted-foreground {\n color: var(--color-muted-foreground);\n }\n .text-primary-foreground {\n color: var(--color-primary-foreground);\n }\n .text-red-500 {\n color: var(--color-red-500);\n }\n .text-red-700 {\n color: var(--color-red-700);\n }\n .text-secondary {\n color: var(--color-secondary);\n }\n .text-success {\n color: var(--color-success);\n }\n .text-text-muted {\n color: var(--color-text-muted);\n }\n .text-text-secondary {\n color: var(--color-text-secondary);\n }\n .text-white {\n color: var(--color-white);\n }\n .accent-tertiary {\n accent-color: var(--color-tertiary);\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-100 {\n opacity: 100%;\n }\n .shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .duration-300 {\n --tw-duration: 300ms;\n transition-duration: 300ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .hover\\:bg-hover {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-hover);\n }\n }\n }\n .hover\\:bg-muted {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-muted);\n }\n }\n }\n .hover\\:bg-overlay {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-overlay);\n }\n }\n }\n .hover\\:bg-secondary\\/80 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, hsl(28 100% 8%) 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-secondary) 80%, transparent);\n }\n }\n }\n }\n .hover\\:bg-surface-hover {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-surface-hover);\n }\n }\n }\n .hover\\:bg-tertiary {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-tertiary);\n }\n }\n }\n .hover\\:bg-tertiary\\/80 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, hsl(28 100% 29%) 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-tertiary) 80%, transparent);\n }\n }\n }\n }\n .hover\\:bg-text-secondary {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-text-secondary);\n }\n }\n }\n .focus\\:border-2 {\n &:focus {\n border-style: var(--tw-border-style);\n border-width: 2px;\n }\n }\n .focus\\:border-secondary {\n &:focus {\n border-color: var(--color-secondary);\n }\n }\n .focus\\:border-transparent {\n &:focus {\n border-color: transparent;\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-secondary {\n &:focus {\n --tw-ring-color: var(--color-secondary);\n }\n }\n .focus\\:ring-tertiary {\n &:focus {\n --tw-ring-color: var(--color-tertiary);\n }\n }\n .focus\\:ring-offset-0 {\n &:focus {\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .sm\\:mt-0 {\n @media (width >= 40rem) {\n margin-top: calc(var(--spacing) * 0);\n }\n }\n .sm\\:flex-3 {\n @media (width >= 40rem) {\n flex: 3;\n }\n }\n .sm\\:flex-row {\n @media (width >= 40rem) {\n flex-direction: row;\n }\n }\n .sm\\:items-center {\n @media (width >= 40rem) {\n align-items: center;\n }\n }\n .sm\\:gap-4 {\n @media (width >= 40rem) {\n gap: calc(var(--spacing) * 4);\n }\n }\n .md\\:grid-cols-2 {\n @media (width >= 48rem) {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n .md\\:pl-2 {\n @media (width >= 48rem) {\n padding-left: calc(var(--spacing) * 2);\n }\n }\n .\\[\\&\\:\\:-webkit-slider-runnable-track\\]\\:rounded-full {\n &::-webkit-slider-runnable-track {\n border-radius: calc(infinity * 1px);\n }\n }\n .\\[\\&\\:\\:-webkit-slider-runnable-track\\]\\:bg-black\\/25 {\n &::-webkit-slider-runnable-track {\n background-color: color-mix(in srgb, #000 25%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 25%, transparent);\n }\n }\n }\n .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:h-\\[10px\\] {\n &::-webkit-slider-thumb {\n height: 10px;\n }\n }\n .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:w-\\[10px\\] {\n &::-webkit-slider-thumb {\n width: 10px;\n }\n }\n .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:appearance-none {\n &::-webkit-slider-thumb {\n appearance: none;\n }\n }\n .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:rounded-full {\n &::-webkit-slider-thumb {\n border-radius: calc(infinity * 1px);\n }\n }\n .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:bg-muted {\n &::-webkit-slider-thumb {\n background-color: var(--color-muted);\n }\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-scale-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-scale-z: 1;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-font-weight: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-duration: initial;\n --tw-ease: initial;\n }\n }\n}\n";
15174
15519
 
15175
15520
  const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
@@ -15187,6 +15532,8 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15187
15532
  showMarkEdgeTutorial = false;
15188
15533
  showAdditionalToolsTutorial = false;
15189
15534
  additionalToolsTutorialShown = false;
15535
+ showObstacleTutorial = false;
15536
+ obstacleTutorialShown = false;
15190
15537
  firstPolygonClosed = false;
15191
15538
  zoom = 1;
15192
15539
  loadingState = "empty";
@@ -15223,11 +15570,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15223
15570
  this.getBuildingImages();
15224
15571
  this.getBuildingInsights();
15225
15572
  // Reset tutorial states for new location
15226
- this.showTutorial = false;
15227
- this.showMarkEdgeTutorial = false;
15228
- this.showAdditionalToolsTutorial = false;
15229
- this.additionalToolsTutorialShown = false;
15230
- this.firstPolygonClosed = false;
15573
+ this.resetTutorials();
15231
15574
  // Show tutorial when coordinates are first set
15232
15575
  this.showTutorial = true;
15233
15576
  }
@@ -15238,11 +15581,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15238
15581
  this.getBuildingImages();
15239
15582
  this.getBuildingInsights();
15240
15583
  // Reset tutorial states for new location
15241
- this.showTutorial = false;
15242
- this.showMarkEdgeTutorial = false;
15243
- this.showAdditionalToolsTutorial = false;
15244
- this.additionalToolsTutorialShown = false;
15245
- this.firstPolygonClosed = false;
15584
+ this.resetTutorials();
15246
15585
  // Show tutorial when coordinates are first set
15247
15586
  this.showTutorial = true;
15248
15587
  }
@@ -15295,7 +15634,14 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15295
15634
  if (!this.rgbTiff)
15296
15635
  return;
15297
15636
  // if (this.canvasManager) return;
15298
- this.canvasManager = new distExports.CanvasManager({
15637
+ let managerConfig = {};
15638
+ if (state.isMobile) {
15639
+ managerConfig = {
15640
+ ...managerConfig,
15641
+ closeDistance: 20,
15642
+ };
15643
+ }
15644
+ this.canvasManager = new CanvasManager({
15299
15645
  coordinateSystem: {
15300
15646
  width: this.rgbTiff.width,
15301
15647
  height: this.rgbTiff.height,
@@ -15311,7 +15657,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15311
15657
  area: currentPolygon.area,
15312
15658
  }
15313
15659
  : null;
15314
- distExports.CanvasUtils.drawState(this.polygonCtx, this.canvasManager.getState(), this.solarPanel, {
15660
+ CanvasUtils.drawState(this.polygonCtx, this.canvasManager.getState(), this.solarPanel, {
15315
15661
  showGrid: false,
15316
15662
  showPreview: !state.isMobile,
15317
15663
  });
@@ -15319,12 +15665,13 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15319
15665
  onPolygonChange: (polygon) => this.handlePolygonsChange(polygon),
15320
15666
  onLowerRoofEdgeMarked: () => {
15321
15667
  this.calculateSolarPanels(false);
15668
+ // Show additional tools tutorial after mark edge tutorial is completed
15322
15669
  if (!this.additionalToolsTutorialShown) {
15323
15670
  this.showAdditionalToolsTutorial = true;
15324
15671
  this.additionalToolsTutorialShown = true;
15325
15672
  }
15326
15673
  },
15327
- });
15674
+ }, managerConfig);
15328
15675
  console.log("debug", this.canvasManager?.getDebugString());
15329
15676
  // Add event listeners after canvas is initialized
15330
15677
  this.setupEventListeners();
@@ -15417,6 +15764,15 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15417
15764
  if (polygon.closed && !this.firstPolygonClosed &&
15418
15765
  polygon.type === "roof") {
15419
15766
  this.firstPolygonClosed = true;
15767
+ // Show obstacle tutorial first instead of mark edge tutorial
15768
+ this.showObstacleTutorial = true;
15769
+ }
15770
+ // Show mark edge tutorial when an obstacle polygon is closed (if obstacle tutorial was shown)
15771
+ if (polygon.closed &&
15772
+ polygon.type === "obstruction" &&
15773
+ this.obstacleTutorialShown &&
15774
+ !this.showMarkEdgeTutorial &&
15775
+ !this.showObstacleTutorial) {
15420
15776
  this.showMarkEdgeTutorial = true;
15421
15777
  }
15422
15778
  if (polygon.closed || this.currentTool !== moveTool) {
@@ -15427,7 +15783,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15427
15783
  }
15428
15784
  return;
15429
15785
  }
15430
- const bestMatch = distExports$1.getBestFittingRoofSegment(polygon, this.buildingInsights?.solarPotential.roofSegmentStats ?? [], this.rgbTiff.bounds, this.canvasElement);
15786
+ const bestMatch = getBestFittingRoofSegment(polygon, this.buildingInsights?.solarPotential.roofSegmentStats ?? [], this.rgbTiff.bounds, this.canvasElement);
15431
15787
  if (!bestMatch) {
15432
15788
  const newPolygon = {
15433
15789
  ...polygon,
@@ -15558,14 +15914,10 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15558
15914
  this.rgbTiff = null;
15559
15915
  this.canvasManager?.clearPolygons();
15560
15916
  // Reset tutorial states for new location
15561
- this.showTutorial = false;
15562
- this.showMarkEdgeTutorial = false;
15563
- this.showAdditionalToolsTutorial = false;
15564
- this.additionalToolsTutorialShown = false;
15565
- this.firstPolygonClosed = false;
15917
+ this.resetTutorials();
15566
15918
  }
15567
15919
  try {
15568
- this.buildingInsights = await distExports$1.fetchSolarData(state.latitude, state.longitude, this.apiKey);
15920
+ this.buildingInsights = await fetchSolarData(state.latitude, state.longitude, this.apiKey);
15569
15921
  if (!this.buildingInsights) {
15570
15922
  console.error("No building insights found. Please enter them manually.");
15571
15923
  }
@@ -15619,7 +15971,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15619
15971
  // Add a small delay to ensure canvas sizing is complete
15620
15972
  await new Promise((resolve) => setTimeout(resolve, 0));
15621
15973
  try {
15622
- distExports.renderRGB(this.rgbTiff, undefined, this.canvasElement);
15974
+ renderRGB(this.rgbTiff, undefined, this.canvasElement);
15623
15975
  this.loadingState = "loaded";
15624
15976
  }
15625
15977
  catch (error) {
@@ -15632,16 +15984,16 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15632
15984
  // Map harmonia-draw Tool enum back to local Tool
15633
15985
  const harmoniaTool = this.canvasManager.getState().selectedTool;
15634
15986
  switch (harmoniaTool) {
15635
- case distExports.Tool.ROOF_POLYGON:
15987
+ case Tool.ROOF_POLYGON:
15636
15988
  this.currentTool = roofTool;
15637
15989
  break;
15638
- case distExports.Tool.OBSTRUCTION_POLYGON:
15990
+ case Tool.OBSTRUCTION_POLYGON:
15639
15991
  this.currentTool = obstructionTool;
15640
15992
  break;
15641
- case distExports.Tool.MOVE:
15993
+ case Tool.MOVE:
15642
15994
  this.currentTool = moveTool;
15643
15995
  break;
15644
- case distExports.Tool.MARK_ROOF_EDGE:
15996
+ case Tool.MARK_ROOF_EDGE:
15645
15997
  this.currentTool = markRoofEdgeTool;
15646
15998
  break;
15647
15999
  default:
@@ -15690,7 +16042,7 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15690
16042
  heightMeters: this.solarPanel.heightMeters / this.pixelInMeters,
15691
16043
  };
15692
16044
  const inset = BORDER_INSET / this.pixelInMeters;
15693
- const solarPanels = distExports$1.getOptimalSolarPositionFully(polygon, this.canvasManager.getObstacles(), convertedSolarPanel, polygon.azimuth, inset, polygon.pitch, state.settings.columnSpacing, state.settings.rowSpacing);
16045
+ const solarPanels = getOptimalSolarPositionFully(polygon, this.canvasManager.getObstacles(), convertedSolarPanel, polygon.azimuth, inset, polygon.pitch, state.settings.columnSpacing, state.settings.rowSpacing);
15694
16046
  this.canvasManager.setPositionedSolarPanel(polygon.id, solarPanels);
15695
16047
  // Convert PositionedSolarPanel[] to SolarPanelSystemPart format
15696
16048
  const allPositionedPanels = this.canvasManager
@@ -15754,16 +16106,16 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15754
16106
  if (this.canvasManager) {
15755
16107
  switch (tool.name) {
15756
16108
  case "roof":
15757
- this.canvasManager.setSelectedTool(distExports.Tool.ROOF_POLYGON);
16109
+ this.canvasManager.setSelectedTool(Tool.ROOF_POLYGON);
15758
16110
  break;
15759
16111
  case "obstruction":
15760
- this.canvasManager.setSelectedTool(distExports.Tool.OBSTRUCTION_POLYGON);
16112
+ this.canvasManager.setSelectedTool(Tool.OBSTRUCTION_POLYGON);
15761
16113
  break;
15762
16114
  case "move":
15763
- this.canvasManager.setSelectedTool(distExports.Tool.MOVE);
16115
+ this.canvasManager.setSelectedTool(Tool.MOVE);
15764
16116
  break;
15765
16117
  case "markRoofEdge":
15766
- this.canvasManager.setSelectedTool(distExports.Tool.MARK_ROOF_EDGE);
16118
+ this.canvasManager.setSelectedTool(Tool.MARK_ROOF_EDGE);
15767
16119
  break;
15768
16120
  }
15769
16121
  }
@@ -15784,6 +16136,17 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15784
16136
  };
15785
16137
  handleAdditionalToolsTutorialClose = () => {
15786
16138
  this.showAdditionalToolsTutorial = false;
16139
+ this.additionalToolsTutorialShown = true;
16140
+ };
16141
+ handleObstacleTutorialClose = () => {
16142
+ this.showObstacleTutorial = false;
16143
+ this.obstacleTutorialShown = true;
16144
+ this.handleToolSelect(obstructionTool);
16145
+ };
16146
+ handleObstacleTutorialSkip = () => {
16147
+ this.showObstacleTutorial = false;
16148
+ this.obstacleTutorialShown = true;
16149
+ this.showMarkEdgeTutorial = true;
15787
16150
  };
15788
16151
  showToastMessage(message, type = "success") {
15789
16152
  this.toastMessage = message;
@@ -15794,26 +16157,35 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15794
16157
  this.showToast = false;
15795
16158
  }, 3000);
15796
16159
  }
16160
+ resetTutorials() {
16161
+ this.showTutorial = false;
16162
+ this.showMarkEdgeTutorial = false;
16163
+ this.showAdditionalToolsTutorial = false;
16164
+ this.showObstacleTutorial = false;
16165
+ this.additionalToolsTutorialShown = false;
16166
+ this.obstacleTutorialShown = false;
16167
+ this.firstPolygonClosed = false;
16168
+ }
15797
16169
  render() {
15798
16170
  const t = getLanguageStrings(state.settings.language);
15799
- return (h("div", { key: 'bb03c8f47839d4be66e0dc876434ba5f5e4bf8cc', class: "flex flex-col justify-center items-center w-full h-full gap-4", id: "map-draw" }, this.showTutorial && (h("tutorial-component", { key: '3dda85a148de1c63ef4d56130513eb7bc69315fe', onClose: this.handleTutorialClose, title: t.mapDraw.tutorial.title, description: t.mapDraw.tutorial.description, videoSource: getAssetPath("./assets/tutorial1.mp4") })), this.showMarkEdgeTutorial && (h("tutorial-component", { key: '8c301a37fac508d3ef10dc2b4d80ab65752b74cd', onClose: this.handleMarkEdgeTutorialClose, title: t.mapDraw.tutorial.markEdge.title, description: t.mapDraw.tutorial.markEdge
15800
- .description, videoSource: getAssetPath("./assets/tutorial2.mp4") })), this.showAdditionalToolsTutorial && (h("tutorial-component", { key: '17a9582624fdc5145030cc7622fd58a8062a2e51', onClose: this
16171
+ return (h("div", { key: 'bb9de30bfdc8c301648b4e555ee91dd10790cfe1', class: "flex flex-col justify-center items-center w-full h-full gap-4", id: "map-draw" }, this.showTutorial && (h("tutorial-component", { key: '55fb0534409ded5116ef5d0848ef4e18429125dc', onClose: this.handleTutorialClose, title: t.mapDraw.tutorial.title, description: t.mapDraw.tutorial.description, videoSource: getAssetPath("./assets/tutorial1.mp4") })), this.showMarkEdgeTutorial && (h("tutorial-component", { key: '106bb6504bd30cc9fbaa45ea81bce7bc8e35ad89', onClose: this.handleMarkEdgeTutorialClose, title: t.mapDraw.tutorial.markEdge.title, description: t.mapDraw.tutorial.markEdge
16172
+ .description, videoSource: getAssetPath("./assets/tutorial2.mp4") })), this.showAdditionalToolsTutorial && (h("tutorial-component", { key: '60e9697dc04c89ce174265021ec2f725fc894887', onClose: this
15801
16173
  .handleAdditionalToolsTutorialClose, title: t.mapDraw.tutorial.additionalTools.title, description: t.mapDraw.tutorial.additionalTools
15802
- .description, videoSource: getAssetPath("./assets/tutorial3.mp4") })), (state.latitude && state.longitude &&
15803
- this.loadingState === "loading") && (h("div", { key: 'c1743c0f62d6bc5b050497676eec49d96b81013f', class: "flex items-center justify-center w-full bg-opacity-75 z-20 pt-7 rounded-4xl" }, h("div", { key: '00e47ecebd5e843f06837b6d60c974d53e512297', class: "animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-secondary" }))), (state.latitude && state.longitude &&
15804
- this.loadingState === "error") && (h("div", { key: '87836271f03dea80ca34636f56e6f332cdf0dcbc', class: "flex items-center justify-center w-full bg-opacity-75 z-20 pt-7 rounded-4xl" }, h("div", { key: '6aca2e9d4124772c5cf5264cb4d870d15e31b42d', class: "text-red-500 text-center text-lg font-semibold" }, t.mapDraw.imageLoadError))), h("div", { key: '93807c2d597f7505e756be9d5ed4152b24e01aad', class: "flex items-start justify-center w-full bg-primary rounded-4xl" }, h("div", { key: '2b3f7b1da9bf62ecc29909537c9c3b7432eda1a4', class: "relative flex items-center justify-center w-full rounded-4xl bg-secondary", style: {
16174
+ .description, videoSource: getAssetPath("./assets/tutorial3.mp4") })), this.showObstacleTutorial && (h("tutorial-component", { key: 'b8ee4d273be4405f0e75433f3c3bfaf8fd38f6ea', onClose: this.handleObstacleTutorialClose, onSkip: this.handleObstacleTutorialSkip, showSkipButton: true, title: t.mapDraw.tutorial.obstacle.title, description: t.mapDraw.tutorial.obstacle.description, videoSource: getAssetPath("./assets/tutorial4.mp4") })), (state.latitude && state.longitude &&
16175
+ this.loadingState === "loading") && (h("div", { key: '1d9795783a5a4001e8942214f9291ff17e5c9e81', class: "flex items-center justify-center w-full bg-opacity-75 z-20 pt-7 rounded-4xl" }, h("div", { key: '4fe8c9ed7e0f951047ade565c7c7d7c284880af7', class: "animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-secondary" }))), (state.latitude && state.longitude &&
16176
+ this.loadingState === "error") && (h("div", { key: 'b573fea2e40fe903464df2f9c87418e10645bdbe', class: "flex items-center justify-center w-full bg-opacity-75 z-20 pt-7 rounded-4xl" }, h("div", { key: '8ca895577317ed846e88a74f460bacac13ba1102', class: "text-red-500 text-center text-lg font-semibold" }, t.mapDraw.imageLoadError))), h("div", { key: '5727a6899c7ef16fd65b328f879f382200cd0099', class: "flex items-start justify-center w-full bg-primary rounded-4xl" }, h("div", { key: '8d0cb90847e01380e795426f4eb0d82ee4472fc3', class: "relative flex items-center justify-center w-full rounded-4xl bg-secondary", style: {
15805
16177
  aspectRatio: this.rgbTiff
15806
16178
  ? `${this.rgbTiff.width}/${this.rgbTiff.height}`
15807
16179
  : "",
15808
- } }, h("canvas", { key: '3c9f68c2c031850922cde46ab8d1476d09e418f0', ref: (el) => this.canvasElement = el, class: "absolute top-0 left-0 w-full h-full rounded-4xl", id: "map-draw-canvas", style: {
16180
+ } }, h("canvas", { key: 'e5dace6eed7f881f26c453ec4a37786940618cea', ref: (el) => this.canvasElement = el, class: "absolute top-0 left-0 w-full h-full rounded-4xl", id: "map-draw-canvas", style: {
15809
16181
  cursor: this.currentTool.cursor,
15810
- } }), h("canvas", { key: 'ac6fd8695d643938399625ae52bddbeb08579377', ref: (el) => this.polygonCanvas = el, class: "absolute top-0 left-0 w-full h-full rounded-4xl", style: {
16182
+ } }), h("canvas", { key: '1e4bbef400cd1d95cd0c2bbfbe5c75de77687744', ref: (el) => this.polygonCanvas = el, class: "absolute top-0 left-0 w-full h-full rounded-4xl", style: {
15811
16183
  cursor: this.currentTool.cursor,
15812
- } }))), this.rgbTiff && (h("div", { key: 'c9c8bb6156847b6510e4db0258028eb9922f1497', class: "w-full" }, h("div", { key: '0b3434f7fd235f42db89ae6374faeb6f733a286a', class: "w-full pb-4" }, h("tool-box", { key: '0710bd7a21553f8cf92a16693236206409230512', currentTool: this.currentTool, onToolSelect: (tool) => this.handleToolSelect(tool), undoCallback: () => this.undo() })), h("div", { key: 'a562acff3eb4edfe7c36f6aeef4893ceb1bb0387', class: "w-full" }, h("polygon-information", { key: 'e0a51152f7f69edb8a390e975c478d511b4c403f', currentPolygon: this.currentPolygon, numberOfPanels: this.currentPolygon
16184
+ } }))), this.rgbTiff && (h("div", { key: '19e7119b1f12ea5a4380a6b1b1bd5aa70ce9be77', class: "w-full" }, h("div", { key: '3e192ed5048aaeeb3a251ef11b30eda3a5b78668', class: "w-full pb-4" }, h("tool-box", { key: '5d3ee6c1e561b836dfe3566fb95573f669d6e692', currentTool: this.currentTool, onToolSelect: (tool) => this.handleToolSelect(tool), undoCallback: () => this.undo() })), h("div", { key: 'e7a98bcf23ea1653547d7b3eeabd3f784ac7114e', class: "w-full" }, h("polygon-information", { key: 'ca3092abaea53001bf5d008e9e9128e91e07cf01', currentPolygon: this.currentPolygon, numberOfPanels: this.currentPolygon
15813
16185
  ? this
15814
16186
  .solarSystem[this.currentPolygon.id]
15815
16187
  ?.numberOfPanels
15816
- : undefined, handleAzimuthChange: (event) => this.handleAzimuthChange(event), handlePitchChange: (event) => this.handlePitchChange(event), calculateSolarPanels: () => this.calculateSolarPanels(true), markAsFlatRoof: () => this.markAsFlatRoof(), currentTool: this.currentTool.name })), h("div", { key: '07bc9f2bc665186d0da23c9b8899e5095aff73d8', class: "w-full" }, h("solar-system-form", { key: '122f5fcfb4b82108e96659298f0b09d94ca196d1', systemConfigs: this.solarSystem, polygons: this.canvasManager?.getPolygons(), roofCanvas: this.canvasElement, polygonCanvas: this.polygonCanvas })))), this.showSettings && (h("settings-modal", { key: 'c310a679506c69302097f1c4907b913341707c66', settings: state.settings, onClose: this.handleSettingsClose, onSave: this.handleSettingsSave })), this.showToast && (h("toast-notification", { key: 'c111a1ee42b26bb3b953d39d1bfe6d270213ab94', message: this.toastMessage, type: this.toastType, duration: 3000 }))));
16188
+ : undefined, handleAzimuthChange: (event) => this.handleAzimuthChange(event), handlePitchChange: (event) => this.handlePitchChange(event), calculateSolarPanels: () => this.calculateSolarPanels(true), markAsFlatRoof: () => this.markAsFlatRoof(), currentTool: this.currentTool.name })), h("div", { key: '004d65f46355bb244a1e7ddfa6c6df28ad3f31ad', class: "w-full" }, h("solar-system-form", { key: 'b9c095e76b308a5ab5de11de436eef7401516318', systemConfigs: this.solarSystem, polygons: this.canvasManager?.getPolygons(), roofCanvas: this.canvasElement, polygonCanvas: this.polygonCanvas, pixelInMeters: this.pixelInMeters })))), this.showSettings && (h("settings-modal", { key: 'adfa4fe395cb1a3a275cb1012b05ee7dce24ec45', settings: state.settings, onClose: this.handleSettingsClose, onSave: this.handleSettingsSave })), this.showToast && (h("toast-notification", { key: '5413c46de3d34e9910d011f49e9e39f778f40a69', message: this.toastMessage, type: this.toastType, duration: 3000 }))));
15817
16189
  }
15818
16190
  static get watchers() { return {
15819
16191
  "rgbTiff": ["setupCanvasManager", "drawMap"],
@@ -15831,6 +16203,8 @@ const MapDraw = /*@__PURE__*/ proxyCustomElement(class MapDraw extends H {
15831
16203
  "showMarkEdgeTutorial": [32],
15832
16204
  "showAdditionalToolsTutorial": [32],
15833
16205
  "additionalToolsTutorialShown": [32],
16206
+ "showObstacleTutorial": [32],
16207
+ "obstacleTutorialShown": [32],
15834
16208
  "firstPolygonClosed": [32],
15835
16209
  "zoom": [32],
15836
16210
  "loadingState": [32],
@@ -15944,6 +16318,6 @@ function defineCustomElement() {
15944
16318
  defineCustomElement();
15945
16319
 
15946
16320
  export { LercParameters as L, MapDraw as M, LercAddCompression as a, defineCustomElement as d, global$1 as g };
15947
- //# sourceMappingURL=p-DlNHVTGB.js.map
16321
+ //# sourceMappingURL=p-BOe-Z8rz.js.map
15948
16322
 
15949
- //# sourceMappingURL=p-DlNHVTGB.js.map
16323
+ //# sourceMappingURL=p-BOe-Z8rz.js.map