Skip to content

Commit 1746673

Browse files
committed
Refine map center calculation to account for mercator projection distortion
1 parent da738a0 commit 1746673

File tree

2 files changed

+29
-25
lines changed

2 files changed

+29
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "geojson-map-fit-mercator",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"description": "Finds the optimal bearing, zoom and center point for fitting a set of GeoJSON features in a Mapbox GL or LibreMap Mercator map.",
55
"main": "dist/index.cjs",
66
"types": "types/index.d.ts",

src/index.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ function findScreenBearing(boundingRectangleBearing: number, preferredBearing: n
128128
let bearing = boundingRectangleBearing;
129129
// Rotate the bearing by 90 degrees if the screen is wider than it is tall
130130
if (screenRatio > 1) {
131-
bearing = bearing + 90 % 360;
131+
bearing = bearing + (90 % 360);
132132
}
133133

134134
// 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) {
135+
if (bearing < preferredBearing - (90 % 360) || bearing > preferredBearing + (90 % 360)) {
136136
bearing = (bearing + 180) % 360;
137137
}
138138

@@ -147,31 +147,39 @@ function findScreenCenter(
147147
merc: SphericalMercator,
148148
) {
149149
const { left = 0, right = 0, top = 0, bottom = 0 } = padding;
150-
const centerPoint = turf.center(boundingRectangle);
151-
if (!centerPoint) {
152-
throw new Error('Unable to calculate centre of surrounding rectangle');
153-
}
154-
const center = turf.getCoord(centerPoint)!;
155-
if (!isLngLat(center)) {
156-
throw new Error('Unable to calculate centre of surrounding rectangle');
157-
}
158150

159-
const centerPx = merc.px(center, zoom);
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;
160173

161-
// Calculate the offset required to center the polygon in the viewport
162174
const xPaddingOffset = right - left;
163175
const yPaddingOffset = bottom - top;
164176

165-
if (xPaddingOffset != 0 || yPaddingOffset != 0) {
166-
const bearingRadians = bearing * (Math.PI / 180);
177+
const bearingRadians = bearing * (Math.PI / 180);
167178

168-
const centerXOffset = xPaddingOffset * Math.cos(bearingRadians) - yPaddingOffset * Math.sin(bearingRadians);
169-
const centerYOffset = xPaddingOffset * Math.sin(bearingRadians) + yPaddingOffset * Math.cos(bearingRadians);
179+
const centerXOffset = xPaddingOffset * Math.cos(bearingRadians) - yPaddingOffset * Math.sin(bearingRadians);
180+
const centerYOffset = xPaddingOffset * Math.sin(bearingRadians) + yPaddingOffset * Math.cos(bearingRadians);
170181

171-
return merc.ll([centerPx[0] + centerXOffset, centerPx[1] + centerYOffset], zoom);
172-
} else {
173-
return merc.ll(centerPx, zoom);
174-
}
182+
return merc.ll([midX + centerXOffset, midY + centerYOffset], zoom);
175183
}
176184

177185
export function minimumBoundingRectangle(geoJsonInput: turf.AllGeoJSON): {
@@ -253,8 +261,4 @@ function findRectangleOrientation(rectangle: Feature<Polygon>): rectangleOrienta
253261
);
254262
}
255263

256-
function isLngLat(lonLat: Position): lonLat is LngLat {
257-
return lonLat.length === 2 && lonLat.every((coord) => typeof coord === 'number');
258-
}
259-
260264
export { mapFitFeatures };

0 commit comments

Comments
 (0)