From 56a31434f2e16adf7d7a03eaa5093364f2a63a9e Mon Sep 17 00:00:00 2001 From: Filip Rutkowski Date: Sun, 25 Feb 2024 16:32:00 +0100 Subject: [PATCH 1/2] Update the calibration matrix based on SVG viewbox --- ...eebotUniverse_Deebot-4-Home-Assistant.json | 16 ++--- .../map_objects/coordinates-converter.ts | 11 +-- src/xiaomi-vacuum-map-card.ts | 67 +++++++++++++++++-- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index 69f965ce..5cf2328e 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -149,28 +149,28 @@ "y": 0 }, "map": { - "x": 400, - "y": 400 + "x": 0, + "y": 0 } }, { "vacuum": { - "x": 6400, + "x": 50, "y": 0 }, "map": { - "x": 528, - "y": 400 + "x": 1, + "y": 0 } }, { "vacuum": { "x": 0, - "y": 6400 + "y": -50 }, "map": { - "x": 400, - "y": 528 + "x": 0, + "y": 1 } } ] diff --git a/src/model/map_objects/coordinates-converter.ts b/src/model/map_objects/coordinates-converter.ts index 132529d1..e77fde0e 100644 --- a/src/model/map_objects/coordinates-converter.ts +++ b/src/model/map_objects/coordinates-converter.ts @@ -1,7 +1,7 @@ -import { applyToPoint, fromTriangles, Matrix } from "transformation-matrix"; +import { applyToPoint, compose, fromTriangles, inverse, Matrix, translate } from "transformation-matrix"; import { default as transformer, QuadPoints } from "change-perspective"; -import { CalibrationPoint, PointType } from "../../types/types"; +import { CalibrationPoint, Point, PointType } from "../../types/types"; enum TransformMode { AFFINE, @@ -16,14 +16,17 @@ export class CoordinatesConverter { private readonly mapToVacuumTransformer: ((x: number, y: number) => [number, number]) | undefined; private readonly vacuumToMapTransformer: ((x: number, y: number) => [number, number]) | undefined; - constructor(calibrationPoints: CalibrationPoint[] | undefined) { + constructor(calibrationPoints: CalibrationPoint[] | undefined, offset?: Point) { const mapPoints = calibrationPoints?.map(cp => cp.map); const vacuumPoints = calibrationPoints?.map(cp => cp.vacuum); if (mapPoints && vacuumPoints) { if (mapPoints.length === 3) { this.transformMode = TransformMode.AFFINE; - this.mapToVacuumMatrix = fromTriangles(mapPoints, vacuumPoints); this.vacuumToMapMatrix = fromTriangles(vacuumPoints, mapPoints); + if (offset) { + this.vacuumToMapMatrix = compose(translate(offset.x, offset.y), this.vacuumToMapMatrix) + } + this.mapToVacuumMatrix = inverse(this.vacuumToMapMatrix) this.calibrated = !!(this.mapToVacuumMatrix && this.vacuumToMapMatrix); } else { this.transformMode = TransformMode.PERSPECTIVE; diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 2b4209e7..1c37de42 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -8,6 +8,7 @@ import type { ActionableObjectConfig, IconActionConfig, LovelaceDomEvent, + Point, PredefinedPointConfig, ReplacedKey, RoomConfig, @@ -121,6 +122,7 @@ export class XiaomiVacuumMapCard extends LitElement { @state() private configErrors: string[] = []; @state() private connected = false; @state() public internalVariables = {}; + @state() private mapViewBox?: DOMRect; @query(".modes-dropdown-menu") private _modesDropdownMenu?: HTMLElement; @queryAll(".icon-dropdown-menu") private _iconDropdownMenus?: NodeListOf; private currentPreset!: CardPresetConfig; @@ -283,12 +285,14 @@ export class XiaomiVacuumMapCard extends LitElement { margin-bottom: ${(preset.map_source.crop?.bottom ?? 0) * -1}px; margin-left: ${(preset.map_source.crop?.left ?? 0) * -1}px; margin-right: ${(preset.map_source.crop?.right ?? 0) * -1}px;"> - + camera_image + @load="${() => this._updateImageSize()}" + />
(zoomerContent.style.transitionDuration = "0s")); } - private _calculateBasicScale(): void { + private _isSameViewbox(viewBox: DOMRect): boolean { + if (this.mapViewBox === undefined) return false; + for (const f in ["x", "y", "width", "height"]) { + if (viewBox[f] !== this.mapViewBox[f]) return false + } + return true; + } + + private _updateSvgImageSize(): void { + const mapObject = this._getMapObject(); + + if (mapObject.contentDocument?.documentElement.nodeName !== 'svg') { + this.mapViewBox = undefined; + return; + } + + const viewBoxRect = (mapObject.contentDocument.documentElement as unknown as SVGSVGElement).viewBox.baseVal; + if (this._isSameViewbox(viewBoxRect)) { + return; + } + this.mapViewBox = viewBoxRect; + + this.realImageHeight = viewBoxRect.height; + this.realImageWidth = viewBoxRect.width; + } + + private _getOffset(): Point | undefined { + if (this.mapViewBox === undefined || (this.mapViewBox.x === 0 && this.mapViewBox.y === 0)) { + return undefined; + } + return {x: -this.mapViewBox.x, y: -this.mapViewBox.y} + } + + private _updateImageSize(): void { const mapImage = this._getMapImage(); - if (mapImage && mapImage.naturalWidth > 0) { + + if (mapImage.naturalWidth > 0) { this.realImageWidth = mapImage.naturalWidth; this.realImageHeight = mapImage.naturalHeight; - this.realScale = mapImage.width / mapImage.naturalWidth; + } else { + this._updateSvgImageSize(); + } + + this._calculateBasicScale(); + } + + private _calculateBasicScale(): void { + const mapImage = this._getMapImage(); + + if (mapImage.offsetWidth && this.realImageWidth) { + this.realScale = mapImage.offsetWidth / this.realImageWidth; + } else { + this.realScale = 1; } } @@ -1227,6 +1278,10 @@ export class XiaomiVacuumMapCard extends LitElement { return this.shadowRoot?.getElementById("map-zoomer") as PinchZoom; } + private _getMapObject(): HTMLObjectElement { + return this.shadowRoot?.getElementById("map-object") as HTMLObjectElement; + } + private _getMapImage(): HTMLImageElement { return this.shadowRoot?.getElementById("map-image") as HTMLImageElement; } From 109ce7600cae44f95730436814dc244e6a3428fd Mon Sep 17 00:00:00 2001 From: Filip Rutkowski Date: Sat, 2 Mar 2024 19:41:35 +0100 Subject: [PATCH 2/2] Use SVG mode only for deebot --- ...eebotUniverse_Deebot-4-Home-Assistant.json | 5 ++- src/xiaomi-vacuum-map-card.ts | 33 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index 5cf2328e..2d06197e 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -173,5 +173,8 @@ "y": 1 } } - ] + ], + "internal_variables": { + "svgmap": "true" + } } diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 1c37de42..d1005187 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -285,13 +285,16 @@ export class XiaomiVacuumMapCard extends LitElement { margin-bottom: ${(preset.map_source.crop?.bottom ?? 0) * -1}px; margin-left: ${(preset.map_source.crop?.left ?? 0) * -1}px; margin-right: ${(preset.map_source.crop?.right ?? 0) * -1}px;"> - + ${conditional( + this._isSvgMapImage(), + () => html`` + )} camera_image
0) { + this.realImageWidth = mapImage.naturalWidth; + this.realImageHeight = mapImage.naturalHeight; + } + } + private _getOffset(): Point | undefined { if (this.mapViewBox === undefined || (this.mapViewBox.x === 0 && this.mapViewBox.y === 0)) { return undefined; @@ -1244,14 +1260,11 @@ export class XiaomiVacuumMapCard extends LitElement { return {x: -this.mapViewBox.x, y: -this.mapViewBox.y} } - private _updateImageSize(): void { - const mapImage = this._getMapImage(); - - if (mapImage.naturalWidth > 0) { - this.realImageWidth = mapImage.naturalWidth; - this.realImageHeight = mapImage.naturalHeight; - } else { + private _updateImageSizeAndScale(): void { + if (this._isSvgMapImage()) { this._updateSvgImageSize(); + } else { + this._updateSimpleImageSize(); } this._calculateBasicScale(); @@ -1260,7 +1273,7 @@ export class XiaomiVacuumMapCard extends LitElement { private _calculateBasicScale(): void { const mapImage = this._getMapImage(); - if (mapImage.offsetWidth && this.realImageWidth) { + if (mapImage && mapImage.offsetWidth && this.realImageWidth) { this.realScale = mapImage.offsetWidth / this.realImageWidth; } else { this.realScale = 1;