Skip to content

Commit

Permalink
Update the calibration matrix based on SVG viewbox
Browse files Browse the repository at this point in the history
  • Loading branch information
Filip Rutkowski authored and Filip Rutkowski committed Feb 25, 2024
1 parent 2b13470 commit 56a3143
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
]
Expand Down
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
67 changes: 61 additions & 6 deletions src/xiaomi-vacuum-map-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ActionableObjectConfig,
IconActionConfig,
LovelaceDomEvent,
Point,
PredefinedPointConfig,
ReplacedKey,
RoomConfig,
Expand Down Expand Up @@ -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<HTMLElement>;
private currentPreset!: CardPresetConfig;
Expand Down Expand Up @@ -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;">
<img
<object id="map-object" data="${mapSrc}" style="display: none;" @load="${() => this._updateImageSize()}"></object>
<img
id="map-image"
alt="camera_image"
class="${this.mapScale * this.realScale > 1 ? "zoomed" : ""}"
src="${mapSrc}"
@load="${() => this._calculateBasicScale()}" />
@load="${() => this._updateImageSize()}"
/>
<div id="map-image-overlay">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand Down Expand Up @@ -587,7 +591,7 @@ export class XiaomiVacuumMapCard extends LitElement {
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 +1211,59 @@ export class XiaomiVacuumMapCard extends LitElement {
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 _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;
}
}

Expand All @@ -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;
}
Expand Down

0 comments on commit 56a3143

Please sign in to comment.