1 | import { LngLat, Point, Map } from 'mapbox-gl';
|
2 | import { Props } from '../projected-layer';
|
3 | import { Anchor, AnchorsOffset } from './types';
|
4 |
|
5 | export interface PointDef {
|
6 | x: number;
|
7 | y: number;
|
8 | }
|
9 |
|
10 | export interface OverlayParams {
|
11 | anchor?: Anchor;
|
12 | offset?: Point;
|
13 | position?: Point;
|
14 | }
|
15 |
|
16 | export const anchors = [
|
17 | 'center',
|
18 | 'top',
|
19 | 'bottom',
|
20 | 'left',
|
21 | 'right',
|
22 | 'top-left',
|
23 | 'top-right',
|
24 | 'bottom-left',
|
25 | 'bottom-right'
|
26 | ] as Anchor[];
|
27 |
|
28 | export const anchorTranslates = {
|
29 | center: 'translate(-50%, -50%)',
|
30 | top: 'translate(-50%, 0)',
|
31 | left: 'translate(0, -50%)',
|
32 | right: 'translate(-100%, -50%)',
|
33 | bottom: 'translate(-50%, -100%)',
|
34 | 'top-left': 'translate(0, 0)',
|
35 | 'top-right': 'translate(-100%, 0)',
|
36 | 'bottom-left': 'translate(0, -100%)',
|
37 | 'bottom-right': 'translate(-100%, -100%)'
|
38 | };
|
39 |
|
40 |
|
41 | const defaultElement = { offsetWidth: 0, offsetHeight: 0 };
|
42 | const defaultPoint = [0, 0];
|
43 |
|
44 | const projectCoordinates = (map: Map, coordinates: [number, number]) =>
|
45 | map.project(LngLat.convert(coordinates));
|
46 |
|
47 | const calculateAnchor = (
|
48 | map: Map,
|
49 | offsets: AnchorsOffset,
|
50 | position: PointDef,
|
51 | { offsetHeight, offsetWidth } = defaultElement
|
52 | ) => {
|
53 | let anchor: string[] = [];
|
54 |
|
55 | if (position.y + offsets.bottom.y - offsetHeight < 0) {
|
56 | anchor = [anchors[1]];
|
57 | } else if (
|
58 | position.y + offsets.top.y + offsetHeight >
|
59 |
|
60 | (map as any).transform.height
|
61 | ) {
|
62 | anchor = [anchors[2]];
|
63 | }
|
64 |
|
65 | if (position.x < offsetWidth / 2) {
|
66 | anchor.push(anchors[3]);
|
67 |
|
68 | } else if (position.x > (map as any).transform.width - offsetWidth / 2) {
|
69 | anchor.push(anchors[4]);
|
70 | }
|
71 |
|
72 | if (anchor.length === 0) {
|
73 | return anchors[2];
|
74 | }
|
75 |
|
76 | return anchor.join('-') as Anchor;
|
77 | };
|
78 |
|
79 | const normalizedOffsets = (
|
80 | offset?: number | Point | AnchorsOffset | [number, number]
|
81 | ): AnchorsOffset => {
|
82 | if (!offset) {
|
83 | return normalizedOffsets(new Point(0, 0));
|
84 | }
|
85 |
|
86 | if (typeof offset === 'number') {
|
87 |
|
88 | const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
|
89 | return {
|
90 | center: new Point(offset, offset),
|
91 | top: new Point(0, offset),
|
92 | bottom: new Point(0, -offset),
|
93 | left: new Point(offset, 0),
|
94 | right: new Point(-offset, 0),
|
95 | 'top-left': new Point(cornerOffset, cornerOffset),
|
96 | 'top-right': new Point(-cornerOffset, cornerOffset),
|
97 | 'bottom-left': new Point(cornerOffset, -cornerOffset),
|
98 | 'bottom-right': new Point(-cornerOffset, -cornerOffset)
|
99 | };
|
100 | }
|
101 |
|
102 | if (offset instanceof Point || Array.isArray(offset)) {
|
103 |
|
104 | return anchors.reduce(
|
105 | (res, anchor) => {
|
106 | res[anchor] = Point.convert(offset);
|
107 | return res;
|
108 | },
|
109 |
|
110 | {} as AnchorsOffset
|
111 | );
|
112 | }
|
113 |
|
114 |
|
115 | return anchors.reduce(
|
116 | (res, anchor) => {
|
117 | res[anchor] = Point.convert(offset[anchor] || defaultPoint);
|
118 | return res;
|
119 | },
|
120 |
|
121 | {} as AnchorsOffset
|
122 | );
|
123 | };
|
124 |
|
125 | export const overlayState = (
|
126 | props: Props,
|
127 | map: Map,
|
128 | container: HTMLElement
|
129 | ) => {
|
130 | const position = projectCoordinates(map, props.coordinates);
|
131 | const offsets = normalizedOffsets(props.offset);
|
132 | const anchor =
|
133 | props.anchor || calculateAnchor(map, offsets, position, container);
|
134 |
|
135 | return {
|
136 | anchor,
|
137 | position,
|
138 | offset: offsets[anchor]
|
139 | };
|
140 | };
|
141 |
|
142 | const moveTranslate = (point: Point) =>
|
143 | point ? `translate(${point.x.toFixed(0)}px, ${point.y.toFixed(0)}px)` : '';
|
144 |
|
145 | export const overlayTransform = ({
|
146 | anchor,
|
147 | position,
|
148 | offset
|
149 | }: OverlayParams) => {
|
150 | const res = [];
|
151 |
|
152 | if (position) {
|
153 | res.push(moveTranslate(position));
|
154 | }
|
155 |
|
156 | if (offset && offset.x !== undefined && offset.y !== undefined) {
|
157 | res.push(moveTranslate(offset));
|
158 | }
|
159 |
|
160 | if (anchor) {
|
161 | res.push(anchorTranslates[anchor]);
|
162 | }
|
163 |
|
164 | return res;
|
165 | };
|