Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #7517 - asymmetrical cameraForBounds #7518

Merged
merged 5 commits into from
Oct 31, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions debug/7517.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html>

<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='/dist/mapbox-gl.css' />
<style>
body {
margin: 0;
padding: 0;
}

html,
body,
#map {
height: 100%;
}
</style>
</head>

<body>
<div id='map'></div>

<script src='/dist/mapbox-gl-dev.js'></script>
<script src='/debug/access_token_generated.js'></script>
<script>
const data = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-67.13734351262877, 45.137451890638886],
[-66.96466, 44.8097],
[-68.03252, 44.3252],
[-69.06, 43.98],
[-70.11617, 43.68405],
[-70.64573401557249, 43.090083319667144],
[-70.75102474636725, 43.08003225358635],
[-70.79761105007827, 43.21973948828747],
[-70.98176001655037, 43.36789581966826],
[-70.94416541205806, 43.46633942318431],
[-71.08482, 45.3052400000002],
[-70.6600225491012, 45.46022288673396],
[-70.30495378282376, 45.914794623389355],
[-70.00014034695016, 46.69317088478567],
[-69.23708614772835, 47.44777598732787],
[-68.90478084987546, 47.184794623394396],
[-68.23430497910454, 47.35462921812177],
[-67.79035274928509, 47.066248887716995],
[-67.79141211614706, 45.702585354182816],
[-67.13734351262877, 45.137451890638886]
]
]
}
};

const maineBBox = [-71.08482,
43.08003225358635, -66.96466,
47.44777598732787
];

const maineBBoxPolygon = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-71.08482, 43.08003225358635],
[-66.96466, 43.08003225358635],
[-66.96466, 47.44777598732787],
[-71.08482, 47.44777598732787],
[-71.08482, 43.08003225358635]
]
]
}
};

const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [-68.13734351262877, 45.137451890638886],
zoom: 5
});

map.on('load', function() {
map.addLayer({
'id': 'maine',
'type': 'fill',
'source': {
'type': 'geojson',
'data': data
},
'layout': {},
'paint': {
'fill-color': '#088',
'fill-opacity': 0.8
}
});
map.addLayer({
'id': 'maine-bbox',
'type': 'line',
'source': {
'type': 'geojson',
'data': maineBBoxPolygon
},
'layout': {},
'paint': {
'line-color': 'red',
'line-width': 2
}
});

const camera = map.cameraForBounds(maineBBox, {
padding: {
top: 0,
right: 20,
bottom: 100,
left: 20
},
offset: [100, 0]
});

map.easeTo({
center: camera.center,
zoom: camera.zoom,
bearing: camera.bearing
});

});
</script>
</body>

</html>
38 changes: 20 additions & 18 deletions src/ui/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,17 +434,9 @@ class Camera extends Evented {
return;
}

// we separate the passed padding option into two parts, the part that does not affect the map's center
// (lateral and vertical padding), and the part that does (paddingOffset). We add the padding offset
// to the options `offset` object where it can alter the map's center in the subsequent calls to
// `easeTo` and `flyTo`.
const paddingOffset = [(options.padding.left - options.padding.right) / 2, (options.padding.top - options.padding.bottom) / 2],
lateralPadding = Math.min(options.padding.right, options.padding.left),
verticalPadding = Math.min(options.padding.top, options.padding.bottom);
options.offset = [options.offset[0] + paddingOffset[0], options.offset[1] + paddingOffset[1]];

const tr = this.transform;
// we want to calculate the upper right and lower left of the box defined by p0 and p1

// We want to calculate the upper right and lower left of the box defined by p0 and p1
// in a coordinate system rotate to match the destination bearing.
const p0world = tr.project(LngLat.convert(p0));
const p1world = tr.project(LngLat.convert(p1));
Expand All @@ -454,22 +446,32 @@ class Camera extends Evented {
const upperRight = new Point(Math.max(p0rotated.x, p1rotated.x), Math.max(p0rotated.y, p1rotated.y));
const lowerLeft = new Point(Math.min(p0rotated.x, p1rotated.x), Math.min(p0rotated.y, p1rotated.y));

const offset = Point.convert(options.offset),
size = upperRight.sub(lowerLeft),
scaleX = (tr.width - lateralPadding * 2 - Math.abs(offset.x) * 2) / size.x,
scaleY = (tr.height - verticalPadding * 2 - Math.abs(offset.y) * 2) / size.y;
// Calculate zoom: consider the original bbox and padding.
const size = upperRight.sub(lowerLeft);
const scaleX = (tr.width - options.padding.left - options.padding.right) / size.x;
const scaleY = (tr.height - options.padding.top - options.padding.bottom) / size.y;

if (scaleY < 0 || scaleX < 0) {
warnOnce(
'Map cannot fit within canvas with the given bounds, padding, and/or offset.'
);
return;
}
options.center = tr.unproject(p0world.add(p1world).div(2));
options.zoom = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom);
options.bearing = bearing;

return options;
const zoom = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom);

// Calculate center: apply the zoom, the configured offset, as well as offset that exists as a result of padding.
const paddingOffset = [(options.padding.left - options.padding.right) / 2, (options.padding.top - options.padding.bottom) / 2];
const offsetAtInitialZoom = Point.convert([options.offset[0] + paddingOffset[0], options.offset[1] + paddingOffset[1]]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: let's avoid temporary arrays here by using new Point(x, y) instead of Point.convert([x, y]), and maybe declaring separate paddingOffsetX and paddingOffsetY variables since we don't really need it being in an array.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, options.offset is of PointLike type so this code will break if you pass e.g. offset: new Point(10, 10) instead of offset: [10, 10]. So let's bring back the const offset = Point.convert(options.offset) line and use that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great points! It looks like the previous implementation was also accessing array indices directly, so I added a test to make sure PointLike objects work.

const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / tr.zoomScale(zoom));

const center = tr.unproject(p0world.add(p1world).div(2).sub(offsetAtFinalZoom));

return {
center,
zoom,
bearing
};
}

/**
Expand Down
29 changes: 28 additions & 1 deletion test/unit/ui/camera.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1714,11 +1714,38 @@ test('camera', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, duration: 0 });
const transform = camera.cameraForBounds(bb, { padding: {top: 15, right: 15, bottom: 15, left: 15}, duration: 0 });
t.deepEqual(fixedLngLat(transform.center, 4), { lng: -100.5, lat: 34.7171 }, 'correctly calculates coordinates for bounds with padding option as object applied');
t.end();
});

t.test('asymetrical padding', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, duration: 0 });
t.deepEqual(fixedLngLat(transform.center, 4), { lng: -96.5558, lat: 32.0833 }, 'correctly calculates coordinates for bounds with padding option as object applied');
t.end();
});

t.test('offset', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

const transform = camera.cameraForBounds(bb, { offset: [0, 100] });
t.deepEqual(fixedLngLat(transform.center, 4), { lng: -100.5, lat: 44.4717 }, 'correctly calculates coordinates for bounds with padding option as object applied');
t.end();
});

t.test('offset and padding', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, offset: [0, 100] });
t.deepEqual(fixedLngLat(transform.center, 4), { lng: -96.5558, lat: 44.4189 }, 'correctly calculates coordinates for bounds with padding option as object applied');
t.end();
});

t.end();
});

Expand Down