Map and Transform methods assume an origin at the top-left corner of the view, like iOS, Android, and GLFW but unlike OS X. Transform is responsible for flipping coordinates between the top-left origin of Map and the bottom-left origin of TransformState. Fixes #3574.
564 lines
16 KiB
C++
564 lines
16 KiB
C++
#include <mbgl/map/map.hpp>
|
|
#include <mbgl/map/map_context.hpp>
|
|
#include <mbgl/map/camera.hpp>
|
|
#include <mbgl/map/view.hpp>
|
|
#include <mbgl/map/transform.hpp>
|
|
#include <mbgl/map/transform_state.hpp>
|
|
#include <mbgl/map/map_data.hpp>
|
|
#include <mbgl/annotation/point_annotation.hpp>
|
|
#include <mbgl/annotation/shape_annotation.hpp>
|
|
#include <mbgl/style/style_layer.hpp>
|
|
#include <mbgl/layer/custom_layer.hpp>
|
|
|
|
#include <mbgl/util/projection.hpp>
|
|
#include <mbgl/util/thread.hpp>
|
|
#include <mbgl/util/math.hpp>
|
|
|
|
namespace mbgl {
|
|
|
|
Map::Map(View& view_, FileSource& fileSource, MapMode mapMode, GLContextMode contextMode, ConstrainMode constrainMode)
|
|
: view(view_),
|
|
transform(std::make_unique<Transform>(view, constrainMode)),
|
|
context(std::make_unique<util::Thread<MapContext>>(
|
|
util::ThreadContext{"Map", util::ThreadType::Map, util::ThreadPriority::Regular},
|
|
view, fileSource, mapMode, contextMode, view.getPixelRatio())),
|
|
data(&context->invokeSync<MapData&>(&MapContext::getData))
|
|
{
|
|
view.initialize(this);
|
|
update(Update::Dimensions);
|
|
}
|
|
|
|
Map::~Map() {
|
|
resume();
|
|
context->invoke(&MapContext::cleanup);
|
|
}
|
|
|
|
void Map::pause() {
|
|
assert(data->mode == MapMode::Continuous);
|
|
|
|
std::unique_lock<std::mutex> lockPause(data->mutexPause);
|
|
if (!data->paused) {
|
|
context->invoke(&MapContext::pause);
|
|
data->condPause.wait(lockPause, [&]{ return data->paused; });
|
|
}
|
|
}
|
|
|
|
bool Map::isPaused() {
|
|
return data->paused;
|
|
}
|
|
|
|
void Map::resume() {
|
|
std::unique_lock<std::mutex> lockPause(data->mutexPause);
|
|
data->paused = false;
|
|
data->condPause.notify_all();
|
|
}
|
|
|
|
void Map::renderStill(StillImageCallback callback) {
|
|
context->invoke(&MapContext::renderStill, transform->getState(),
|
|
FrameData{ view.getFramebufferSize() }, callback);
|
|
}
|
|
|
|
void Map::renderSync() {
|
|
if (renderState == RenderState::never) {
|
|
view.notifyMapChange(MapChangeWillStartRenderingMap);
|
|
}
|
|
|
|
view.notifyMapChange(MapChangeWillStartRenderingFrame);
|
|
|
|
const Update flags = transform->updateTransitions(Clock::now());
|
|
const bool fullyLoaded = context->invokeSync<bool>(
|
|
&MapContext::renderSync, transform->getState(), FrameData { view.getFramebufferSize() });
|
|
|
|
view.notifyMapChange(fullyLoaded ?
|
|
MapChangeDidFinishRenderingFrameFullyRendered :
|
|
MapChangeDidFinishRenderingFrame);
|
|
|
|
if (!fullyLoaded) {
|
|
renderState = RenderState::partial;
|
|
} else if (renderState != RenderState::fully) {
|
|
renderState = RenderState::fully;
|
|
view.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered);
|
|
if (data->loading) {
|
|
data->loading = false;
|
|
view.notifyMapChange(MapChangeDidFinishLoadingMap);
|
|
}
|
|
}
|
|
|
|
// Triggers an asynchronous update, that eventually triggers a view
|
|
// invalidation, causing renderSync to be called again if in transition.
|
|
if (flags != Update::Nothing) {
|
|
update(flags);
|
|
}
|
|
}
|
|
|
|
void Map::update(Update flags) {
|
|
if (flags & Update::Dimensions) {
|
|
transform->resize(view.getSize());
|
|
}
|
|
context->invoke(&MapContext::triggerUpdate, transform->getState(), flags);
|
|
}
|
|
|
|
#pragma mark - Style
|
|
|
|
void Map::setStyleURL(const std::string &url) {
|
|
view.notifyMapChange(MapChangeWillStartLoadingMap);
|
|
context->invoke(&MapContext::setStyleURL, url);
|
|
}
|
|
|
|
void Map::setStyleJSON(const std::string& json, const std::string& base) {
|
|
view.notifyMapChange(MapChangeWillStartLoadingMap);
|
|
context->invoke(&MapContext::setStyleJSON, json, base);
|
|
}
|
|
|
|
std::string Map::getStyleURL() const {
|
|
return context->invokeSync<std::string>(&MapContext::getStyleURL);
|
|
}
|
|
|
|
std::string Map::getStyleJSON() const {
|
|
return context->invokeSync<std::string>(&MapContext::getStyleJSON);
|
|
}
|
|
|
|
#pragma mark - Transitions
|
|
|
|
void Map::cancelTransitions() {
|
|
transform->cancelTransitions();
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::setGestureInProgress(bool inProgress) {
|
|
transform->setGestureInProgress(inProgress);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
bool Map::isGestureInProgress() const {
|
|
return transform->isGestureInProgress();
|
|
}
|
|
|
|
bool Map::isRotating() const {
|
|
return transform->isRotating();
|
|
}
|
|
|
|
bool Map::isScaling() const {
|
|
return transform->isScaling();
|
|
}
|
|
|
|
bool Map::isPanning() const {
|
|
return transform->isPanning();
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
void Map::jumpTo(const CameraOptions& camera) {
|
|
transform->jumpTo(camera);
|
|
update(camera.zoom ? Update::Zoom : Update::Repaint);
|
|
}
|
|
|
|
void Map::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
|
|
transform->easeTo(camera, animation);
|
|
update(camera.zoom ? Update::Zoom : Update::Repaint);
|
|
}
|
|
|
|
|
|
void Map::flyTo(const CameraOptions& camera, const AnimationOptions& animation) {
|
|
transform->flyTo(camera, animation);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
#pragma mark - Position
|
|
|
|
void Map::moveBy(const PrecisionPoint& point, const Duration& duration) {
|
|
transform->moveBy(point, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::setLatLng(const LatLng& latLng, const Duration& duration) {
|
|
setLatLng(latLng, EdgeInsets(), duration);
|
|
}
|
|
|
|
void Map::setLatLng(const LatLng& latLng, const EdgeInsets& padding, const Duration& duration) {
|
|
transform->setLatLng(latLng, padding, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::setLatLng(const LatLng& latLng, const PrecisionPoint& point, const Duration& duration) {
|
|
transform->setLatLng(latLng, point, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
LatLng Map::getLatLng(const EdgeInsets& padding) const {
|
|
return transform->getLatLng(padding);
|
|
}
|
|
|
|
void Map::resetPosition(const EdgeInsets& padding) {
|
|
CameraOptions camera;
|
|
camera.angle = 0;
|
|
camera.center = LatLng(0, 0);
|
|
if (padding) {
|
|
camera.padding = padding;
|
|
}
|
|
camera.zoom = 0;
|
|
transform->jumpTo(camera);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
|
|
#pragma mark - Scale
|
|
|
|
void Map::scaleBy(double ds, const PrecisionPoint& point, const Duration& duration) {
|
|
transform->scaleBy(ds, point, duration);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
void Map::setScale(double scale, const PrecisionPoint& point, const Duration& duration) {
|
|
transform->setScale(scale, point, duration);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
double Map::getScale() const {
|
|
return transform->getScale();
|
|
}
|
|
|
|
void Map::setZoom(double zoom, const Duration& duration) {
|
|
setZoom(zoom, {}, duration);
|
|
}
|
|
|
|
void Map::setZoom(double zoom, const EdgeInsets& padding, const Duration& duration) {
|
|
transform->setZoom(zoom, padding, duration);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
double Map::getZoom() const {
|
|
return transform->getZoom();
|
|
}
|
|
|
|
void Map::setLatLngZoom(const LatLng& latLng, double zoom, const Duration& duration) {
|
|
setLatLngZoom(latLng, zoom, {}, duration);
|
|
}
|
|
|
|
void Map::setLatLngZoom(const LatLng& latLng, double zoom, const EdgeInsets& padding, const Duration& duration) {
|
|
transform->setLatLngZoom(latLng, zoom, padding, duration);
|
|
update(Update::Zoom);
|
|
}
|
|
|
|
CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding) {
|
|
AnnotationSegment segment = {
|
|
{bounds.ne.latitude, bounds.sw.longitude},
|
|
bounds.sw,
|
|
{bounds.sw.latitude, bounds.ne.longitude},
|
|
bounds.ne,
|
|
};
|
|
return cameraForLatLngs(segment, padding);
|
|
}
|
|
|
|
CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, const EdgeInsets& padding) {
|
|
CameraOptions options;
|
|
if (latLngs.empty()) {
|
|
return options;
|
|
}
|
|
|
|
// Calculate the bounds of the possibly rotated shape with respect to the viewport.
|
|
PrecisionPoint nePixel = {-INFINITY, -INFINITY};
|
|
PrecisionPoint swPixel = {INFINITY, INFINITY};
|
|
double viewportHeight = getHeight();
|
|
for (LatLng latLng : latLngs) {
|
|
PrecisionPoint 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, viewportHeight - pixel.y);
|
|
nePixel.y = std::max(nePixel.y, viewportHeight - pixel.y);
|
|
}
|
|
double width = nePixel.x - swPixel.x;
|
|
double height = nePixel.y - swPixel.y;
|
|
|
|
// Calculate the zoom level.
|
|
double scaleX = (getWidth() - padding.left - padding.right) / width;
|
|
double scaleY = (getHeight() - padding.top - padding.bottom) / height;
|
|
double minScale = ::fmin(scaleX, scaleY);
|
|
double zoom = ::log2(getScale() * minScale);
|
|
zoom = util::clamp(zoom, getMinZoom(), getMaxZoom());
|
|
|
|
// Calculate the center point of a virtual bounds that is extended in all directions by padding.
|
|
PrecisionPoint paddedNEPixel = {
|
|
nePixel.x + padding.right / minScale,
|
|
nePixel.y + padding.top / minScale,
|
|
};
|
|
PrecisionPoint paddedSWPixel = {
|
|
swPixel.x - padding.left / minScale,
|
|
swPixel.y - padding.bottom / minScale,
|
|
};
|
|
PrecisionPoint centerPixel = {
|
|
(paddedNEPixel.x + paddedSWPixel.x) / 2,
|
|
(paddedNEPixel.y + paddedSWPixel.y) / 2,
|
|
};
|
|
|
|
// CameraOptions origin is at the top-left corner.
|
|
centerPixel.y = viewportHeight - centerPixel.y;
|
|
|
|
options.center = latLngForPixel(centerPixel);
|
|
options.zoom = zoom;
|
|
return options;
|
|
}
|
|
|
|
void Map::resetZoom() {
|
|
setZoom(0);
|
|
}
|
|
|
|
double Map::getMinZoom() const {
|
|
return transform->getState().getMinZoom();
|
|
}
|
|
|
|
double Map::getMaxZoom() const {
|
|
return transform->getState().getMaxZoom();
|
|
}
|
|
|
|
|
|
#pragma mark - Size
|
|
|
|
uint16_t Map::getWidth() const {
|
|
return transform->getState().getWidth();
|
|
}
|
|
|
|
uint16_t Map::getHeight() const {
|
|
return transform->getState().getHeight();
|
|
}
|
|
|
|
|
|
#pragma mark - Rotation
|
|
|
|
void Map::rotateBy(const PrecisionPoint& first, const PrecisionPoint& second, const Duration& duration) {
|
|
transform->rotateBy(first, second, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::setBearing(double degrees, const Duration& duration) {
|
|
setBearing(degrees, EdgeInsets(), duration);
|
|
}
|
|
|
|
void Map::setBearing(double degrees, const PrecisionPoint& center, const Duration& duration) {
|
|
transform->setAngle(-degrees * M_PI / 180, center, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::setBearing(double degrees, const EdgeInsets& padding, const Duration& duration) {
|
|
transform->setAngle(-degrees * M_PI / 180, padding, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
double Map::getBearing() const {
|
|
return -transform->getAngle() / M_PI * 180;
|
|
}
|
|
|
|
void Map::resetNorth(const Duration& duration) {
|
|
transform->setAngle(0, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
|
|
#pragma mark - Pitch
|
|
|
|
void Map::setPitch(double pitch, const Duration& duration) {
|
|
transform->setPitch(pitch * M_PI / 180, duration);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
double Map::getPitch() const {
|
|
return transform->getPitch() / M_PI * 180;
|
|
}
|
|
|
|
|
|
#pragma mark - North Orientation
|
|
|
|
void Map::setNorthOrientation(NorthOrientation orientation) {
|
|
transform->setNorthOrientation(orientation);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
NorthOrientation Map::getNorthOrientation() const {
|
|
return transform->getNorthOrientation();
|
|
}
|
|
|
|
|
|
#pragma mark - Projection
|
|
|
|
MetersBounds Map::getWorldBoundsMeters() const {
|
|
return Projection::getWorldBoundsMeters();
|
|
}
|
|
|
|
LatLngBounds Map::getWorldBoundsLatLng() const {
|
|
return Projection::getWorldBoundsLatLng();
|
|
}
|
|
|
|
double Map::getMetersPerPixelAtLatitude(double lat, double zoom) const {
|
|
return Projection::getMetersPerPixelAtLatitude(lat, zoom);
|
|
}
|
|
|
|
ProjectedMeters Map::projectedMetersForLatLng(const LatLng& latLng) const {
|
|
return Projection::projectedMetersForLatLng(latLng);
|
|
}
|
|
|
|
LatLng Map::latLngForProjectedMeters(const ProjectedMeters& projectedMeters) const {
|
|
return Projection::latLngForProjectedMeters(projectedMeters);
|
|
}
|
|
|
|
PrecisionPoint Map::pixelForLatLng(const LatLng& latLng) const {
|
|
return transform->latLngToPoint(latLng);
|
|
}
|
|
|
|
LatLng Map::latLngForPixel(const PrecisionPoint& pixel) const {
|
|
return transform->pointToLatLng(pixel);
|
|
}
|
|
|
|
#pragma mark - Annotations
|
|
|
|
void Map::addAnnotationIcon(const std::string& name, std::shared_ptr<const SpriteImage> sprite) {
|
|
context->invoke(&MapContext::addAnnotationIcon, name, sprite);
|
|
}
|
|
|
|
void Map::removeAnnotationIcon(const std::string& name) {
|
|
context->invoke(&MapContext::removeAnnotationIcon, name);
|
|
}
|
|
|
|
double Map::getTopOffsetPixelsForAnnotationIcon(const std::string& symbol) {
|
|
return context->invokeSync<double>(&MapContext::getTopOffsetPixelsForAnnotationIcon, symbol);
|
|
}
|
|
|
|
AnnotationID Map::addPointAnnotation(const PointAnnotation& annotation) {
|
|
return addPointAnnotations({ annotation }).front();
|
|
}
|
|
|
|
AnnotationIDs Map::addPointAnnotations(const std::vector<PointAnnotation>& annotations) {
|
|
auto result = data->getAnnotationManager()->addPointAnnotations(annotations, getMaxZoom());
|
|
update(Update::Annotations);
|
|
return result;
|
|
}
|
|
|
|
AnnotationID Map::addShapeAnnotation(const ShapeAnnotation& annotation) {
|
|
return addShapeAnnotations({ annotation }).front();
|
|
}
|
|
|
|
AnnotationIDs Map::addShapeAnnotations(const std::vector<ShapeAnnotation>& annotations) {
|
|
auto result = data->getAnnotationManager()->addShapeAnnotations(annotations, getMaxZoom());
|
|
update(Update::Annotations);
|
|
return result;
|
|
}
|
|
|
|
void Map::removeAnnotation(AnnotationID annotation) {
|
|
removeAnnotations({ annotation });
|
|
}
|
|
|
|
void Map::removeAnnotations(const AnnotationIDs& annotations) {
|
|
data->getAnnotationManager()->removeAnnotations(annotations);
|
|
update(Update::Annotations);
|
|
}
|
|
|
|
AnnotationIDs Map::getPointAnnotationsInBounds(const LatLngBounds& bounds) {
|
|
return data->getAnnotationManager()->getPointAnnotationsInBounds(bounds);
|
|
}
|
|
|
|
LatLngBounds Map::getBoundsForAnnotations(const AnnotationIDs& annotations) {
|
|
return data->getAnnotationManager()->getBoundsForAnnotations(annotations);
|
|
}
|
|
|
|
#pragma mark - Style API
|
|
|
|
void Map::addCustomLayer(const std::string& id,
|
|
CustomLayerInitializeFunction initialize,
|
|
CustomLayerRenderFunction render,
|
|
CustomLayerDeinitializeFunction deinitialize,
|
|
void* context_,
|
|
const char* before) {
|
|
context->invoke(&MapContext::addLayer,
|
|
std::make_unique<CustomLayer>(id, initialize, render, deinitialize, context_),
|
|
before ? std::string(before) : mapbox::util::optional<std::string>());
|
|
}
|
|
|
|
void Map::removeCustomLayer(const std::string& id) {
|
|
context->invoke(&MapContext::removeLayer, id);
|
|
}
|
|
|
|
#pragma mark - Toggles
|
|
|
|
void Map::setDebug(MapDebugOptions mode) {
|
|
data->setDebug(mode);
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
void Map::cycleDebugOptions() {
|
|
data->cycleDebugOptions();
|
|
update(Update::Repaint);
|
|
}
|
|
|
|
MapDebugOptions Map::getDebug() const {
|
|
return data->getDebug();
|
|
}
|
|
|
|
bool Map::isFullyLoaded() const {
|
|
return context->invokeSync<bool>(&MapContext::isLoaded);
|
|
}
|
|
|
|
void Map::addClass(const std::string& klass) {
|
|
if (data->addClass(klass)) {
|
|
update(Update::Classes);
|
|
}
|
|
}
|
|
|
|
void Map::removeClass(const std::string& klass) {
|
|
if (data->removeClass(klass)) {
|
|
update(Update::Classes);
|
|
}
|
|
}
|
|
|
|
void Map::setClasses(const std::vector<std::string>& classes) {
|
|
data->setClasses(classes);
|
|
update(Update::Classes);
|
|
}
|
|
|
|
bool Map::hasClass(const std::string& klass) const {
|
|
return data->hasClass(klass);
|
|
}
|
|
|
|
std::vector<std::string> Map::getClasses() const {
|
|
return data->getClasses();
|
|
}
|
|
|
|
void Map::setDefaultFadeDuration(const Duration& duration) {
|
|
data->setDefaultFadeDuration(duration);
|
|
update(Update::Classes);
|
|
}
|
|
|
|
Duration Map::getDefaultFadeDuration() const {
|
|
return data->getDefaultFadeDuration();
|
|
}
|
|
|
|
void Map::setDefaultTransitionDuration(const Duration& duration) {
|
|
data->setDefaultTransitionDuration(duration);
|
|
update(Update::DefaultTransition);
|
|
}
|
|
|
|
Duration Map::getDefaultTransitionDuration() const {
|
|
return data->getDefaultTransitionDuration();
|
|
}
|
|
|
|
void Map::setDefaultTransitionDelay(const Duration& delay) {
|
|
data->setDefaultTransitionDelay(delay);
|
|
update(Update::DefaultTransition);
|
|
}
|
|
|
|
Duration Map::getDefaultTransitionDelay() const {
|
|
return data->getDefaultTransitionDelay();
|
|
}
|
|
|
|
void Map::setSourceTileCacheSize(size_t size) {
|
|
context->invoke(&MapContext::setSourceTileCacheSize, size);
|
|
}
|
|
|
|
void Map::onLowMemory() {
|
|
context->invoke(&MapContext::onLowMemory);
|
|
}
|
|
|
|
void Map::dumpDebugLogs() const {
|
|
context->invokeSync(&MapContext::dumpDebugLogs);
|
|
}
|
|
|
|
} // namespace mbgl
|