Farmbot-Web-App/frontend/farm_designer/point_groups/paths.tsx

131 lines
4.2 KiB
TypeScript

import * as React from "react";
import { MapTransformProps } from "../map/interfaces";
import { sortGroupBy, sortOptionsTable } from "./point_group_sort_selector";
import { sortBy } from "lodash";
import { PointsPathLine } from "./group_order_visual";
import { Color } from "../../ui";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper";
import { Actions } from "../../constants";
import { edit } from "../../api/crud";
import { TaggedPointGroup, TaggedPoint } from "farmbot";
import { error } from "../../toast/toast";
const xy = (point: TaggedPoint) => ({ x: point.body.x, y: point.body.y });
const distance = (p1: { x: number, y: number }, p2: { x: number, y: number }) =>
Math.pow(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2), 0.5);
const pathDistance = (pathPoints: TaggedPoint[]) => {
let total = 0;
let prev: { x: number, y: number } | undefined = undefined;
pathPoints.map(xy)
.map(p => {
prev ? total += distance(p, prev) : 0;
prev = p;
});
return Math.round(total);
};
const findNearest =
(from: { x: number, y: number }, available: TaggedPoint[]) => {
const distances = available.map(p => ({
point: p, distance: distance(xy(p), from)
}));
return sortBy(distances, "distance")[0].point;
};
export const nn = (pathPoints: TaggedPoint[]) => {
let available = pathPoints.slice(0);
const ordered: TaggedPoint[] = [];
let from = { x: 0, y: 0 };
pathPoints.map(() => {
if (available.length < 1) { return; }
const nearest = findNearest(from, available);
ordered.push(nearest);
from = { x: nearest.body.x, y: nearest.body.y };
available = available.filter(p => p.uuid !== nearest.uuid);
});
return ordered;
};
const SORT_TYPES: (PointGroupSortType | "nn")[] = [
"random", "xy_ascending", "xy_descending", "yx_ascending", "yx_descending"];
export interface PathInfoBarProps {
sortTypeKey: PointGroupSortType | "nn";
dispatch: Function;
group: TaggedPointGroup;
pathData: { [key: string]: number };
}
export const PathInfoBar = (props: PathInfoBarProps) => {
const { sortTypeKey, dispatch, group } = props;
const pathLength = props.pathData[sortTypeKey];
const maxLength = Math.max(...Object.values(props.pathData));
const normalizedLength = pathLength / maxLength * 100;
const sortLabel =
sortTypeKey == "nn" ? "Optimized" : sortOptionsTable()[sortTypeKey];
return <div className={"sort-path-info-bar"}
onMouseEnter={() =>
dispatch({ type: Actions.TRY_SORT_TYPE, payload: sortTypeKey })}
onMouseLeave={() =>
dispatch({ type: Actions.TRY_SORT_TYPE, payload: undefined })}
onClick={() =>
sortTypeKey == "nn"
? error(t("Not supported yet."))
: dispatch(edit(group, { sort_type: sortTypeKey }))}
style={{ width: `${normalizedLength}%` }}>
{`${sortLabel}: ${Math.round(pathLength / 10) / 100}m`}
</div>;
};
export interface PathsProps {
pathPoints: TaggedPoint[];
dispatch: Function;
group: TaggedPointGroup;
}
interface PathsState {
pathData: { [key: string]: number };
}
export class Paths extends React.Component<PathsProps, PathsState> {
state: PathsState = { pathData: {} };
generatePathData = (pathPoints: TaggedPoint[]) => {
SORT_TYPES.map((sortType: PointGroupSortType) =>
this.state.pathData[sortType] =
pathDistance(sortGroupBy(sortType, pathPoints)));
this.state.pathData.nn = pathDistance(nn(pathPoints));
};
render() {
if (!this.state.pathData.nn) { this.generatePathData(this.props.pathPoints); }
return <div>
<label>{t("Path lengths by sort type")}</label>
{SORT_TYPES.concat("nn").map(st =>
<PathInfoBar key={st}
sortTypeKey={st}
dispatch={this.props.dispatch}
group={this.props.group}
pathData={this.state.pathData} />)}
</div>;
}
}
interface NNPathProps {
pathPoints: TaggedPoint[];
mapTransformProps: MapTransformProps;
}
export const NNPath = (props: NNPathProps) =>
localStorage.getItem("try_it") == "ok"
? <PointsPathLine
color={Color.blue}
strokeWidth={2}
dash={1}
orderedPoints={nn(props.pathPoints).map(xy)}
mapTransformProps={props.mapTransformProps} />
: <g />;