|
1 | 1 | import { SphericalMercator } from '@mapbox/sphericalmercator';
|
2 | 2 | import * as turf from '@turf/turf';
|
3 |
| -import type { Polygon, Feature, FeatureCollection, Position, LineString } from 'geojson'; |
4 |
| - |
5 |
| -type XY = [number, number]; |
6 |
| -type LngLat = [number, number]; |
7 |
| - |
8 |
| -interface mapFitOptions { |
9 |
| - tileSize?: number; |
10 |
| - preferredBearing?: number; |
11 |
| - maxZoom?: number; |
12 |
| - floatZoom?: boolean; |
13 |
| - padding?: mapFitPadding; |
14 |
| -} |
15 |
| - |
16 |
| -interface mapFitPadding { |
17 |
| - left?: number; |
18 |
| - right?: number; |
19 |
| - top?: number; |
20 |
| - bottom?: number; |
21 |
| -} |
22 |
| - |
23 |
| -interface mapFitResult { |
24 |
| - bearing: number; |
25 |
| - zoom: number; |
26 |
| - center: LngLat; |
27 |
| -} |
28 |
| - |
29 |
| -interface rectangleOrientation { |
30 |
| - shortSide: Feature<LineString> | undefined; |
31 |
| - longSide: Feature<LineString> | undefined; |
32 |
| -} |
33 |
| - |
34 |
| -interface boundingOrientation { |
35 |
| - bearing: number | undefined; |
36 |
| - orientation: rectangleOrientation; |
37 |
| - envelope: Feature<Polygon> | undefined; |
38 |
| -} |
| 3 | +import type { Polygon, Feature, FeatureCollection, LineString } from 'geojson'; |
| 4 | +import { findScreenCenter, findScreenBearing, findScreenZoom} from './screen'; |
| 5 | +import { XY, mapFitPadding, mapFitOptions, mapFitResult, rectangleOrientation, boundingOrientation } from './types'; |
39 | 6 |
|
40 | 7 | function mapFitFeatures(
|
41 | 8 | features: FeatureCollection,
|
@@ -84,104 +51,6 @@ function mapFitFeatures(
|
84 | 51 | return { bearing, zoom, center };
|
85 | 52 | }
|
86 | 53 |
|
87 |
| -function findScreenZoom( |
88 |
| - paddedScreenDimensions: XY, |
89 |
| - paddedScreenRatio: number, |
90 |
| - boundingRectangleOrientation: rectangleOrientation, |
91 |
| - maxZoom: number, |
92 |
| - floatZoom: boolean, |
93 |
| - merc: SphericalMercator, |
94 |
| -): number { |
95 |
| - const { shortSide, longSide } = boundingRectangleOrientation; |
96 |
| - const longSideCoords = turf.getCoords(longSide!); |
97 |
| - const shortSideCoords = turf.getCoords(shortSide!); |
98 |
| - |
99 |
| - // We need to determine the ratio required for the zoom level. To do this we are going to approximate the length |
100 |
| - // of the longest and shortest sides of the polygon in pixels (This doesn't account for projection distortion but is |
101 |
| - // a good estimation) |
102 |
| - const longPx: [XY, XY] = [merc.px(longSideCoords[0], maxZoom), merc.px(longSideCoords[1], maxZoom)]; |
103 |
| - const shortPx: [XY, XY] = [merc.px(shortSideCoords[0], maxZoom), merc.px(shortSideCoords[1], maxZoom)]; |
104 |
| - |
105 |
| - // Because these points aren't aligned to the axis, we use the Pythagorean theorem to calculate the distance |
106 |
| - const longPxX = longPx[0][0] - longPx[1][0]; |
107 |
| - const longPxY = longPx[0][1] - longPx[1][1]; |
108 |
| - const shortPxX = shortPx[0][0] - shortPx[1][0]; |
109 |
| - const shortPxY = shortPx[0][1] - shortPx[1][1]; |
110 |
| - const longPxDistance = Math.sqrt(Math.pow(longPxX, 2) + Math.pow(longPxY, 2)); |
111 |
| - const shortPxDistance = Math.sqrt(Math.pow(shortPxX, 2) + Math.pow(shortPxY, 2)); |
112 |
| - |
113 |
| - let xPx = longPxDistance; |
114 |
| - let yPx = shortPxDistance; |
115 |
| - |
116 |
| - // If the screen is taller than it is wide, swap the x and y values |
117 |
| - if (paddedScreenRatio < 1) { |
118 |
| - xPx = shortPxDistance; |
119 |
| - yPx = longPxDistance; |
120 |
| - } |
121 |
| - |
122 |
| - const ratios: XY = [Math.abs(xPx / paddedScreenDimensions[0]), Math.abs(yPx / paddedScreenDimensions[1])]; |
123 |
| - const zoom = Math.min(maxZoom - Math.log(ratios[0]) / Math.log(2), maxZoom - Math.log(ratios[1]) / Math.log(2)); |
124 |
| - return floatZoom ? zoom : Math.floor(zoom); |
125 |
| -} |
126 |
| - |
127 |
| -function findScreenBearing(boundingRectangleBearing: number, preferredBearing: number, screenRatio: number): number { |
128 |
| - let bearing = boundingRectangleBearing; |
129 |
| - // Rotate the bearing by 90 degrees if the screen is wider than it is tall |
130 |
| - if (screenRatio > 1) { |
131 |
| - bearing = bearing + (90 % 360); |
132 |
| - } |
133 |
| - |
134 |
| - // Rotate the bearing 180 degrees if the preferred bearing is on the opposite side of the screen |
135 |
| - if (bearing < preferredBearing - (90 % 360) || bearing > preferredBearing + (90 % 360)) { |
136 |
| - bearing = (bearing + 180) % 360; |
137 |
| - } |
138 |
| - |
139 |
| - return bearing; |
140 |
| -} |
141 |
| - |
142 |
| -function findScreenCenter( |
143 |
| - boundingRectangle: Feature<Polygon>, |
144 |
| - bearing: number, |
145 |
| - zoom: number, |
146 |
| - padding: mapFitPadding, |
147 |
| - merc: SphericalMercator, |
148 |
| -) { |
149 |
| - const { left = 0, right = 0, top = 0, bottom = 0 } = padding; |
150 |
| - |
151 |
| - // Use the bounding rectangle's pixel location to calculate the centre of the |
152 |
| - // map. This allows us to account for mercator projection distortion. |
153 |
| - const coords = turf.getCoords(boundingRectangle); |
154 |
| - const uniqCoords = coords[0].reduce((uniq: Position[], coord: [number, number]) => { |
155 |
| - if (!uniq.find((c) => c[0] === coord[0] && c[1] === coord[1])) { |
156 |
| - uniq.push(coord); |
157 |
| - } |
158 |
| - return uniq; |
159 |
| - }, []); |
160 |
| - |
161 |
| - const sumCoords = uniqCoords.reduce( |
162 |
| - (acc: [number, number], coord: [number, number]) => { |
163 |
| - const [x, y] = merc.px(coord as LngLat, zoom); |
164 |
| - acc[0] = acc[0] + x; |
165 |
| - acc[1] = acc[1] + y; |
166 |
| - return acc; |
167 |
| - }, |
168 |
| - [0, 0], |
169 |
| - ); |
170 |
| - |
171 |
| - const midX = sumCoords[0] / uniqCoords.length; |
172 |
| - const midY = sumCoords[1] / uniqCoords.length; |
173 |
| - |
174 |
| - const xPaddingOffset = right - left; |
175 |
| - const yPaddingOffset = bottom - top; |
176 |
| - |
177 |
| - const bearingRadians = bearing * (Math.PI / 180); |
178 |
| - |
179 |
| - const centerXOffset = xPaddingOffset * Math.cos(bearingRadians) - yPaddingOffset * Math.sin(bearingRadians); |
180 |
| - const centerYOffset = xPaddingOffset * Math.sin(bearingRadians) + yPaddingOffset * Math.cos(bearingRadians); |
181 |
| - |
182 |
| - return merc.ll([midX + centerXOffset, midY + centerYOffset], zoom); |
183 |
| -} |
184 |
| - |
185 | 54 | export function minimumBoundingRectangle(geoJsonInput: turf.AllGeoJSON): {
|
186 | 55 | boundsOrientation: boundingOrientation;
|
187 | 56 | boundingRectangle: Feature<Polygon>;
|
|
0 commit comments