homebridge-roborock-vacuum 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-vacuum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
"abstract-things": "0.9.0",
|
|
36
36
|
"axios": "^1.7.7",
|
|
37
37
|
"binary-parser": "^2.2.1",
|
|
38
|
-
"canvas": "^2.11.2",
|
|
39
38
|
"chalk": "4.1.2",
|
|
40
39
|
"crc-32": "^1.2.2",
|
|
41
40
|
"debug": "4.3.5",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"val": "{\"uid\":1466357,\"tokentype\":\"\",\"token\":\"d8fe0bd712334ee585bc2145140f07d1-1JZxT5AINkUhb5JDyL8S+w==\",\"rruid\":\"rr6238c899155830\",\"region\":\"us\",\"countrycode\":\"886\",\"country\":\"TW\",\"nickname\":\"tasict\",\"rriot\":{\"u\":\"1RQsQJ2o8bxMCo6F45pICu\",\"s\":\"
|
|
2
|
+
"val": "{\"uid\":1466357,\"tokentype\":\"\",\"token\":\"d8fe0bd712334ee585bc2145140f07d1-1JZxT5AINkUhb5JDyL8S+w==\",\"rruid\":\"rr6238c899155830\",\"region\":\"us\",\"countrycode\":\"886\",\"country\":\"TW\",\"nickname\":\"tasict\",\"rriot\":{\"u\":\"1RQsQJ2o8bxMCo6F45pICu\",\"s\":\"l5UDP7\",\"h\":\"7h2O8mNvkt\",\"k\":\"kxF1J95i\",\"r\":{\"r\":\"US\",\"a\":\"https://api-us.roborock.com\",\"m\":\"ssl://mqtt-us.roborock.com:8883\",\"l\":\"https://wood-us.roborock.com\"}},\"tuyaDeviceState\":0,\"avatarurl\":\"https://files.roborock.com/iottest/default_avatar.png\"}",
|
|
3
3
|
"ack": true
|
|
4
4
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const rrMessage = require("./message").message;
|
|
4
4
|
const RRMapParser = require("./RRMapParser");
|
|
5
|
-
const MapCreator = require("./mapCreator");
|
|
6
5
|
const fs = require("fs");
|
|
7
6
|
const zlib = require("zlib");
|
|
8
7
|
|
|
@@ -36,7 +35,6 @@ class vacuum {
|
|
|
36
35
|
this.message = new rrMessage(this.adapter);
|
|
37
36
|
|
|
38
37
|
this.mapParser = new RRMapParser(this.adapter);
|
|
39
|
-
this.mapCreator = new MapCreator(this.adapter);
|
|
40
38
|
|
|
41
39
|
this.parameterFolders = {
|
|
42
40
|
get_mop_mode: "deviceStatus",
|
|
@@ -50,94 +48,10 @@ class vacuum {
|
|
|
50
48
|
};
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
async getMap(duid) {
|
|
54
|
-
if (this.adapter.config.enable_map_creation) {
|
|
55
|
-
this.adapter.log.debug(`Update map`);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
// const map = await connector.sendRequest(duid, "get_map_v1", [], true);
|
|
59
|
-
// const map = await this.adapter.rr_mqtt_connector.sendRequest(duid, "get_map_v1", [], true);
|
|
60
|
-
const map = await this.adapter.messageQueueHandler.sendRequest(duid, "get_map_v1", [], true);
|
|
61
|
-
// this.adapter.log.debug(`Map received: ${map}`);
|
|
62
|
-
if (map != "retry") {
|
|
63
|
-
const mappedRooms = await this.adapter.messageQueueHandler.sendRequest(duid, "get_room_mapping", []);
|
|
64
|
-
|
|
65
|
-
// const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_status", []);
|
|
66
|
-
const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_prop", ["get_status"]);
|
|
67
|
-
const selectedMap = deviceStatus[0].map_status >> 2 ?? -1; // to get the currently selected map perform bitwise right shift
|
|
68
|
-
|
|
69
|
-
// This is for testing and debugging maps. This can't be stored in a state.
|
|
70
|
-
zlib.gzip(map, (error, buffer) => {
|
|
71
|
-
if (error) {
|
|
72
|
-
this.adapter.log.error(`Error compressing map to gz ${error}`);
|
|
73
|
-
} else {
|
|
74
|
-
fs.writeFile("./test.rrmap.gz", buffer, (error) => {
|
|
75
|
-
if (error) {
|
|
76
|
-
this.adapter.log.error(`Error writing map file ${error}`);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const parsedData = await this.mapParser.parsedata(map);
|
|
83
|
-
|
|
84
|
-
const [mapBase64, mapBase64Truncated] = this.mapCreator.canvasMap(parsedData, duid, selectedMap, mappedRooms);
|
|
85
|
-
|
|
86
|
-
await this.adapter.setStateAsync(`Devices.${duid}.map.mapData`, { val: JSON.stringify(parsedData), ack: true });
|
|
87
|
-
await this.adapter.setStateAsync(`Devices.${duid}.map.mapBase64`, { val: mapBase64, ack: true });
|
|
88
|
-
await this.adapter.setStateAsync(`Devices.${duid}.map.mapBase64Truncated`, { val: mapBase64Truncated, ack: true });
|
|
89
|
-
|
|
90
|
-
// Send current map with Scale factor
|
|
91
|
-
const mapToSend = {
|
|
92
|
-
duid: duid,
|
|
93
|
-
command: "map",
|
|
94
|
-
base64: mapBase64,
|
|
95
|
-
map: parsedData,
|
|
96
|
-
scale: this.adapter.config.map_scale,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
if (this.adapter.socket != null) {
|
|
100
|
-
this.adapter.socket.send(JSON.stringify(mapToSend));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
} catch (error) {
|
|
104
|
-
this.adapter.catchError(error, "get_map_v1", duid), this.robotModel;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async getCleaningRecordMap(duid, startTime) {
|
|
110
|
-
try {
|
|
111
|
-
const cleaningRecordMap = await this.adapter.messageQueueHandler.sendRequest(duid, "get_clean_record_map", { start_time: startTime }, true);
|
|
112
|
-
const parsedData = await this.mapParser.parsedata(cleaningRecordMap);
|
|
113
|
-
const [mapBase64, mapBase64Truncated] = this.mapCreator.canvasMap(parsedData, duid);
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
mapBase64: mapBase64,
|
|
117
|
-
mapBase64Truncated: mapBase64Truncated,
|
|
118
|
-
mapData: JSON.stringify(parsedData),
|
|
119
|
-
};
|
|
120
|
-
} catch (error) {
|
|
121
|
-
this.adapter.catchError(error, "get_clean_record_map", duid, this.robotModel);
|
|
122
|
-
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
51
|
|
|
127
52
|
async command(duid, parameter, value) {
|
|
128
53
|
try {
|
|
129
54
|
switch (parameter) {
|
|
130
|
-
case "load_multi_map": {
|
|
131
|
-
const result = await this.adapter.messageQueueHandler.sendRequest(duid, "load_multi_map", value);
|
|
132
|
-
|
|
133
|
-
if (result[0] == "ok") {
|
|
134
|
-
await this.getMap(duid).then(async () => {
|
|
135
|
-
await this.getParameter(duid, "get_room_mapping");
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
55
|
case "app_segment_clean": {
|
|
142
56
|
this.adapter.log.debug("Start room cleaning");
|
|
143
57
|
|
|
@@ -304,7 +218,6 @@ class vacuum {
|
|
|
304
218
|
|
|
305
219
|
if (mapFromCommand && mapFromCommand.val != currentMap) {
|
|
306
220
|
await this.adapter.setStateAsync(`Devices.${duid}.commands.load_multi_map`, currentMap, true);
|
|
307
|
-
await this.adapter.vacuums[duid].getMap(duid);
|
|
308
221
|
}
|
|
309
222
|
}
|
|
310
223
|
}
|
|
@@ -549,13 +462,6 @@ class vacuum {
|
|
|
549
462
|
});
|
|
550
463
|
}
|
|
551
464
|
|
|
552
|
-
if (this.adapter.config.enable_map_creation == true) {
|
|
553
|
-
const mapArray = await this.getCleaningRecordMap(duid, cleaningAttributes[cleaningAttribute][cleaningRecord]);
|
|
554
|
-
for (const mapType in mapArray) {
|
|
555
|
-
const val = mapArray[mapType];
|
|
556
|
-
this.adapter.setStateAsync(`Devices.${duid}.cleaningInfo.records.${cleaningRecord}.map.${mapType}`, { val: val, ack: true });
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
465
|
}
|
|
560
466
|
|
|
561
467
|
const objectString = `Devices.${duid}.cleaningInfo.JSON`;
|
|
@@ -1,692 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { createCanvas } = require("canvas");
|
|
4
|
-
const { Image } = require("canvas");
|
|
5
|
-
|
|
6
|
-
// Farben ändern
|
|
7
|
-
const colors = {
|
|
8
|
-
floor: "#23465e",
|
|
9
|
-
obstacle: "#2b2e30",
|
|
10
|
-
path: "rgba(255,255,255,0.5)",
|
|
11
|
-
newmap: true,
|
|
12
|
-
};
|
|
13
|
-
const orgcolors = [
|
|
14
|
-
"#017E82",
|
|
15
|
-
"#BD7B00",
|
|
16
|
-
"#C05A41",
|
|
17
|
-
"#4579B5",
|
|
18
|
-
"#434242", // wall
|
|
19
|
-
"#dfdfdf", // desel floor
|
|
20
|
-
];
|
|
21
|
-
const offset = 60;
|
|
22
|
-
|
|
23
|
-
const robot =
|
|
24
|
-
"data:image/png;base64,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";
|
|
25
|
-
const robot1 =
|
|
26
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAAHGjw8oAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADbUExURQAAAICAgICAgICAgICAgICAgHx8fH19fX19fYCAgIGBgX5+foCAgH5+foCAgH9/f39/f35+foCAgH9/f39/f4CAgH5+foGBgYCAgICAgIGBgX9/f39/f35+foCAgH9/f39/f4CAgIODg4eHh4mJiZCQkJycnJ2dnZ6enqCgoKSkpKenp62trbGxsbKysry8vL29vcLCwsXFxcbGxsvLy87OztPT09XV1d/f3+Tk5Ojo6Ozs7O3t7e7u7vHx8fLy8vPz8/X19fb29vf39/j4+Pn5+f39/f7+/v///9yECocAAAAgdFJOUwAGChgcKCkzOT5PVWZnlJmfsLq7wcrS1Nre4OXz+vr7ZhJmqwAAAAlwSFlzAAAXEQAAFxEByibzPwAAAcpJREFUKFNlkolaWkEMhYPggliBFiwWhGOx3AqCsggI4lZt8/5P5ElmuEX5P5hMMjeZJBMRafCvUKnbIqpcioci96owTQWqP0QKC54nImUAyr9k7VD1me4YvibHlJKpVUzQhR+dmdTRSDUvdHh8NK8nhqUVch7cITmXA3rtYDmH+3OL4XI1T+BhJUcXczQxOBXJuve0/daeUr5A6g9muJzo5NI2kPKtyRSGBStKQZ5RC1hENWn6NSRTrDUqLD/lsNKoFTNRETlGMn9dDoGdoDcT1fHPi7EuUDD9dMBw4+6vMQVyInnPXDsdW+8tjWfbYTbzg/OstcagzSlb0+wL/6k+1KPhCrj6YFhzS5eXuHcYNF4bsGtDYhFLTOSMqTsx9e3iyKfynb1SK+RqtEq70RzZPwEGKwv7G0OK1QA42Y+HIgct9P3WWG9ItI/mQTgvoeuWAMdlTRclO/+Km2jwlhDvinGNbyJH6EWV84AJ1wl8JowejqTqTmv+0GqDmVLlg/wLX5Mp2rO3WRs2Zs5fznAVd1EzRh10OONr7hhhM4ctevhiVVxHdYsbq+JzHzaIfdjs5CZ9tGInSfoWEXuL7//fwtn9+Jp7wSryDjBFqnOGeuUxAAAAAElFTkSuQmCC";
|
|
27
|
-
const charger =
|
|
28
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAdVBMVEUAAAA44Yo44Yo44Yo44Yo44Yo44Yo44Yo44Yp26q844Yr///9767Kv89DG9t2g8Md26q5C44/5/vvz/fjY+ei19NNV5ZtJ45T2/fmY78KP7r1v6atq6Kjs/PPi+u7e+uvM9+Gb8MSS7r+H7bhm6KVh56JZ5p3ZkKITAAAACnRSTlMABTr188xpJ4aepd0A4wAAANZJREFUKM9VklmCgzAMQwkQYCSmLKWl2+zL/Y9YcIUL7wvkJHIUJyKkVcyy+JIGCZILGF//QLEqlTmMdsBEXi56igfH/QVGqvXSu49+1KftCbn+dtxB5LOPfNGQNRaKaQNkTJ46OMGczZg8wJB/9TB+J3nFkyqJMp44vBrnWYhJJmOn/5uVzAotV/zACnbUtTbOpHcQzVx8kxw6mavdpYP90dsNcE5k6xd8RoIb2Xgk6xAbfm5C9NiHtxGiXD/U2P96UJunrS/LOeV2GG4wfBi241P5+NwBnAEUFx9FUdUAAAAASUVORK5CYII=";
|
|
29
|
-
const tank =
|
|
30
|
-
"data:image/png;base64,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";
|
|
31
|
-
const spaceship =
|
|
32
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAFMklEQVRYR+2YfUxVZRjAf+cAfiAOHZaIFxXD0g2V3LVipZFuOrRcm6KBHw0b/GHERpFGfoQokcV0M6x5mbiKyFK31NRls666RmUrP0gtUVQU8IP8AEGBe297zznXe+71fsBB+4v3D8Z97/M85/c+X+9zj8SDW7eBUJ25XsDdrpqXumpAp29jSoZMTDxsXCS2+wJNXbX/YAFTVsqMeg5WTO4GNBIZG90eNOI3l063B434zwK8oCnGkrISXRWfARzAT0CGEeNCp6ttppHIYWFExYIkQc4mCOsHeclw6wY01MC/taIXip5oaHUdMPmtMBa85/7w+jq4egX2bQRrWTegv9A00u1BQ6l7T6nbgx2tYiGXCsia7yqAKqAzHowFEjR9O1Cu9Um/Mewo4ETgQJAsYXc4cDi4CER3ErBGkjDJkoTNLvo3zwMHA2VYRwGFMaujLonk9D/Z9l29IcBZL0aatpY8iTRor+BKFId+oIDj48Opqm7m+s02Q4D9w0NMsTGhHD5y8+EAYhoJDZegpdEQIL37mogYDBdPPSTA9w9B+XKotBoDjEs0kboK3p3QacBSYIEuHyZ5JLCSg/gDnPkotLeqJuRgyN/veRfX4B9QFOKPOobPgYXOIjk8tFc/85LoZ8ms2iMqdSGwWSfsH3D+CpgRzkcrRioqb+efgoKD8INFPywEAkyTJam0OHYaa2p+5vydG78D4+8BxodFmnePnsuQX9Zhc9gDAV4CHgOuKXexBlhVIc4BsQkHPAEHAGeISxzsJ8RpQZJceuGZbKYf/5IjTfXugIBZ5zHvgJZTsHkJVOxwic5dCnMWKx50A7SchN2fwo71Ltn4qTB7ma8cTANEqjmXB2DUE2bS18OqaWC3eQfcdUtVbm50mQnti+jc9wHuvKkOsXrZM2dVPe9FkoYcVMryPVCSBbV/ewCaRppZVALLEvWAInHF64zRwIc4AZ14l8/DpdMqYN5MNq0TYvBa9nHI264CDh4BA4eqGseP6gEXix1xXK0gVcDVVvgkXbSigIBlQKvyELFCesL2K7oIALOjoKVJERGM92QdDtde7zD4plbVa9Ve1aREu/5XfrbQA5hnDHBLDfQJdwdzfkoeRG56JAW5jyNH7QWL5qGMsdhrk1ha+A+FJfWwtc5d32aDE5VwpwnykwwBVgLi2FMIADh8oIMRMaF8b73mBjg1cQCnq5s5e1nqCOA+IAo5KM53iEeMM7PWCgWpcP6EeuLgECj+FWTnlOXhyOIsOKYNJELmYzGFAW8kgF1MVMCYiZCpq2S9CSGT+TS0t6m7EdGQutpHDjoBvQfz/9mtOg0tzT4AJcmseEy/JBm+vgjBIoe9rJxJcPaY9oUEX4gZFpgvZlMl+WH4GCjS32CauMhD4bnUIeDQvK1UGtDeLvTdqljcCi97IAQBawLl4ISeA5jcP4a8c1a3HMwblsj+69UcunutIzm4BLB5PP9bcfv4G1iFO1spP+e7iueYyIqII3fIBAZVFLkB1iXkUHjhEOsbKtUo6JdSxX+pVbx6uvhGhEhLRI8g+kmuYF9KPnWKtILJEf29U0s4Q8T1vhVo5I8QLdpDyxLZI+yl/WNf5ak/LNy2tYkS/UBpSzlbVNGiV8TfKOCdPkEhWb+Ny2Dy0c+ob23a5eVFkvBcg6/jBAL0prf1kZA+s8pHzWRG5Ve02NsKgaWAnXkFqnyZ+Kj8AizoLYfk7oxLIfXkdq623d4GJHfGt0YA1wLZuodkAhsUQNfbMlGOAvB1oFgnuw54szOA/wGAoJRHR2vq3wAAAABJRU5ErkJggg==";
|
|
33
|
-
const robot2 =
|
|
34
|
-
"data:image/png;base64,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";
|
|
35
|
-
const originalRobot =
|
|
36
|
-
"data:image/png;base64,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";
|
|
37
|
-
const go_to_pin =
|
|
38
|
-
"data:image/png;base64,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AAAOVWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDplODk0ZjlkMy1iNjZlLTQ1ODQtODY0MS0zMGI5MTk2ZDM2NTQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6YjM1MWM4ODEtZmQwYi00MjlkLWExZDMtNjkwOTFiNWE1YzYxIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6YTZlMmNkZTctOGZhZC00ZDVhLWI5N2UtY2VhZTc0NWNmYjVmIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzczNjQ5MzQzNDYyMjgiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMiIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjM6MDI6MjVUMjM6NDI6MTIrMDE6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIzOjAyOjI1VDIzOjQyOjEyKzAxOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjcxYWIyOGQtODExMy00NWRiLWJkMjQtNGJkOTJjZjk5NThmIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIzLTAyLTI1VDIzOjI1OjIzIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA4MjZiNWE2LTgwZjctNDU2My1hY2UwLThjMTMzYjQyZmZhNCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChXaW5kb3dzKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wMi0yNVQyMzo0MjoxNCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6W+nyxAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5wIZFioOADz0AgAAAyVJREFUaN7tmr9uE0EQhz8ncmKSwglQkMSCIg0URMgFFUUkZARyj1DeIhQuIsQj0LqnJHkBK6FJ5QK5IIpBsuhiWVEiOY7y3/INxaybBOE9+27vbPknjWSfd2fmu/Xt7c4djDXWWGMNkRLhupcJ4CnwHHgNrADLwLxp0AT+AD+B78Ae8BsS3pCdR5kCeQPyA+QExAORHuaZtj9M36lhgX0FsmMB2Mt21Fd8QVMgGyA3AcB27cb4TMUNdgbkG0gnQNiudYzvmbjA3gPZDAH0tm1qrGhhJ0C+Wk5Kg5pnYk1ECfwB5NoBbNeuNWY0sPdBjhzCdu1IY7uFTYAUI4DtWlFzcAf8CKQeIXBdc/CvfieAt8Cinw6ZDBQKUC5Do6FWLuuxTMZ3/EWTg7MRrvgZkXxepFIR8Ty5I8/T3/J536NccQW7CHJpm9jKikitJj1Vq2lbH8CXmkv4wDk/y8dSSaxVKvleduZcXMNPgEmbhrkcZLP2jrNZ7WOpSZNL6MBztvvopSVIp+0dp9Pax8defs4F8LQtcCoFyaS942RS+/gAnnYBfAaITcOrK2i37R2329rHdjIxuYQOfAJYlWDqdWi17B23WtrHUp7JJfRZejVGs/SqC+CHIBcxuA9faC5uFh97MVhp7blcWq77XfBnMiKFgki5LNJoqJXLeiyT6WsDse4SeBnkPMLd0rnm4G63dAhUIyy1VE0OroATZ8BWhMBbJgenVY95kNMI/s6nGrs/DVABTDSBYgSjWzSxIynkPQNpOhzdpsbsXwPWeBO/gJLDM1wyMSMtxi+AtByMbktjDaYgqviH5lqWMM+qiXFIPCQLIMchju5xEKMbNPRaiMBrxE8yC7IbAuyu+o6lJAvSDhC2rT5jLfkU0IPxjvqKvWQOZD8A4H31NRSSFwNuH8/Vx9BIEuZlFK/PJ/0bjh+HBgI91eesvTtE72jdgX4McuAD9kD7DLXkvWVZ90bbjoTkc49bVUfbjIxkFmT7P8DbMV5N9Q39AKT6D9iq/jaSkpe39s4tPTbSknfmdYVL/exWEdzcZQL4aL58GcKXwccaa6yx3Okve0d25MUPGTcAAAAASUVORK5CYII=";
|
|
39
|
-
|
|
40
|
-
//const ctximg = canvasImg.getContext('2d');
|
|
41
|
-
|
|
42
|
-
const img = new Image(); // Create a new Image
|
|
43
|
-
img.src = originalRobot;
|
|
44
|
-
|
|
45
|
-
const img_charger = new Image();
|
|
46
|
-
img_charger.src = charger;
|
|
47
|
-
|
|
48
|
-
const obstacleTitles = {
|
|
49
|
-
0: "Wire",
|
|
50
|
-
1: "Pet waste",
|
|
51
|
-
2: "Footwear",
|
|
52
|
-
3: "Pedestal",
|
|
53
|
-
4: "Pedestal",
|
|
54
|
-
5: "Power strip",
|
|
55
|
-
9: "Scale",
|
|
56
|
-
10: "Fabric",
|
|
57
|
-
18: "Dustpan",
|
|
58
|
-
25: "Dustpan",
|
|
59
|
-
26: "Bar",
|
|
60
|
-
27: "Bar",
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
class MapCreator {
|
|
64
|
-
constructor(adapter) {
|
|
65
|
-
this.adapter = adapter;
|
|
66
|
-
this.scaleFactor = adapter.config.map_scale;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
getX(dimensions, px) {
|
|
70
|
-
return (px * this.scaleFactor) % dimensions.width;
|
|
71
|
-
}
|
|
72
|
-
getY(dimensions, px) {
|
|
73
|
-
return dimensions.height - Math.floor(px / (dimensions.width / this.scaleFactor)) * this.scaleFactor - this.scaleFactor;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
robotXtoPixelX(image, robotCoord) {
|
|
77
|
-
return (robotCoord - image.position.left) * this.scaleFactor - 2;
|
|
78
|
-
}
|
|
79
|
-
robotYtoPixelY(image, robotCoord) {
|
|
80
|
-
return (image.dimensions.height / this.scaleFactor + image.position.top - robotCoord) * this.scaleFactor - 2;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
rotateCanvas(img, angleInDegrees) {
|
|
84
|
-
const canvasImg = createCanvas(img.width, img.height);
|
|
85
|
-
const ctx = canvasImg.getContext("2d");
|
|
86
|
-
const angleOffset = 90;
|
|
87
|
-
let angleInRadians = ((angleInDegrees - angleOffset) * Math.PI) / 180;
|
|
88
|
-
angleInRadians = ((angleInRadians + Math.PI) % (2 * Math.PI)) - Math.PI; // Normalize angle to -π to π
|
|
89
|
-
|
|
90
|
-
ctx.translate(img.width / 2, img.height / 2);
|
|
91
|
-
ctx.rotate(-angleInRadians);
|
|
92
|
-
ctx.translate(-img.width / 2, -img.height / 2);
|
|
93
|
-
ctx.drawImage(img, 0, 0);
|
|
94
|
-
|
|
95
|
-
return canvasImg;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
drawLineBresenham(imageData, x1, y1, x2, y2) {
|
|
99
|
-
const pixels = imageData.data;
|
|
100
|
-
|
|
101
|
-
const dx = Math.abs(x2 - x1);
|
|
102
|
-
const dy = Math.abs(y2 - y1);
|
|
103
|
-
const sx = x1 < x2 ? 1 : -1;
|
|
104
|
-
const sy = y1 < y2 ? 1 : -1;
|
|
105
|
-
let err = dx - dy;
|
|
106
|
-
|
|
107
|
-
for(;;) {
|
|
108
|
-
// Setze Pixel im ImageData
|
|
109
|
-
if (x1 >= 0 && x1 < imageData.width && y1 >= 0 && y1 < imageData.height) { // handle out of bounds. lineto would already do this but we need to set pixels directly
|
|
110
|
-
const index = (x1 + y1 * imageData.width) * 4;
|
|
111
|
-
pixels[index] = 128; // r
|
|
112
|
-
pixels[index + 1] = 128; // g
|
|
113
|
-
pixels[index + 2] = 128; // b
|
|
114
|
-
pixels[index + 3] = 128; // a
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (x1 === x2 && y1 === y2) break;
|
|
118
|
-
const e2 = 2 * err;
|
|
119
|
-
if (e2 > -dy) {
|
|
120
|
-
err -= dy;
|
|
121
|
-
x1 += sx;
|
|
122
|
-
}
|
|
123
|
-
if (e2 < dx) {
|
|
124
|
-
err += dx;
|
|
125
|
-
y1 += sy;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
areRoomsAdjacent(roomA, roomB) {
|
|
131
|
-
const horizontalOverlap = roomA.minX <= roomB.maxX && roomA.maxX >= roomB.minX;
|
|
132
|
-
const verticalOverlap = roomA.minY <= roomB.maxY && roomA.maxY >= roomB.minY;
|
|
133
|
-
return horizontalOverlap && verticalOverlap;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
hexToRGBA(hex, alpha = 1) {
|
|
137
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
138
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
139
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
140
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
canvasMap(mapdata, duid, selectedMap, mappedRooms, options) {
|
|
144
|
-
if (options) {
|
|
145
|
-
colors.floor = options.FLOORCOLOR;
|
|
146
|
-
colors.obstacle = options.WALLCOLOR;
|
|
147
|
-
colors.path = options.PATHCOLOR;
|
|
148
|
-
colors.newmap = options && options.newmap ? options.newmap : true;
|
|
149
|
-
if (options.ROBOT === "robot") {
|
|
150
|
-
img.src = robot;
|
|
151
|
-
} else if (options.ROBOT === "robot1") {
|
|
152
|
-
img.src = robot1;
|
|
153
|
-
} else if (options.ROBOT === "tank") {
|
|
154
|
-
img.src = tank;
|
|
155
|
-
} else if (options.ROBOT === "spaceship") {
|
|
156
|
-
img.src = spaceship;
|
|
157
|
-
} else if (options.ROBOT === "robot2") {
|
|
158
|
-
img.src = robot2;
|
|
159
|
-
} else if (options.ROBOT === "originalRobot") {
|
|
160
|
-
img.src = originalRobot;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
let maxtop = 0;
|
|
164
|
-
let maxleft = 0;
|
|
165
|
-
let minleft = 0;
|
|
166
|
-
let mintop = 0;
|
|
167
|
-
|
|
168
|
-
mapdata.IMAGE.dimensions.width = mapdata.IMAGE.dimensions.width * this.scaleFactor;
|
|
169
|
-
mapdata.IMAGE.dimensions.height = mapdata.IMAGE.dimensions.height * this.scaleFactor;
|
|
170
|
-
|
|
171
|
-
const canvas = createCanvas(mapdata.IMAGE.dimensions.width, mapdata.IMAGE.dimensions.height);
|
|
172
|
-
const ctx = canvas.getContext("2d");
|
|
173
|
-
|
|
174
|
-
if (mapdata.IMAGE.pixels.floor && mapdata.IMAGE.pixels.floor.length !== 0) {
|
|
175
|
-
if (typeof mapdata.IMAGE.pixels.floor[0] === "number") {
|
|
176
|
-
// init min
|
|
177
|
-
minleft = mapdata.IMAGE.pixels.floor[0] % mapdata.IMAGE.dimensions.width;
|
|
178
|
-
mintop = mapdata.IMAGE.dimensions.height - 1 - Math.floor(mapdata.IMAGE.pixels.floor[0] / mapdata.IMAGE.dimensions.width);
|
|
179
|
-
|
|
180
|
-
["floor", "obstacle"].forEach((key) => {
|
|
181
|
-
ctx.beginPath();
|
|
182
|
-
mapdata.IMAGE.pixels[key].forEach((px) => {
|
|
183
|
-
if (key === "obstacle") {
|
|
184
|
-
ctx.fillStyle = colors.newmap ? orgcolors[4] : colors.obstacle;
|
|
185
|
-
} else {
|
|
186
|
-
ctx.fillStyle = colors.newmap ? orgcolors[5] : colors.floor;
|
|
187
|
-
}
|
|
188
|
-
//ctx.fillStyle = colors[key];
|
|
189
|
-
ctx.rect(this.getX(mapdata.IMAGE.dimensions, px), this.getY(mapdata.IMAGE.dimensions, px), this.scaleFactor, this.scaleFactor);
|
|
190
|
-
|
|
191
|
-
maxtop = Math.max(maxtop, this.getY(mapdata.IMAGE.dimensions, px));
|
|
192
|
-
maxleft = Math.max(maxleft, this.getX(mapdata.IMAGE.dimensions, px));
|
|
193
|
-
minleft = Math.min(minleft, this.getX(mapdata.IMAGE.dimensions, px));
|
|
194
|
-
mintop = Math.min(mintop, this.getY(mapdata.IMAGE.dimensions, px));
|
|
195
|
-
});
|
|
196
|
-
ctx.fill();
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Zeichne Alle Räume
|
|
202
|
-
const segmentsData = {};
|
|
203
|
-
const segmentsBounds = {};
|
|
204
|
-
const assignedColors = {};
|
|
205
|
-
const availableColors = [...orgcolors];
|
|
206
|
-
|
|
207
|
-
if (mapdata.IMAGE.pixels.segments && !mapdata.CURRENTLY_CLEANED_BLOCKS && colors.newmap) {
|
|
208
|
-
mapdata.IMAGE.pixels.segments.forEach((px) => {
|
|
209
|
-
const segnum = (px >> 21);
|
|
210
|
-
const x = this.getX(mapdata.IMAGE.dimensions, px & 0xfffff);
|
|
211
|
-
const y = this.getY(mapdata.IMAGE.dimensions, px & 0xfffff);
|
|
212
|
-
|
|
213
|
-
if (!segmentsData[segnum]) {
|
|
214
|
-
segmentsData[segnum] = { points: [], minX: x, maxX: x, minY: y, maxY: y };
|
|
215
|
-
} else {
|
|
216
|
-
const segment = segmentsData[segnum];
|
|
217
|
-
segment.points.push({ x, y });
|
|
218
|
-
segment.minX = Math.min(segment.minX, x);
|
|
219
|
-
segment.maxX = Math.max(segment.maxX, x);
|
|
220
|
-
segment.minY = Math.min(segment.minY, y);
|
|
221
|
-
segment.maxY = Math.max(segment.maxY, y);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
Object.keys(segmentsData).forEach(segnum => {
|
|
226
|
-
const segment = segmentsData[segnum];
|
|
227
|
-
segmentsBounds[segnum] = {
|
|
228
|
-
minX: segment.minX,
|
|
229
|
-
maxX: segment.maxX,
|
|
230
|
-
minY: segment.minY,
|
|
231
|
-
maxY: segment.maxY
|
|
232
|
-
};
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
Object.keys(segmentsBounds).forEach(segnum => {
|
|
236
|
-
const currentBounds = segmentsBounds[segnum];
|
|
237
|
-
const adjacentSegments = Object.keys(segmentsBounds).filter(otherSegnum => {
|
|
238
|
-
const otherBounds = segmentsBounds[otherSegnum];
|
|
239
|
-
return segnum !== otherSegnum && this.areRoomsAdjacent(currentBounds, otherBounds);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const usedColors = adjacentSegments.map(adjSegnum => assignedColors[adjSegnum]);
|
|
243
|
-
const availableColor = availableColors.find(color => !usedColors.includes(color));
|
|
244
|
-
|
|
245
|
-
if (availableColor) {
|
|
246
|
-
assignedColors[segnum] = availableColor;
|
|
247
|
-
} else {
|
|
248
|
-
assignedColors[segnum] = availableColors[Math.floor(Math.random() * availableColors.length)];
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
Object.keys(segmentsData).forEach(segnum => {
|
|
253
|
-
const segment = segmentsData[segnum];
|
|
254
|
-
ctx.fillStyle = assignedColors[segnum] || availableColors[0];
|
|
255
|
-
ctx.beginPath();
|
|
256
|
-
segment.points.forEach(point => {
|
|
257
|
-
ctx.rect(point.x, point.y, this.scaleFactor, this.scaleFactor);
|
|
258
|
-
});
|
|
259
|
-
ctx.fill();
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
ctx.closePath();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (mapdata.CURRENTLY_CLEANED_BLOCKS && colors.newmap) {
|
|
266
|
-
let segnum, lastcolor;
|
|
267
|
-
ctx.beginPath();
|
|
268
|
-
mapdata.IMAGE.pixels.segments.forEach((px) => {
|
|
269
|
-
segnum = px >> 21;
|
|
270
|
-
if (mapdata.CURRENTLY_CLEANED_BLOCKS.includes(segnum)) {
|
|
271
|
-
if (segnum !== lastcolor) {
|
|
272
|
-
ctx.fill();
|
|
273
|
-
ctx.beginPath();
|
|
274
|
-
ctx.fillStyle = orgcolors[segnum % 4];
|
|
275
|
-
lastcolor = segnum;
|
|
276
|
-
}
|
|
277
|
-
px = px & 0xfffff;
|
|
278
|
-
ctx.rect(this.getX(mapdata.IMAGE.dimensions, px), this.getY(mapdata.IMAGE.dimensions, px), this.scaleFactor, this.scaleFactor);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
ctx.fill();
|
|
282
|
-
ctx.closePath();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Zeichne Zonen active Zonen
|
|
286
|
-
if (mapdata.CURRENTLY_CLEANED_ZONES) {
|
|
287
|
-
if (mapdata.CURRENTLY_CLEANED_ZONES[0]) {
|
|
288
|
-
ctx.beginPath();
|
|
289
|
-
mapdata.CURRENTLY_CLEANED_ZONES.forEach((coord) => {
|
|
290
|
-
ctx.fillStyle = "rgba(46,139,87,0.1)";
|
|
291
|
-
ctx.fillRect(
|
|
292
|
-
this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
|
|
293
|
-
this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50),
|
|
294
|
-
this.robotXtoPixelX(mapdata.IMAGE, coord[2] / 50) - this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
|
|
295
|
-
this.robotYtoPixelY(mapdata.IMAGE, coord[3] / 50) - this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)
|
|
296
|
-
);
|
|
297
|
-
ctx.strokeStyle = "#2e8b57";
|
|
298
|
-
ctx.lineWidth = 4;
|
|
299
|
-
ctx.strokeRect(
|
|
300
|
-
this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
|
|
301
|
-
this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50),
|
|
302
|
-
this.robotXtoPixelX(mapdata.IMAGE, coord[2] / 50) - this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
|
|
303
|
-
this.robotYtoPixelY(mapdata.IMAGE, coord[3] / 50) - this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)
|
|
304
|
-
);
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Zeichne Teppich
|
|
310
|
-
if (mapdata.CARPET_MAP) {
|
|
311
|
-
const offset = 8 * this.scaleFactor;
|
|
312
|
-
ctx.fillStyle = "rgba(0,0,0,0.5)";
|
|
313
|
-
|
|
314
|
-
const imageData = ctx.getImageData(0, 0, mapdata.IMAGE.dimensions.width, mapdata.IMAGE.dimensions.height);
|
|
315
|
-
mapdata.CARPET_MAP.forEach((px) => {
|
|
316
|
-
const x2 = this.getX(mapdata.IMAGE.dimensions, px) - offset;
|
|
317
|
-
const y1 = this.getY(mapdata.IMAGE.dimensions, px);
|
|
318
|
-
const x1 = x2 + this.scaleFactor - 1;
|
|
319
|
-
const y2 = y1 + this.scaleFactor - 1;
|
|
320
|
-
|
|
321
|
-
this.drawLineBresenham(imageData, x1, y1, x2, y2);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// Zeichne das ganze ImageData auf einmal
|
|
325
|
-
ctx.putImageData(imageData, 0, 0);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Male den Wischpfad
|
|
329
|
-
if (mapdata.PATH && mapdata.MOP_PATH) {
|
|
330
|
-
const mopOffset = -12; // i dont know why this offset?? Maybe the value from the end
|
|
331
|
-
if (mapdata.PATH.points && mapdata.PATH.points.length !== 0) {
|
|
332
|
-
let startX, startY; // this is needed to avoid weird spikes in sharp corners! don't remove this ever!
|
|
333
|
-
|
|
334
|
-
ctx.beginPath();
|
|
335
|
-
ctx.lineWidth = 7 * this.scaleFactor; // 7 makes the mop path look the same as on the Roborock app
|
|
336
|
-
ctx.lineCap = "round";
|
|
337
|
-
ctx.strokeStyle = "rgba(255,255,255,0.2)";
|
|
338
|
-
|
|
339
|
-
mapdata.PATH.points.forEach((coord, index) => {
|
|
340
|
-
if (mapdata.MOP_PATH && mapdata.MOP_PATH[index + mopOffset] !== 0) {
|
|
341
|
-
if (mapdata.MOP_PATH[index - 1 + mopOffset] === 0) {
|
|
342
|
-
startX = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
|
|
343
|
-
startY = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
|
|
344
|
-
ctx.moveTo(startX, startY);
|
|
345
|
-
if (mapdata.MOP_PATH[index + mopOffset] !== 1) {
|
|
346
|
-
// see value 9 and 12 in mop_path both in front of charger
|
|
347
|
-
}
|
|
348
|
-
} else if (mapdata.MOP_PATH[index - 1 + mopOffset] === 1) {
|
|
349
|
-
ctx.moveTo(startX, startY);
|
|
350
|
-
ctx.lineTo(this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50), this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50));
|
|
351
|
-
startX = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
|
|
352
|
-
startY = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
|
|
353
|
-
}
|
|
354
|
-
} else if (mapdata.MOP_PATH && mapdata.MOP_PATH[index + mopOffset] === 0) {
|
|
355
|
-
if (mapdata.MOP_PATH[index - 1 + mopOffset] !== 0) {
|
|
356
|
-
// do nothing ??
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
ctx.stroke();
|
|
361
|
-
ctx.closePath();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Male den Pfad
|
|
366
|
-
if (mapdata.PATH) {
|
|
367
|
-
if (mapdata.PATH.points && mapdata.PATH.points.length !== 0) {
|
|
368
|
-
ctx.fillStyle = colors.path;
|
|
369
|
-
let first = true;
|
|
370
|
-
let cold1, cold2;
|
|
371
|
-
|
|
372
|
-
ctx.beginPath();
|
|
373
|
-
mapdata.PATH.points.forEach((coord) => {
|
|
374
|
-
if (first) {
|
|
375
|
-
(cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)),
|
|
376
|
-
(cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)),
|
|
377
|
-
ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);
|
|
378
|
-
first = false;
|
|
379
|
-
} else {
|
|
380
|
-
ctx.lineWidth = this.scaleFactor / 2;
|
|
381
|
-
ctx.strokeStyle = colors.path;
|
|
382
|
-
|
|
383
|
-
ctx.moveTo(cold1, cold2);
|
|
384
|
-
(cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)), (cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)), ctx.lineTo(cold1, cold2);
|
|
385
|
-
// ctx.stroke();
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
ctx.stroke();
|
|
389
|
-
ctx.closePath();
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Male geplanten Pfad
|
|
394
|
-
if (mapdata.GOTO_PREDICTED_PATH) {
|
|
395
|
-
if (mapdata.GOTO_PREDICTED_PATH.points && mapdata.GOTO_PREDICTED_PATH.points.length !== 0) {
|
|
396
|
-
let cold1, cold2;
|
|
397
|
-
ctx.lineWidth = (3 * this.scaleFactor) / 2;
|
|
398
|
-
ctx.strokeStyle = "rgba(255, 255, 255, 1)";
|
|
399
|
-
ctx.setLineDash([3 * this.scaleFactor, 3 * this.scaleFactor]);
|
|
400
|
-
ctx.lineCap = "round";
|
|
401
|
-
ctx.beginPath();
|
|
402
|
-
mapdata.GOTO_PREDICTED_PATH.points.forEach((coord, index) => {
|
|
403
|
-
if (index === 0) {
|
|
404
|
-
cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
|
|
405
|
-
cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
|
|
406
|
-
ctx.fillStyle = "rgba(255, 255, 255, 1)";
|
|
407
|
-
ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);
|
|
408
|
-
ctx.moveTo(cold1, cold2);
|
|
409
|
-
} else {
|
|
410
|
-
const newCold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
|
|
411
|
-
const newCold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
|
|
412
|
-
if (cold1 !== newCold1 || cold2 !== newCold2) {
|
|
413
|
-
ctx.lineTo(newCold1, newCold2);
|
|
414
|
-
cold1 = newCold1;
|
|
415
|
-
cold2 = newCold2;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
ctx.stroke();
|
|
420
|
-
ctx.setLineDash([]);
|
|
421
|
-
ctx.lineCap = "butt";
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (mapdata.FORBIDDEN_ZONES) {
|
|
426
|
-
mapdata.FORBIDDEN_ZONES.forEach((zone) => {
|
|
427
|
-
const forbiddenMinX = Math.min(zone[0], zone[2], zone[4], zone[6]);
|
|
428
|
-
const forbiddenMinY = Math.min(zone[1], zone[3], zone[5], zone[7]);
|
|
429
|
-
const forbiddenMaxX = Math.max(zone[0], zone[2], zone[4], zone[6]);
|
|
430
|
-
const forbiddenMaxY = Math.max(zone[1], zone[3], zone[5], zone[7]);
|
|
431
|
-
|
|
432
|
-
const forbiddenSizeX = forbiddenMaxX - forbiddenMinX;
|
|
433
|
-
const forbiddenSizeY = forbiddenMaxY - forbiddenMinY;
|
|
434
|
-
|
|
435
|
-
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
|
|
436
|
-
ctx.fillRect(
|
|
437
|
-
this.robotXtoPixelX(mapdata.IMAGE, forbiddenMinX / 50),
|
|
438
|
-
this.robotYtoPixelY(mapdata.IMAGE, forbiddenMaxY / 50),
|
|
439
|
-
(forbiddenSizeX / 50) * this.scaleFactor,
|
|
440
|
-
(forbiddenSizeY / 50) * this.scaleFactor
|
|
441
|
-
);
|
|
442
|
-
ctx.lineWidth = (1 * this.scaleFactor) / 2;
|
|
443
|
-
ctx.strokeStyle = "rgba(255, 0, 0, 1)";
|
|
444
|
-
ctx.strokeRect(
|
|
445
|
-
this.robotXtoPixelX(mapdata.IMAGE, forbiddenMinX / 50),
|
|
446
|
-
this.robotYtoPixelY(mapdata.IMAGE, forbiddenMaxY / 50),
|
|
447
|
-
(forbiddenSizeX / 50) * this.scaleFactor,
|
|
448
|
-
(forbiddenSizeY / 50) * this.scaleFactor
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (mapdata.VIRTUAL_WALLS) {
|
|
454
|
-
mapdata.VIRTUAL_WALLS.forEach((wall) => {
|
|
455
|
-
const startX = this.robotXtoPixelX(mapdata.IMAGE, wall[0] / 50) + this.scaleFactor;
|
|
456
|
-
const startY = this.robotYtoPixelY(mapdata.IMAGE, wall[1] / 50) + this.scaleFactor;
|
|
457
|
-
const endX = this.robotXtoPixelX(mapdata.IMAGE, wall[2] / 50) + this.scaleFactor;
|
|
458
|
-
const endY = this.robotYtoPixelY(mapdata.IMAGE, wall[3] / 50) + this.scaleFactor;
|
|
459
|
-
|
|
460
|
-
// Calculate start end end of vector
|
|
461
|
-
let vecX = endX - startX;
|
|
462
|
-
let vecY = endY - startY;
|
|
463
|
-
|
|
464
|
-
// Normalize vector
|
|
465
|
-
const len = Math.sqrt(vecX * vecX + vecY * vecY);
|
|
466
|
-
vecX /= len;
|
|
467
|
-
vecY /= len;
|
|
468
|
-
|
|
469
|
-
// Line width
|
|
470
|
-
const lineWidth = 1 * this.scaleFactor;
|
|
471
|
-
|
|
472
|
-
// Adjust start and end of the line
|
|
473
|
-
const adjustedStartX = startX + vecX * (lineWidth / 2);
|
|
474
|
-
const adjustedStartY = startY + vecY * (lineWidth / 2);
|
|
475
|
-
const adjustedEndX = endX - vecX * (lineWidth / 2);
|
|
476
|
-
const adjustedEndY = endY - vecY * (lineWidth / 2);
|
|
477
|
-
|
|
478
|
-
ctx.lineWidth = lineWidth;
|
|
479
|
-
ctx.strokeStyle = "rgba(255, 0, 0, 1)";
|
|
480
|
-
|
|
481
|
-
ctx.beginPath();
|
|
482
|
-
ctx.moveTo(adjustedStartX, adjustedStartY);
|
|
483
|
-
ctx.lineTo(adjustedEndX, adjustedEndY);
|
|
484
|
-
ctx.stroke();
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (mapdata.NO_MOP_ZONE) {
|
|
489
|
-
mapdata.NO_MOP_ZONE.forEach((zone) => {
|
|
490
|
-
const noMopMinX = Math.min(zone[0], zone[2], zone[4], zone[6]);
|
|
491
|
-
const noMopMinY = Math.min(zone[1], zone[3], zone[5], zone[7]);
|
|
492
|
-
const noMopMaxX = Math.max(zone[0], zone[2], zone[4], zone[6]);
|
|
493
|
-
const noMopMaxY = Math.max(zone[1], zone[3], zone[5], zone[7]);
|
|
494
|
-
|
|
495
|
-
const noMopSizeX = noMopMaxX - noMopMinX;
|
|
496
|
-
const noMopSizeY = noMopMaxY - noMopMinY;
|
|
497
|
-
|
|
498
|
-
ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
|
|
499
|
-
ctx.fillRect(
|
|
500
|
-
this.robotXtoPixelX(mapdata.IMAGE, noMopMinX / 50),
|
|
501
|
-
this.robotYtoPixelY(mapdata.IMAGE, noMopMaxY / 50),
|
|
502
|
-
(noMopSizeX / 50) * this.scaleFactor,
|
|
503
|
-
(noMopSizeY / 50) * this.scaleFactor
|
|
504
|
-
);
|
|
505
|
-
ctx.lineWidth = (1 * this.scaleFactor) / 2;
|
|
506
|
-
ctx.strokeStyle = "rgba(0, 0, 255, 1)";
|
|
507
|
-
ctx.strokeRect(
|
|
508
|
-
this.robotXtoPixelX(mapdata.IMAGE, noMopMinX / 50),
|
|
509
|
-
this.robotYtoPixelY(mapdata.IMAGE, noMopMaxY / 50),
|
|
510
|
-
(noMopSizeX / 50) * this.scaleFactor,
|
|
511
|
-
(noMopSizeY / 50) * this.scaleFactor
|
|
512
|
-
);
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if (mapdata.OBSTACLES2) {
|
|
517
|
-
mapdata.OBSTACLES2.forEach((obstacle) => {
|
|
518
|
-
const obstacleType = obstacle[2];
|
|
519
|
-
const obstacleTitle = obstacleTitles[obstacleType] || "Unknown";
|
|
520
|
-
const obstacleConfidence = Math.round(obstacle[3] / 100);
|
|
521
|
-
const obstacleText = obstacleTitle[obstacleType] ? obstacleTitle + "(" + obstacleConfidence + "%)" : obstacleTitle;
|
|
522
|
-
|
|
523
|
-
const x = this.robotXtoPixelX(mapdata.IMAGE, obstacle[0] / 50);
|
|
524
|
-
const y = this.robotYtoPixelY(mapdata.IMAGE, obstacle[1] / 50);
|
|
525
|
-
|
|
526
|
-
ctx.fillStyle = "red";
|
|
527
|
-
ctx.beginPath();
|
|
528
|
-
ctx.arc(x, y, 5, 0, 2 * Math.PI);
|
|
529
|
-
ctx.fill();
|
|
530
|
-
|
|
531
|
-
// Set the text properties
|
|
532
|
-
ctx.font = "14px sans-serif";
|
|
533
|
-
ctx.fillStyle = "white";
|
|
534
|
-
ctx.textBaseline = "middle";
|
|
535
|
-
ctx.textAlign = "center";
|
|
536
|
-
|
|
537
|
-
// Calculate the text width and height
|
|
538
|
-
const textWidth = ctx.measureText(obstacleText).width;
|
|
539
|
-
const textHeight = parseInt(ctx.font, 10);
|
|
540
|
-
|
|
541
|
-
// Calculate the position and dimensions of the background rectangle
|
|
542
|
-
const padding = 5;
|
|
543
|
-
const borderRadius = 5;
|
|
544
|
-
const rectX = x - textWidth / 2 - padding;
|
|
545
|
-
const rectY = y + 5 + padding / 2;
|
|
546
|
-
const rectWidth = textWidth + 2 * padding;
|
|
547
|
-
const rectHeight = textHeight + padding;
|
|
548
|
-
|
|
549
|
-
// Draw the background rectangle with rounded corners
|
|
550
|
-
ctx.fillStyle = "red";
|
|
551
|
-
ctx.beginPath();
|
|
552
|
-
ctx.moveTo(rectX + borderRadius, rectY);
|
|
553
|
-
ctx.lineTo(rectX + rectWidth - borderRadius, rectY);
|
|
554
|
-
ctx.quadraticCurveTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + borderRadius);
|
|
555
|
-
ctx.lineTo(rectX + rectWidth, rectY + rectHeight - borderRadius);
|
|
556
|
-
ctx.quadraticCurveTo(rectX + rectWidth, rectY + rectHeight, rectX + rectWidth - borderRadius, rectY + rectHeight);
|
|
557
|
-
ctx.lineTo(rectX + borderRadius, rectY + rectHeight);
|
|
558
|
-
ctx.quadraticCurveTo(rectX, rectY + rectHeight, rectX, rectY + rectHeight - borderRadius);
|
|
559
|
-
ctx.lineTo(rectX, rectY + borderRadius);
|
|
560
|
-
ctx.quadraticCurveTo(rectX, rectY, rectX + borderRadius, rectY);
|
|
561
|
-
ctx.closePath();
|
|
562
|
-
ctx.fill();
|
|
563
|
-
|
|
564
|
-
// Draw the white text centered within the background rectangle
|
|
565
|
-
ctx.fillStyle = "white";
|
|
566
|
-
ctx.fillText(obstacleText, x, y + 5 + padding + textHeight / 2);
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Zeichne Ladestation wenn vorhanden
|
|
571
|
-
if (mapdata.CHARGER_LOCATION) {
|
|
572
|
-
if (mapdata.CHARGER_LOCATION.position[0] && mapdata.CHARGER_LOCATION.position[1]) {
|
|
573
|
-
ctx.beginPath();
|
|
574
|
-
const img_charger_rotated = this.rotateCanvas(img_charger, mapdata.CHARGER_LOCATION.angle);
|
|
575
|
-
ctx.drawImage(
|
|
576
|
-
img_charger_rotated,
|
|
577
|
-
this.robotXtoPixelX(mapdata.IMAGE, mapdata.CHARGER_LOCATION.position[0] / 50) - img_charger_rotated.width / 2,
|
|
578
|
-
this.robotYtoPixelY(mapdata.IMAGE, mapdata.CHARGER_LOCATION.position[1] / 50) - img_charger_rotated.height / 2,
|
|
579
|
-
img_charger_rotated.width,
|
|
580
|
-
img_charger_rotated.height
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Zeichne Roboter
|
|
586
|
-
if (mapdata.ROBOT_POSITION) {
|
|
587
|
-
if (mapdata.PATH.current_angle && mapdata.ROBOT_POSITION[0] && mapdata.ROBOT_POSITION[1]) {
|
|
588
|
-
ctx.beginPath();
|
|
589
|
-
const canvasImg = this.rotateCanvas(img, mapdata.PATH.current_angle);
|
|
590
|
-
ctx.drawImage(
|
|
591
|
-
canvasImg,
|
|
592
|
-
this.robotXtoPixelX(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[0] / 50) - img.width / 4,
|
|
593
|
-
this.robotYtoPixelY(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[1] / 50) - img.height / 4,
|
|
594
|
-
canvasImg.width / 2,
|
|
595
|
-
canvasImg.height / 2
|
|
596
|
-
);
|
|
597
|
-
} else {
|
|
598
|
-
const img_robot_rotated = this.rotateCanvas(img, mapdata.ROBOT_POSITION.angle);
|
|
599
|
-
ctx.drawImage(
|
|
600
|
-
img_robot_rotated,
|
|
601
|
-
this.robotXtoPixelX(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[0] / 50) - img_robot_rotated.width / 4,
|
|
602
|
-
this.robotYtoPixelY(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[1] / 50) - img_robot_rotated.height / 4,
|
|
603
|
-
img_robot_rotated.width / 2,
|
|
604
|
-
img_robot_rotated.height / 2
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Zeichne Zielposition wenn vorhanden
|
|
610
|
-
if (mapdata.GOTO_TARGET) {
|
|
611
|
-
const go_to_pin_img = new Image();
|
|
612
|
-
go_to_pin_img.src = go_to_pin;
|
|
613
|
-
|
|
614
|
-
if (mapdata.GOTO_TARGET[0] && mapdata.GOTO_TARGET[1]) {
|
|
615
|
-
ctx.beginPath();
|
|
616
|
-
ctx.drawImage(
|
|
617
|
-
go_to_pin_img,
|
|
618
|
-
this.robotXtoPixelX(mapdata.IMAGE, mapdata.GOTO_TARGET[0] / 50) - go_to_pin_img.width / 2,
|
|
619
|
-
this.robotYtoPixelY(mapdata.IMAGE, mapdata.GOTO_TARGET[1] / 50) - (go_to_pin_img.height + 6),
|
|
620
|
-
go_to_pin_img.width,
|
|
621
|
-
go_to_pin_img.height
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Draw room names with background
|
|
627
|
-
ctx.beginPath();
|
|
628
|
-
Object.keys(segmentsData).forEach((segnum) => {
|
|
629
|
-
const segment = segmentsData[segnum];
|
|
630
|
-
|
|
631
|
-
let roomName = "";
|
|
632
|
-
|
|
633
|
-
if (typeof selectedMap != "undefined") {
|
|
634
|
-
// cannot get room name if map is from map history
|
|
635
|
-
|
|
636
|
-
for (const mappedRoom of mappedRooms) {
|
|
637
|
-
const segmentID = mappedRoom[0];
|
|
638
|
-
const roomID = mappedRoom[1];
|
|
639
|
-
|
|
640
|
-
if (segmentID == segnum) {
|
|
641
|
-
roomName = this.adapter.roomIDs[roomID];
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (roomName != "") {
|
|
647
|
-
const centerX = segment.minX + (segment.maxX - segment.minX) / 2;
|
|
648
|
-
const centerY = segment.minY + (segment.maxY - segment.minY) / 2;
|
|
649
|
-
|
|
650
|
-
ctx.font = "bold 16px Arial";
|
|
651
|
-
const textWidth = ctx.measureText(roomName).width;
|
|
652
|
-
const textHeight = 16;
|
|
653
|
-
const padding = 10;
|
|
654
|
-
const backgroundWidth = textWidth + 2 * padding;
|
|
655
|
-
const backgroundHeight = textHeight + 2 * padding;
|
|
656
|
-
|
|
657
|
-
// fake square for shadow
|
|
658
|
-
const imgdata = ctx.getImageData(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
|
|
659
|
-
ctx.shadowOffsetX = 4;
|
|
660
|
-
ctx.shadowOffsetY = 4;
|
|
661
|
-
ctx.shadowBlur = 5;
|
|
662
|
-
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
|
|
663
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0)";
|
|
664
|
-
ctx.fillRect(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
|
|
665
|
-
ctx.putImageData(imgdata, centerX - backgroundWidth / 2, centerY - backgroundHeight / 2);
|
|
666
|
-
|
|
667
|
-
// draw actual square over fake square to have a sharp shadow
|
|
668
|
-
ctx.shadowColor = "transparent";
|
|
669
|
-
ctx.fillStyle = this.hexToRGBA(assignedColors[segnum], 0.75);
|
|
670
|
-
ctx.fillRect(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
|
|
671
|
-
|
|
672
|
-
ctx.fillStyle = "black";
|
|
673
|
-
ctx.textAlign = "center";
|
|
674
|
-
ctx.textBaseline = "middle";
|
|
675
|
-
ctx.fillText(roomName, centerX, centerY);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
ctx.closePath();
|
|
680
|
-
|
|
681
|
-
// crop image
|
|
682
|
-
const canvas_trimmed = createCanvas(maxleft - minleft + 2 * offset, maxtop - mintop + 2 * offset);
|
|
683
|
-
const ctx_trimmed = canvas_trimmed.getContext("2d");
|
|
684
|
-
const trimmed = ctx.getImageData(minleft - offset, mintop - offset, maxleft - minleft + 2 * offset, maxtop - mintop + offset);
|
|
685
|
-
|
|
686
|
-
ctx_trimmed.putImageData(trimmed, 0, 0);
|
|
687
|
-
|
|
688
|
-
return [canvas.toDataURL(), canvas_trimmed.toDataURL()];
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
module.exports = MapCreator;
|