From 3f2e2ba85666b4688900bfd269567474d07d6868 Mon Sep 17 00:00:00 2001
From: muerwre <gotham48@gmail.com>
Date: Thu, 7 Mar 2019 16:37:40 +0700
Subject: [PATCH] drawing arrows

---
 src/components/dialogs/MapListDialog.tsx |  1 -
 src/index.tsx                            |  8 ++--
 src/redux/user/sagas.ts                  |  2 +
 src/sprites/arrow.svg                    |  5 +++
 src/utils/arrow.ts                       |  5 ++-
 src/utils/geom.ts                        | 11 +++++-
 src/utils/renderer.ts                    | 47 ++++++++++++++++++++----
 7 files changed, 65 insertions(+), 14 deletions(-)
 create mode 100644 src/sprites/arrow.svg

diff --git a/src/components/dialogs/MapListDialog.tsx b/src/components/dialogs/MapListDialog.tsx
index 6c4ae16..0924e77 100644
--- a/src/components/dialogs/MapListDialog.tsx
+++ b/src/components/dialogs/MapListDialog.tsx
@@ -76,7 +76,6 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
   });
 
   stopEditing = (): void => {
-    console.log('stop it!');
     this.setState({ editor_target: null });
   };
 
diff --git a/src/index.tsx b/src/index.tsx
index 184a75a..0a5e939 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -14,10 +14,6 @@
   todo polyline editing only in manual mode (or by click)
   todo selecting logo on crop
 
-  done public maps
-    done editing map on map list
-    done setting map public on map list
-
   todo network operations notify
     done delayed notify (delay(2000).then(showLoadingMsg))
     todo network error notifications
@@ -28,6 +24,10 @@
 
   ## DONE
 
+  done public maps
+    done editing map on map list
+    done setting map public on map list
+
   done routing spinner
   done maybe: stickers clusterization?
   done moving out the screen makes stickers editable again
diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts
index 600cf9b..12fbb06 100644
--- a/src/redux/user/sagas.ts
+++ b/src/redux/user/sagas.ts
@@ -41,6 +41,7 @@ import { MODES } from '$constants/modes';
 import { DEFAULT_USER } from '$constants/auth';
 import { TIPS } from '$constants/tips';
 import {
+  composeArrows,
   composeImages,
   composePoly, composeStickers, downloadCanvas,
   fetchImages,
@@ -362,6 +363,7 @@ function* getRenderData() {
 
   yield composeImages({ geometry, images, ctx });
   yield composePoly({ points, ctx });
+  yield composeArrows({ points, ctx });
   yield composeStickers({ stickers, ctx });
 
   yield put(setRenderer({ info: 'Готово', progress: 1 }));
diff --git a/src/sprites/arrow.svg b/src/sprites/arrow.svg
new file mode 100644
index 0000000..ea28122
--- /dev/null
+++ b/src/sprites/arrow.svg
@@ -0,0 +1,5 @@
+<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg">
+    <g id="path-arrow" transform="scale(2) translate(5 -2)">
+        <path d="m 2.625,3.375 h 7.5 L 10.28125,1.609375 13.5,4.25 10.484375,6.921875 10.171875,5.15625 2.625,5.125 Z" fill="#ff3344" fillRule="evenodd" />
+    </g>
+</svg>
diff --git a/src/utils/arrow.ts b/src/utils/arrow.ts
index 7d7fa5a..e5439d8 100644
--- a/src/utils/arrow.ts
+++ b/src/utils/arrow.ts
@@ -1,5 +1,8 @@
 import { divIcon, LatLngLiteral, Marker, marker, DivIcon } from "leaflet";
 
+const arrow_image = require('$sprites/arrow.svg');
+
+// <use xlink:href="#path-arrow" transform="scale(2) translate(5 -2)"/>
 export const createArrow = (latlng: LatLngLiteral, angle: number): Marker => marker(latlng, {
   draggable: false,
   interactive: false,
@@ -7,7 +10,7 @@ export const createArrow = (latlng: LatLngLiteral, angle: number): Marker => mar
     html: `
       <div class="leaflet-arrow" style="transform: rotate(${angle}deg);">
         <svg width="48" height="48" preserveAspectRatio="xMidYMid">        
-          <use xlink:href="#path-arrow" transform="scale(2) translate(5 -2)"/>
+          <image xlink:href="${arrow_image}" x="0" y="0" width="48" height="48"/>
         </svg>      
       </div>
     `,
diff --git a/src/utils/geom.ts b/src/utils/geom.ts
index 11db158..7c21275 100644
--- a/src/utils/geom.ts
+++ b/src/utils/geom.ts
@@ -1,4 +1,4 @@
-import { LatLng, LatLngLiteral, Point, PointExpression } from "leaflet";
+import { LatLng, LatLngLiteral, point, Point, PointExpression } from "leaflet";
 
 interface ILatLng {
   lat: number,
@@ -10,9 +10,18 @@ export const middleCoord = (l1: ILatLng, l2: ILatLng): ILatLng => ({
   lng: (l2.lng + ((l1.lng - l2.lng) / 2))
 });
 
+export const middleCoordPx = (p1: Point, p2: Point): Point => point({
+  x: (p1.x + ((p2.x - p1.x) / 2)),
+  y: (p1.y + ((p2.y - p1.y) / 2))
+});
+
 export const deg2rad = (deg: number): number => ((deg * Math.PI) / 180);
 export const rad2deg = (rad: number): number => ((rad / Math.PI) * 180);
 
+export const findDistancePx = (p1: Point, p2: Point): number => {
+  return Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2));
+};
+
 export const findDistance = (t1: number, n1: number, t2: number, n2: number): number => {
   // convert coordinates to radians
   const lat1 = deg2rad(t1);
diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts
index 9cdbd5c..4a2e1ee 100644
--- a/src/utils/renderer.ts
+++ b/src/utils/renderer.ts
@@ -6,11 +6,8 @@ import { STICKERS } from '$constants/stickers';
 import { ILatLng } from "$modules/Stickers";
 import { IStickerDump } from "$modules/Sticker";
 import { IRootState } from "$redux/user/reducer";
-
-export interface IMapPoint {
-  x: number,
-  y: number,
-}
+import { angleBetweenPoints, angleBetweenPointsRad, findDistancePx, middleCoordPx } from "$utils/geom";
+import { Point } from "leaflet";
 
 export interface ITilePlacement {
   minX: number,
@@ -86,7 +83,7 @@ export const getTilePlacement = (): ITilePlacement => {
   };
 };
 
-export const getPolyPlacement = (): IMapPoint[] => (
+export const getPolyPlacement = (): Point[] => (
   (!editor.poly.poly || !editor.poly.poly.getLatLngs() || editor.poly.poly.getLatLngs().length <= 0)
     ? []
     : editor.poly.poly.getLatLngs().map((latlng) => ({ ...editor.map.map.latLngToContainerPoint(latlng) }))
@@ -148,7 +145,7 @@ export const composeImages = (
   });
 };
 
-export const composePoly = ({ points, ctx }: { points: IMapPoint[], ctx: CanvasRenderingContext2D }): void => {
+export const composePoly = ({ points, ctx }: { points: Point[], ctx: CanvasRenderingContext2D }): void => {
   if (editor.poly.isEmpty) return;
 
   let minX = points[0].x;
@@ -183,6 +180,42 @@ export const composePoly = ({ points, ctx }: { points: IMapPoint[], ctx: CanvasR
   ctx.closePath();
 };
 
+export const composeArrows = async ({ points, ctx }: { points: Point[], ctx: CanvasRenderingContext2D }): Promise<boolean[]> => {
+  const image = await imageFetcher(require('$sprites/arrow.svg'));
+
+  const distances = points.map((point, i) => (
+    (points[i + 1] && findDistancePx(points[i], points[i + 1])) || 0
+  ));
+
+  // we want to annotate at least 5 arrows
+  const min_arrows = (distances.length >= 5 ? 5 : distances.length - 1);
+  const min_distance = distances.sort((a, b) => (b - a))[min_arrows];
+
+  return points.map((point, i) => {
+    if (!points[i + 1]) return false;
+
+    const distance = findDistancePx(points[i], points[i + 1]);
+    const angle = angleBetweenPointsRad(points[i], points[i + 1]);
+
+    if (distance < min_distance && distance < 100) return false;
+
+    const middle = middleCoordPx(points[i], points[i + 1]);
+
+    ctx.save();
+    ctx.translate(middle.x, middle.y);
+    ctx.rotate((Math.PI * 0.5) - angle);
+    ctx.translate(-middle.x, -middle.y);
+
+    ctx.moveTo(middle.x, middle.y);
+
+    ctx.drawImage(image, middle.x - 24, middle.y - 24, 48, 48);
+
+    ctx.restore();
+
+    return true;
+  });
+};
+
 const measureText = (
   ctx: CanvasRenderingContext2D,
   text: string,