Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the calibration matrix based on SVG viewbox #715

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since HA 2024.2 the custom component is merged into the cores Ecovacs integration and I will archive the custom component soon. I think this file needs to be renamed

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any functional changes related to this card because of it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No the migration itself has no functional changes.
With the release of 3.1.0 the integration switched from png to SVG and that change can caused some functional changes. As I don't use your card, I completely forgotten to test it.

The core integration uses also the SVG image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, do I need to make some changes? In either PR? Or just be patient?

Original file line number Diff line number Diff line change
Expand Up @@ -149,29 +149,32 @@
"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
}
}
]
],
"internal_variables": {
"svgmap": "true"
}
}
11 changes: 7 additions & 4 deletions src/model/map_objects/coordinates-converter.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down
78 changes: 73 additions & 5 deletions src/xiaomi-vacuum-map-card.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { css, CSSResultGroup, html, LitElement, PropertyValues, svg, SVGTemplateResult, TemplateResult } from "lit";
import { customElement, property, query, queryAll, state } from "lit/decorators";

Check warning on line 3 in src/xiaomi-vacuum-map-card.ts

View workflow job for this annotation

GitHub Actions / Build

'customElement' is defined but never used

Check warning on line 3 in src/xiaomi-vacuum-map-card.ts

View workflow job for this annotation

GitHub Actions / Build

'property' is defined but never used

Check warning on line 3 in src/xiaomi-vacuum-map-card.ts

View workflow job for this annotation

GitHub Actions / Build

'query' is defined but never used

Check warning on line 3 in src/xiaomi-vacuum-map-card.ts

View workflow job for this annotation

GitHub Actions / Build

'queryAll' is defined but never used

Check warning on line 3 in src/xiaomi-vacuum-map-card.ts

View workflow job for this annotation

GitHub Actions / Build

'state' is defined but never used
import { ActionHandlerEvent, forwardHaptic, LovelaceCard, LovelaceCardEditor } from "custom-card-helpers";

import "./editor";
Expand All @@ -8,6 +8,7 @@
ActionableObjectConfig,
IconActionConfig,
LovelaceDomEvent,
Point,
PredefinedPointConfig,
ReplacedKey,
RoomConfig,
Expand Down Expand Up @@ -121,6 +122,7 @@
@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<HTMLElement>;
private currentPreset!: CardPresetConfig;
Expand Down Expand Up @@ -283,12 +285,17 @@
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;">
<img
${conditional(
this._isSvgMapImage(),
() => html`<object id="map-object" data="${mapSrc}" width="0" height="0" type="image/svg+xml" @load="${() => this._updateImageSizeAndScale()}"></object>`
)}
<img
id="map-image"
alt="camera_image"
class="${this.mapScale * this.realScale > 1 ? "zoomed" : ""}"
src="${mapSrc}"
@load="${() => this._calculateBasicScale()}" />
@load="${() => this._updateImageSizeAndScale()}"
/>
<div id="map-image-overlay">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand Down Expand Up @@ -587,7 +594,7 @@
private _updateCalibration(config: CardPresetConfig): void {
this.coordinatesConverter = undefined;
const calibrationPoints = this._getCalibration(config);
this.coordinatesConverter = new CoordinatesConverter(calibrationPoints);
this.coordinatesConverter = new CoordinatesConverter(calibrationPoints, this._getOffset());
}

private _getMapSrc(config: CardPresetConfig): string {
Expand Down Expand Up @@ -1207,12 +1214,69 @@
delay(300).then(() => (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 _isSvgMapImage(): boolean {
return !!this.internalVariables['svgmap'];
}

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 _updateSimpleImageSize(): void {
const mapImage = this._getMapImage();

if (mapImage && mapImage.naturalWidth > 0) {
this.realImageWidth = mapImage.naturalWidth;
this.realImageHeight = mapImage.naturalHeight;
this.realScale = mapImage.width / mapImage.naturalWidth;
}
}

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 _updateImageSizeAndScale(): void {
if (this._isSvgMapImage()) {
this._updateSvgImageSize();
} else {
this._updateSimpleImageSize();
}

this._calculateBasicScale();
}

private _calculateBasicScale(): void {
const mapImage = this._getMapImage();

if (mapImage && mapImage.offsetWidth && this.realImageWidth) {
this.realScale = mapImage.offsetWidth / this.realImageWidth;
} else {
this.realScale = 1;
}
}

Expand All @@ -1227,6 +1291,10 @@
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;
}
Expand Down
Loading