Fit to coordinates

Whoever determined the boundaries of the District of Columbia did not have fit-to-bounds implementations in mind. With this change, shapes that are not unrotated rectangles fit much, much better.
This commit is contained in:
Minh Nguyễn
2015-06-26 14:27:44 -07:00
parent 7b22caddd0
commit ac2ad993a5
5 changed files with 76 additions and 17 deletions

View File

@@ -134,6 +134,13 @@ IB_DESIGNABLE
* @param animated Specify `YES` to animate the change by smoothly scrolling and zooming or `NO` to immediately display the given bounds. */
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated;
/** Changes the receiver’s viewport to fit all of the given coordinates and optionally some additional padding on each side.
* @param coordinates The coordinates that the viewport will show.
* @param count The number of coordinates. This number must not be greater than the number of elements in `coordinates`.
* @param insets The minimum padding (in screen points) that will be visible around the given coordinate bounds.
* @param animated Specify `YES` to animate the change by smoothly scrolling and zooming or `NO` to immediately display the given bounds. */
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated;
/** The heading of the map (measured in degrees) relative to true north.
*
* The value `0` means that the top edge of the map view corresponds to true north. The value `90` means the top of the map is pointing due east. The value `180` means the top of the map points due south, and so on. */

View File

@@ -100,6 +100,7 @@ public:
double getZoom() const;
void setLatLngZoom(LatLng latLng, double zoom, Duration = Duration::zero());
void fitBounds(LatLngBounds bounds, EdgeInsets padding, Duration duration = Duration::zero());
void fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration duration = Duration::zero());
void resetZoom();
double getMinZoom() const;
double getMaxZoom() const;

View File

@@ -1523,13 +1523,33 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
}
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
{
CLLocationCoordinate2D coordinates[] = {
{bounds.ne.latitude, bounds.sw.longitude},
bounds.sw,
{bounds.sw.latitude, bounds.ne.longitude},
bounds.ne,
};
[self setVisibleCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
edgePadding:insets
animated:animated];
}
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
{
// NOTE: does not disrupt tracking mode
CGFloat duration = animated ? MGLAnimationDuration : 0;
[self willChangeValueForKey:@"visibleCoordinateBounds"];
mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right};
_mbglMap->fitBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), mbglInsets, secondsAsDuration(duration));
mbgl::AnnotationSegment segment;
segment.reserve(count);
for (NSUInteger i = 0; i < count; i++)
{
segment.push_back({coordinates[i].latitude, coordinates[i].longitude});
}
_mbglMap->fitBounds(segment, mbglInsets, secondsAsDuration(duration));
[self didChangeValueForKey:@"visibleCoordinateBounds"];
[self unrotateIfNeededAnimated:animated];

View File

@@ -153,25 +153,35 @@ void Map::setLatLngZoom(LatLng latLng, double zoom, Duration duration) {
}
void Map::fitBounds(LatLngBounds bounds, EdgeInsets padding, Duration duration) {
// Calculate the bounds of the possibly rotated `bounds` parameter with respect to the viewport.
vec2<> nwPixel = pixelForLatLng({bounds.ne.latitude, bounds.sw.longitude});
vec2<> swPixel = pixelForLatLng(bounds.sw);
vec2<> sePixel = pixelForLatLng({bounds.sw.latitude, bounds.ne.longitude});
vec2<> nePixel = pixelForLatLng(bounds.ne);
vec2<> visualBounds = {
(std::max(std::max(nwPixel.x, swPixel.x),
std::max(sePixel.x, nePixel.x)) -
std::min(std::min(nwPixel.x, swPixel.x),
std::min(sePixel.x, nePixel.x))),
(std::max(std::max(nwPixel.y, swPixel.y),
std::max(sePixel.y, nePixel.y)) -
std::min(std::min(nwPixel.y, swPixel.y),
std::min(sePixel.y, nePixel.y))),
AnnotationSegment segment = {
{bounds.ne.latitude, bounds.sw.longitude},
bounds.sw,
{bounds.sw.latitude, bounds.ne.longitude},
bounds.ne,
};
fitBounds(segment, padding, duration);
}
void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration duration) {
if (segment.empty()) {
return;
}
// Calculate the bounds of the possibly rotated shape with respect to the viewport.
vec2<> nePixel = {-INFINITY, -INFINITY};
vec2<> swPixel = {INFINITY, INFINITY};
for (LatLng latLng : segment) {
vec2<> pixel = pixelForLatLng(latLng);
swPixel.x = std::min(swPixel.x, pixel.x);
nePixel.x = std::max(nePixel.x, pixel.x);
swPixel.y = std::min(swPixel.y, pixel.y);
nePixel.y = std::max(nePixel.y, pixel.y);
}
vec2<> size = nePixel - swPixel;
// Calculate the zoom level.
double scaleX = (getWidth() - padding.left - padding.right) / visualBounds.x;
double scaleY = (getHeight() - padding.top - padding.bottom) / visualBounds.y;
double scaleX = (getWidth() - padding.left - padding.right) / size.x;
double scaleY = (getHeight() - padding.top - padding.bottom) / size.y;
double minScale = std::fmin(scaleX, scaleY);
double zoom = std::log2(getScale() * minScale);
zoom = std::fmax(std::fmin(zoom, getMaxZoom()), getMinZoom());

View File

@@ -183,6 +183,27 @@
@"after panning 30° to the east, setting visible coordinate bounds back to %@ should not leave them at %@",
MGLStringFromCoordinateBounds(initialBounds),
MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds));
// Inscribed shapes with rotation
tester.mapView.direction = 45;
// https://en.wikipedia.org/wiki/Boundary_Markers_of_the_Original_District_of_Columbia
CLLocationCoordinate2D dcCoordinates[] = {
{38.790339, -77.040583},
{38.893219, -77.172304},
{38.995946, -77.040947},
{38.892829, -76.909229},
};
MGLCoordinateBounds dcBounds = {{38.790339, -77.172304}, {38.995946, -76.909229}};
[tester.mapView setVisibleCoordinateBounds:dcBounds
animated:NO];
double zoomLevel = tester.mapView.zoomLevel;
[tester.mapView setVisibleCoordinates:dcCoordinates
count:sizeof(dcCoordinates) / sizeof(dcCoordinates[0])
edgePadding:UIEdgeInsetsZero
animated:NO];
XCTAssertGreaterThan(tester.mapView.zoomLevel, zoomLevel,
@"when the map is rotated, DC should fit at a zoom level higher than %f, but instead the zoom level is %f",
zoomLevel, tester.mapView.zoomLevel);
}
- (void)testPan {