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:
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user