Explorar o código

fix: 制作4dmap的定制

bill hai 1 ano
pai
achega
bc8876114a

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 17 - 0
src/app/4dmap/compass.svg


+ 24 - 0
src/app/4dmap/icon.ts

@@ -0,0 +1,24 @@
+import {
+  analysisSvg,
+  getAbsoluteTransform,
+  injectPoiType,
+  pathsToActShape,
+} from "../../board";
+import compassSVGString from "./compass.svg?raw";
+
+injectPoiType("compass", (_, tree) => {
+  const svgAct = pathsToActShape({
+    ...analysisSvg(compassSVGString),
+    fixed: true,
+  });
+
+  return {
+    ...svgAct,
+    setData(data) {
+      const tf = getAbsoluteTransform(svgAct.shape).copy().invert();
+      const width = tree.container.stage.width();
+      const position = tf.point({ x: width - 60, y: 120 });
+      svgAct.setData({ ...data, ...position });
+    },
+  };
+});

+ 93 - 3
src/app/4dmap/index.ts

@@ -4,10 +4,14 @@ import {
   BoundQueryPlugin,
   setGenerateStartId,
   inRevise,
+  Poi,
+  PoiAttrib,
 } from "../../board";
 import { PolygonsAttrib } from "./type";
 import { Map } from "ol";
 import { boundingExtent } from "ol/extent";
+import "./icon";
+import { Scale, ScaleAttrib } from "../../board/packages/scale/scale";
 
 const boundJoinMap = (
   boundQuery: BoundQueryPlugin,
@@ -28,6 +32,8 @@ const boundJoinMap = (
       size: map.getSize(),
       padding: [0, 0, 0, 0], // 根据需要调整边距
     });
+
+    // console.log(`每个像素代表的米数: ${metersPerPixel}`);
   };
 
   const setBoardBound = () => {
@@ -49,6 +55,7 @@ const boundJoinMap = (
     mapView = bindMap.getView();
     mapView.addEventListener("change:center", setBoardBound);
     mapView.addEventListener("change:resolution", setBoardBound);
+
     map.addEventListener("moveend", setBoardBound);
     map = bindMap;
     setBoardBound();
@@ -78,8 +85,22 @@ const boundJoinMap = (
   };
 };
 
+function captureMap(canvas: HTMLCanvasElement, map: Map, pixelRatio) {
+  // 创建一个临时canvas用于绘制截图
+  const size = map.getSize();
+  canvas.width = size[0] * pixelRatio;
+  canvas.height = size[1] * pixelRatio;
+  canvas.style.width = size[0] + "px";
+  canvas.style.height = size[1] + "px";
+
+  const mapContext = canvas.getContext("2d");
+  const mapCanvas = map.getViewport().querySelector("canvas")!;
+  mapContext.drawImage(mapCanvas, 0, 0, canvas.width, canvas.height);
+  return canvas.toDataURL();
+}
+
 const initBoard = register(
-  { polygons: Polygons },
+  { polygons: Polygons, compass: Poi, scale: Scale },
   { bound: new BoundQueryPlugin() }
 );
 setGenerateStartId(5000000);
@@ -90,10 +111,24 @@ export const createBoard = (
     map?: Map;
   } = {}
 ) => {
+  const compass: PoiAttrib = {
+    id: "0",
+    x: 0,
+    y: 0,
+    type: "compass",
+  };
+  const scale: ScaleAttrib = {
+    id: "0",
+    left: 20,
+    bottom: 20,
+    minWidth: 100,
+    maxWidth: 150,
+  };
+
   const data = props.store || [
     { id: "0", points: [], polygons: [], lines: [] },
   ];
-  const board = initBoard(props.dom, { polygons: data }, false);
+  const board = initBoard(props.dom, { polygons: data, compass, scale }, false);
   const mapJoin = boundJoinMap(board.bound, props.dom, props.map);
 
   const setProps = (props: { dom?: HTMLDivElement; map?: Map } = {}) => {
@@ -106,15 +141,31 @@ export const createBoard = (
     }
   };
   setProps(props);
+  let captureCanvas: HTMLCanvasElement;
+
+  const scaleEntity = board.tree.entrys.scale[0] as Scale;
+  scaleEntity.allowable = 0.00000000001;
+  scaleEntity.getScaleUnit = (data: number[]) => {
+    if (!props.map) return;
+    const mapView = props.map.getView();
+    const projection = mapView.getProjection();
+    const mpu = projection.getMetersPerUnit();
+    const metersPerPixel = data[0] * mpu;
+    return {
+      value: metersPerPixel,
+      unit: " m",
+    };
+  };
 
   return {
     raw: board,
+    scale: scaleEntity,
     setProps,
     getData() {
       return board.getData().polygons[0];
     },
     setData(polygon: PolygonsAttrib & { id: string }) {
-      board.setData({ polygons: [polygon] });
+      board.setData({ polygons: [polygon], compass, scale });
     },
     destory() {
       mapJoin.destory();
@@ -126,6 +177,45 @@ export const createBoard = (
     getPixelFromCoordinate(point: number[]) {
       return board.tree.getPixelFromStage(point);
     },
+    async toDataURL(pixelRatio = 1) {
+      const boardDataURL = board.tree.stage.toDataURL({ pixelRatio });
+      if (!props.map) {
+        return boardDataURL;
+      }
+
+      const size = [board.tree.stage.width(), board.tree.stage.height()];
+      if (!captureCanvas) {
+        captureCanvas = document.createElement("canvas");
+      }
+
+      captureCanvas.width = size[0] * pixelRatio;
+      captureCanvas.height = size[1] * pixelRatio;
+      captureCanvas.style.width = size[0] + "px";
+      captureCanvas.style.height = size[1] + "px";
+
+      const captureCtx = captureCanvas.getContext("2d");
+      captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
+
+      captureCtx.save();
+      captureMap(captureCanvas, props.map, pixelRatio);
+      captureCtx.restore();
+      return new Promise<string>((resolve, reject) => {
+        const image = new Image();
+        image.src = boardDataURL;
+        image.onload = () => {
+          captureCtx.drawImage(
+            image,
+            0,
+            0,
+            captureCanvas.width,
+            captureCanvas.height
+          );
+          const dataURL = captureCanvas.toDataURL("image/png");
+          resolve(dataURL);
+        };
+        image.onerror = reject;
+      });
+    },
   };
 };
 

+ 0 - 1
src/app/4dmap/path.ts

@@ -49,7 +49,6 @@ export class PoPath extends WholeLinePolygon<WholeLinePolygonAttrib, Group> {
 
     const polygons = this.parent as unknown as Polygons;
     this.bus.on("shapeStatusChange", ({ current, type }) => {
-      console.log(current, type);
       if (polygons.status.editPolygonId) return;
       if (current === "active") {
         type === "click" &&

+ 2 - 2
src/app/4dmap/point.ts

@@ -171,8 +171,8 @@ export class PoPoint extends WholeLinePoint<PolygonsPointAttrib, Group> {
 
   protected initReactive() {
     const polygons = this.parent as unknown as Polygons;
-    this.bus.on("shapeStatusChange", ({ current }) => {
-      if (current === "active") {
+    this.bus.on("shapeStatusChange", ({ current, type }) => {
+      if (current === "active" && type === "click") {
         polygons.bus.emit("clickPoint", this.attrib);
       }
     });

+ 16 - 5
src/board/env.ts

@@ -1,8 +1,19 @@
 export let DEV = false;
 
+const ua = navigator.userAgent;
+
 export const changeEnv = (dev: boolean) => (DEV = dev);
-export const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
-export const hasTouchEvents =
-  "ontouchstart" in window ||
-  navigator.maxTouchPoints > 0 ||
-  (navigator as any).msMaxTouchPoints > 0;
+export const isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
+
+let isAndroid = /(?:Android)/.test(ua);
+let isFireFox = /(?:Firefox)/.test(ua);
+export const isTablet =
+  /(?:iPad|PlayBook|Tablet)/.test(ua) ||
+  (isAndroid && !/(?:Mobile)/.test(ua)) ||
+  (isFireFox && /(?:Tablet)/.test(ua)) ||
+  (isMobile &&
+    (window.innerWidth < window.innerHeight
+      ? window.innerWidth
+      : window.innerHeight) > 600);
+
+export const hasTouchEvents = isMobile || isTablet;

+ 14 - 37
src/board/packages/container.ts

@@ -11,7 +11,7 @@ import { Group } from "konva/lib/Group";
 import { getAbsoluteTransform } from "../shared";
 import { Euler, MathUtils, Matrix4, Quaternion, Vector3 } from "three";
 import { constantFactory } from "./view-constant";
-import { entityTreeExecute } from "../helper/entity";
+import { Transform } from "konva/lib/Util";
 
 export type ContainerProps<
   T extends string = string,
@@ -26,6 +26,14 @@ export type ContainerProps<
   dom: HTMLDivElement;
 } & Omit<EntityProps<Attrib & { data: DATA }>, "attrib">;
 
+export type ContainerEvent = EntityEvent & {
+  active: Entity;
+  dataChange: void;
+  dataChangeBefore: void;
+  viewChange: { mat: Transform; size: number[] };
+  dataChangeAfter: void;
+};
+
 export class Container<
   T extends string = string,
   R extends Attrib = Attrib,
@@ -36,16 +44,7 @@ export class Container<
   ENTRYS extends { [key in T]: InstanceType<TYPES[key]>[] } = {
     [key in T]: InstanceType<TYPES[key]>[];
   }
-> extends Entity<
-  Attrib & { data: DATA },
-  Layer,
-  EntityEvent & {
-    active: Entity;
-    dataChange: void;
-    dataChangeBefore: void;
-    dataChangeAfter: void;
-  }
-> {
+> extends Entity<Attrib & { data: DATA }, Layer, ContainerEvent> {
   tempLayer: Layer;
   config: ContainerProps<T, R, S, C, TYPES, DATA>;
   entrys = {} as ENTRYS;
@@ -96,7 +95,6 @@ export class Container<
 
   initIncFactory() {
     const types = this.config.types as any;
-    let active: Entity | null = null;
     for (const key in types) {
       this.incFactorys[key] = incEntitysFactoryGenerate(
         types[key],
@@ -113,31 +111,6 @@ export class Container<
               this.entrys[key].splice(ndx, 1);
             }
           });
-        },
-        (instance) => {
-          // entityTreeExecute(instance, (entity) => {
-          //   if (!(entity as any).actShape?.active || entity.activeDestory)
-          //     return;
-          //   entity.enableActive((actived) => {
-          //     if (actived) {
-          //       if (active !== entity) {
-          //         this.bus.emit("active", entity);
-          //         active = entity;
-          //       }
-          //     } else {
-          //       if (active === entity) {
-          //         this.bus.emit("active", null);
-          //         active = null;
-          //       }
-          //     }
-          //   });
-          //   entity.bus.on("destroyed", () => {
-          //     if (active === entity) {
-          //       this.bus.emit("active", null);
-          //       active = null;
-          //     }
-          //   });
-          // });
         }
       );
     }
@@ -243,5 +216,9 @@ export class Container<
     this.redraw();
 
     this.constant.updateConstantScale(Math.min(scale.x, scale.y));
+    this.bus.emit("viewChange", {
+      mat: getAbsoluteTransform(this.shape, true),
+      size: [this.stage.width(), this.stage.height()],
+    });
   }
 }

+ 1 - 0
src/board/packages/entity.ts

@@ -69,6 +69,7 @@ export abstract class Entity<
   E extends EntityEvent = EntityEvent
 > {
   props: EntityProps<T>;
+
   bus = mitt<E>();
   container: Container<
     string,

+ 1 - 0
src/board/packages/poi/poi.ts

@@ -5,6 +5,7 @@ import { defaultPoiShapeFactory, poiTypes } from "./types";
 type PoiData = {
   x: number;
   y: number;
+  rotate?: number;
   type: string;
 };
 export type PoiAttrib = Attrib & PoiData;

+ 7 - 4
src/board/packages/poi/types.ts

@@ -1,7 +1,7 @@
 import { Circle } from "konva/lib/shapes/Circle";
 import { CustomizeShapeFactory, ShapeType } from "../../type";
 import { getRealAbsoluteSize } from "../../shared";
-import { PoiAttrib } from "./poi";
+import { Poi, PoiAttrib } from "./poi";
 
 export const defaultPoiShapeFactory = () => {
   const p = new Circle({
@@ -17,9 +17,12 @@ export const defaultPoiShapeFactory = () => {
     getSize: () => [3, 3],
     shape: p,
     common() {},
-    setData(data: { x: number; y: number }) {
+    setData(data: { x: number; y: number; rotate?: number }) {
       pub();
       p.position(data);
+      if (data.rotate) {
+        p.rotate(data.rotate);
+      }
     },
   };
 };
@@ -34,13 +37,13 @@ export const poiTypes: {
   >;
 } = {};
 
-export const injectPoiType = (
+export const injectPoiType = <T = Poi>(
   type: string,
   factory: CustomizeShapeFactory<
     PoiAttrib,
     { x: number; y: number },
     ShapeType,
-    any,
+    T,
     { getSize: () => number[] }
   >
 ) => {

+ 183 - 0
src/board/packages/scale/scale.ts

@@ -0,0 +1,183 @@
+import { Transform } from "konva/lib/Util";
+import { Entity, EntityProps } from "../entity";
+import { getAbsoluteTransform, mergeFuns, round } from "../../shared";
+import { ContainerEvent } from "../container";
+import { Group } from "konva/lib/Group";
+import { Line } from "konva/lib/shapes/Line";
+import { Text } from "konva/lib/shapes/Text";
+
+export type ScaleAttrib = {
+  id: string;
+  left?: number;
+  top?: number;
+  right?: number;
+  bottom?: number;
+  minWidth: number;
+  maxWidth: number;
+};
+
+export class Scale extends Entity<ScaleAttrib, Group> {
+  private mat: Transform;
+  pixelScale = { value: 0, unit: "" };
+  allowable = 0.001;
+  private fontSize = 12;
+  private padding = 4;
+  get height() {
+    return this.fontSize + this.padding;
+  }
+
+  constructor(props: EntityProps<ScaleAttrib>) {
+    props.name = props.name || "pixel-scale";
+    props.zIndex = 1000 || props.zIndex;
+    if (!props.attrib.left && !props.attrib.right) {
+      props.attrib.left = 10;
+    }
+    if (!props.attrib.top && !props.attrib.bottom) {
+      props.attrib.bottom = 10;
+    }
+    super(props);
+  }
+
+  getScaleUnit = (pixelScale: number[]) => {
+    const scale = Math.max(...pixelScale);
+    // const value = round(scale, 3);
+    return {
+      value: scale * 10000,
+      unit: "",
+    };
+  };
+
+  initShape() {
+    const group = new Group();
+    group.add(
+      new Line({
+        name: "scale-line",
+        points: [
+          0,
+          this.height / 2,
+          0,
+          this.height,
+          100,
+          this.height,
+          100,
+          this.height / 2,
+        ],
+        strokeScaleEnabled: false,
+        strokeWidth: 1.5,
+        stroke: "#fff",
+      }),
+      new Text({
+        name: "scale-text",
+        text: ``,
+        fontFamily: "Calibri",
+        x: 0,
+        y: 0,
+        align: "center",
+        fontSize: this.fontSize,
+        fill: "#fff",
+      })
+    );
+
+    return group;
+  }
+
+  setColor(color: string) {
+    this.shape.findOne<Line>(".scale-line").stroke(color);
+    this.shape.findOne<Text>(".scale-text").fill(color);
+  }
+
+  diffRedraw() {
+    if (!this.mat || !this.pixelScale || isNaN(this.pixelScale.value)) return;
+
+    let minDeviation = 1;
+    let width;
+    let realWidth;
+    for (let w = this.attrib.minWidth; w <= this.attrib.maxWidth; w++) {
+      const rw = w * this.pixelScale.value;
+      const deviation = round(rw, 10) - rw;
+      if (deviation < this.allowable) {
+        width = w;
+        realWidth = rw;
+        break;
+      } else if (deviation < minDeviation) {
+        width = w;
+        realWidth = rw;
+        minDeviation = deviation;
+      }
+    }
+    if (realWidth < 10) {
+      realWidth = round(realWidth, 2);
+    } else if (realWidth < 100) {
+      realWidth = round(realWidth, 1);
+    } else {
+      realWidth = round(realWidth, 0);
+    }
+
+    if (width === 0 || realWidth === 0) return;
+
+    const line = this.shape.findOne<Line>(".scale-line");
+    const text = this.shape.findOne<Text>(".scale-text");
+
+    const points = line.points();
+    points[4] = points[6] = width;
+
+    text.width(width);
+    text.text(realWidth + this.pixelScale.unit);
+
+    const stage = this.container.stage;
+    let x = this.attrib.left;
+    let y = this.attrib.top;
+    if (typeof x !== "number" && typeof this.attrib.right === "number") {
+      x = stage.width() - width - this.attrib.right;
+    }
+    if (typeof y !== "number" && typeof this.attrib.bottom === "number") {
+      y = stage.height() - this.height - this.attrib.bottom;
+    }
+
+    const end = this.mat.point({ x: 1, y: 1 });
+    const start = this.mat.point({ x: 0, y: 0 });
+    const scale = {
+      x: end.x - start.x,
+      y: end.y - start.y,
+    };
+
+    this.shape.position(this.mat.point({ x, y })).scale(scale);
+    this.shape.draw();
+  }
+
+  setPixelScale(mat: Transform, size: number[]) {
+    mat = mat.copy().invert();
+    const lt = mat.point({ x: 0, y: 0 });
+    const rb = mat.point({ x: size[0], y: size[1] });
+    this.mat = mat;
+    this.pixelScale = this.getScaleUnit([
+      (rb.x - lt.x) / size[0],
+      (rb.y - lt.y) / size[1],
+    ]);
+  }
+
+  protected initReactive() {
+    const viewChangeHandler = ({ mat, size }: ContainerEvent["viewChange"]) => {
+      this.setPixelScale(mat, size);
+    };
+    this.container.bus.on("viewChange", viewChangeHandler);
+    return mergeFuns(super.initReactive(), () =>
+      this.container.bus.off("viewChange", viewChangeHandler)
+    );
+  }
+
+  mounted() {
+    super.mounted();
+    const stage = this.container.stage;
+    this.setPixelScale(getAbsoluteTransform(stage, true), [
+      stage.width(),
+      stage.height(),
+    ]);
+    console.log(stage);
+    stage.on("click", (ev) => {
+      console.log(
+        this.container.getRealFromStage([ev.evt.offsetX, ev.evt.offsetY])
+      );
+    });
+  }
+}

+ 4 - 0
src/board/plugins/bound-plugin.ts

@@ -169,6 +169,10 @@ export class BoundQueryPlugin {
     stage.scale({ x: scaleX, y: scaleY });
     stage.position({ x: offsetX, y: offsetY });
     this.tree.redraw();
+    this.tree.bus.emit("viewChange", {
+      mat: stage.getTransform(),
+      size: [stage.width(), stage.height()],
+    });
     (this.tree.shape as Layer).batchDraw();
   }
 }

+ 63 - 12
src/board/shared/act.ts

@@ -4,8 +4,41 @@ import { getRealAbsoluteSize } from "./shape-helper";
 import { Group } from "konva/lib/Group";
 import { KonvaEventObject } from "konva/lib/Node";
 
+export type SVGPaths = (
+  | string
+  | { fill?: string; stroke?: string; strokeWidth?: number; data: string }
+)[];
+
+const temp = document.createElement("div");
+export const analysisSvg = (
+  svgString: string
+): Pick<PathsToActShapeProps, "paths" | "size"> => {
+  temp.innerHTML = svgString;
+  const svg = temp.querySelector("svg");
+  const size = [svg.width.baseVal.value, svg.height.baseVal.value];
+
+  const paths = Array.from(svg.querySelectorAll("path"));
+  const pathDatas = paths.map((path) => {
+    const fill = path.getAttribute("fill");
+    const data = path.getAttribute("d");
+    const stroke = path.getAttribute("stroke");
+    const strokeWidth = path.getAttribute("stroke-width");
+    return {
+      fill,
+      data,
+      stroke,
+      strokeWidth: strokeWidth && Number(strokeWidth),
+    };
+  });
+
+  return {
+    paths: pathDatas,
+    size,
+  };
+};
+
 export type PathsToActShapeProps = {
-  paths: string[];
+  paths: SVGPaths;
   size: number[];
   realWidth?: number;
   offset?: number[];
@@ -21,19 +54,38 @@ export const pathsToActShape = (props: PathsToActShapeProps, test = false) => {
   const offset = (props.offset || [0, 0]).map((v) => v * scale);
   const strokeWidth = props.strokeWidth ? props.strokeWidth * scale : 1;
   const strokeColor = props.strokeColor || "#000";
-  const paths = props.paths.map(
-    (data, ndx) =>
+  const pathAttribs = props.paths.map((path) => {
+    if (typeof path === "string") {
+      return {
+        strokeWidth,
+        stroke: strokeColor,
+        fill: strokeColor,
+        data: path,
+      };
+    } else {
+      return path;
+    }
+  });
+
+  const paths = pathAttribs.map(
+    (path, ndx) =>
       new Path({
-        data,
+        data: path.data,
         id: `path-${ndx}`,
         name: `path`,
         strokeScaleEnabled: !!props.fixed,
-        stroke: strokeColor,
-        fill: strokeColor,
         scale: { x: scale, y: scale },
-        strokeWidth,
       })
   );
+  const common = () => {
+    paths.forEach((path, ndx) => {
+      const attrib = pathAttribs[ndx];
+      attrib.fill && path.fill(attrib.fill);
+      attrib.stroke && path.stroke(attrib.stroke);
+      attrib.strokeWidth && path.strokeWidth(attrib.strokeWidth);
+    });
+  };
+
   const rect = new Rect({
     x: offset[0],
     y: offset[1],
@@ -69,12 +121,11 @@ export const pathsToActShape = (props: PathsToActShapeProps, test = false) => {
     setData(data) {
       group.position(data);
       props.fixed && setStyle();
+      if (data.rotate) {
+        group.rotation(data.rotate);
+      }
     },
-    common() {
-      paths.forEach((path) => {
-        path.fill(strokeColor);
-      });
-    },
+    common,
   };
 };
 

+ 5 - 2
src/board/shared/entity-utils.ts

@@ -7,7 +7,7 @@ import { getTouchOffset } from "./act";
 import { createLineByDire, getLineProjection } from "./math";
 import { disableMouse, enableMouse } from "./shape-mose";
 import { generateId, getChangeAllPoart } from "./util";
-import { computed, ref, watch, watchEffect } from "vue";
+import { ref, watch, watchEffect } from "vue";
 
 const getExtendsProps = (parent: Entity<any, any>) => {
   return parent
@@ -207,7 +207,10 @@ export const addEntityAttrib = (
   };
 };
 
-const updateEntityStatus = (entity: Entity, status: ShapeStylesStatusKey) => {
+export const updateEntityStatus = (
+  entity: Entity,
+  status: ShapeStylesStatusKey
+) => {
   entity.bus.emit("statusChange", { [status]: true });
 };
 

+ 0 - 1
src/board/shared/shape-mose.ts

@@ -85,7 +85,6 @@ export const openShapeMouseStyles = <T extends Shape | Group>(
     for (const l of ls) {
       const showStatus = { ...l };
       while (api && !(api in styles)) {
-        console.log(shape.id(), api, styles);
         showStatus[api] = false;
         api = getActiveKey(showStatus);
       }