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.
- package/dist/cjs/eraser-icon_19.cjs.entry.js +1 -1
- package/dist/cjs/{lerc-v64rYVDy.js → lerc-Bdx5y-yw.js} +3 -3
- package/dist/cjs/{lerc-v64rYVDy.js.map → lerc-Bdx5y-yw.js.map} +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/loading-widget.cjs.entry.js +1 -1
- package/dist/cjs/stencil-library.cjs.js +1 -1
- package/dist/collection/components/map-draw/map-draw.js +54 -25
- package/dist/collection/components/map-draw/map-draw.js.map +1 -1
- package/dist/collection/components/map-draw/map-selector.js +3 -3
- package/dist/collection/components/map-draw/polygon-buttons.js +1 -1
- package/dist/collection/components/map-draw/polygon-information.js +2 -2
- package/dist/collection/components/map-draw/tool-box.js +3 -3
- package/dist/collection/components/map-draw/tutorial-component.js +72 -4
- package/dist/collection/components/map-draw/tutorial-component.js.map +1 -1
- package/dist/collection/components/settings/settings.js +8 -8
- package/dist/collection/components/solar-expert/solar-expert.js +1 -1
- package/dist/collection/components/solar-system-form/solar-system-form.js +57 -36
- package/dist/collection/components/solar-system-form/solar-system-form.js.map +1 -1
- package/dist/collection/components/widgets/loading-widget.js +1 -1
- package/dist/collection/types/lang.js.map +1 -1
- package/dist/collection/utils/api.js +3 -2
- package/dist/collection/utils/api.js.map +1 -1
- package/dist/collection/utils/lang/english.js +7 -0
- package/dist/collection/utils/lang/english.js.map +1 -1
- package/dist/collection/utils/lang/german.js +7 -0
- package/dist/collection/utils/lang/german.js.map +1 -1
- package/dist/collection/utils/lang/spanish.js +7 -0
- package/dist/collection/utils/lang/spanish.js.map +1 -1
- package/dist/collection/utils/solar.js +0 -1
- package/dist/collection/utils/solar.js.map +1 -1
- package/dist/components/loading-widget.js +1 -1
- package/dist/components/map-draw.js +1 -1
- package/dist/components/map-selector.js +1 -1
- package/dist/components/{p-i1uLweD0.js → p-26DHwbCE.js} +4 -4
- package/dist/components/{p-i1uLweD0.js.map → p-26DHwbCE.js.map} +1 -1
- package/dist/components/{p-DlNHVTGB.js → p-BOe-Z8rz.js} +1462 -1088
- package/dist/components/p-BOe-Z8rz.js.map +1 -0
- package/dist/components/{p-Bopprtc7.js → p-Bduzzygj.js} +6 -6
- package/dist/components/{p-Bopprtc7.js.map → p-Bduzzygj.js.map} +1 -1
- package/dist/components/p-C3ZXE525.js +855 -0
- package/dist/components/p-C3ZXE525.js.map +1 -0
- package/dist/components/{p-DWirjxpO.js → p-Cx7fffWb.js} +6 -6
- package/dist/components/{p-DWirjxpO.js.map → p-Cx7fffWb.js.map} +1 -1
- package/dist/components/{p-Dzl6kfPI.js → p-D2AHNjbG.js} +39 -7
- package/dist/components/p-D2AHNjbG.js.map +1 -0
- package/dist/components/{p-BcVa4_YP.js → p-DLWzgdrw.js} +3 -3
- package/dist/components/{p-BcVa4_YP.js.map → p-DLWzgdrw.js.map} +1 -1
- package/dist/components/{p-B4X-RCW0.js → p-Ddk3b30j.js} +11 -11
- package/dist/components/{p-B4X-RCW0.js.map → p-Ddk3b30j.js.map} +1 -1
- package/dist/components/{p-eDwaXClX.js → p-DfzSejIb.js} +23 -2
- package/dist/components/p-DfzSejIb.js.map +1 -0
- package/dist/components/{p-BFJHTJPM.js → p-OxUYjaAL.js} +6 -6
- package/dist/components/{p-BFJHTJPM.js.map → p-OxUYjaAL.js.map} +1 -1
- package/dist/components/polygon-buttons.js +1 -1
- package/dist/components/polygon-information.js +1 -1
- package/dist/components/settings-modal.js +1 -1
- package/dist/components/solar-expert.js +10 -10
- package/dist/components/solar-system-form.js +1 -1
- package/dist/components/tool-box.js +1 -1
- package/dist/components/tutorial-component.js +1 -1
- package/dist/esm/eraser-icon_19.entry.js +1 -1
- package/dist/esm/{lerc-CAiDQjTu.js → lerc-DF5Lrv5A.js} +3 -3
- package/dist/esm/{lerc-CAiDQjTu.js.map → lerc-DF5Lrv5A.js.map} +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/loading-widget.entry.js +1 -1
- package/dist/esm/stencil-library.js +1 -1
- package/dist/stencil-library/assets/tutorial4.mp4 +0 -0
- package/dist/stencil-library/{p-ab0f2031.entry.js → p-9b4e52c3.entry.js} +2 -2
- package/dist/stencil-library/p-CymYGBJH.js +2 -0
- package/dist/stencil-library/p-CymYGBJH.js.map +1 -0
- package/dist/stencil-library/{p-cAuhy_jT.js → p-DHgjDQwz.js} +4 -4
- package/dist/stencil-library/{p-cAuhy_jT.js.map → p-DHgjDQwz.js.map} +1 -1
- package/dist/stencil-library/{p-e7b94dbb.entry.js → p-ba64c5e4.entry.js} +2 -2
- package/dist/stencil-library/stencil-library.esm.js +1 -1
- package/dist/types/components/map-draw/map-draw.d.ts +5 -0
- package/dist/types/components/map-draw/tutorial-component.d.ts +7 -0
- package/dist/types/components/solar-system-form/solar-system-form.d.ts +1 -0
- package/dist/types/components.d.ts +6 -0
- package/dist/types/types/lang.d.ts +7 -0
- package/dist/types/utils/api.d.ts +1 -0
- package/package.json +1 -1
- package/dist/components/p-DlNHVTGB.js.map +0 -1
- package/dist/components/p-Dzl6kfPI.js.map +0 -1
- package/dist/components/p-PkMjF2if.js +0 -1985
- package/dist/components/p-PkMjF2if.js.map +0 -1
- package/dist/components/p-eDwaXClX.js.map +0 -1
- package/dist/stencil-library/p-BBtmpSUK.js +0 -2
- package/dist/stencil-library/p-BBtmpSUK.js.map +0 -1
- /package/dist/stencil-library/{p-ab0f2031.entry.js.map → p-9b4e52c3.entry.js.map} +0 -0
- /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 {
|
|
5
|
-
import { d as defineCustomElement$
|
|
6
|
-
import { d as defineCustomElement$9, g as getPixelInMeters } from './p-
|
|
7
|
-
import { s as state, o as onChange, g as getLanguageStrings } from './p-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
|
|
14169
|
-
|
|
14170
|
-
|
|
14171
|
-
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
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
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
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
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
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
|
-
|
|
15127
|
-
|
|
15128
|
-
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
15987
|
+
case Tool.ROOF_POLYGON:
|
|
15636
15988
|
this.currentTool = roofTool;
|
|
15637
15989
|
break;
|
|
15638
|
-
case
|
|
15990
|
+
case Tool.OBSTRUCTION_POLYGON:
|
|
15639
15991
|
this.currentTool = obstructionTool;
|
|
15640
15992
|
break;
|
|
15641
|
-
case
|
|
15993
|
+
case Tool.MOVE:
|
|
15642
15994
|
this.currentTool = moveTool;
|
|
15643
15995
|
break;
|
|
15644
|
-
case
|
|
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 =
|
|
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(
|
|
16109
|
+
this.canvasManager.setSelectedTool(Tool.ROOF_POLYGON);
|
|
15758
16110
|
break;
|
|
15759
16111
|
case "obstruction":
|
|
15760
|
-
this.canvasManager.setSelectedTool(
|
|
16112
|
+
this.canvasManager.setSelectedTool(Tool.OBSTRUCTION_POLYGON);
|
|
15761
16113
|
break;
|
|
15762
16114
|
case "move":
|
|
15763
|
-
this.canvasManager.setSelectedTool(
|
|
16115
|
+
this.canvasManager.setSelectedTool(Tool.MOVE);
|
|
15764
16116
|
break;
|
|
15765
16117
|
case "markRoofEdge":
|
|
15766
|
-
this.canvasManager.setSelectedTool(
|
|
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: '
|
|
15800
|
-
.description, videoSource: getAssetPath("./assets/tutorial2.mp4") })), this.showAdditionalToolsTutorial && (h("tutorial-component", { key: '
|
|
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: '
|
|
15804
|
-
this.loadingState === "error") && (h("div", { key: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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-
|
|
16321
|
+
//# sourceMappingURL=p-BOe-Z8rz.js.map
|
|
15948
16322
|
|
|
15949
|
-
//# sourceMappingURL=p-
|
|
16323
|
+
//# sourceMappingURL=p-BOe-Z8rz.js.map
|