Leaflet: 允许在地图中心偏移。

创建于 2012-07-31  ·  31评论  ·  资料来源: Leaflet/Leaflet

你好。 能够相对于视口偏移地图的中心将是非常棒的。 我的主要用例是我在左侧有一个覆盖地图的大框,并且希望地图的中心更靠右。 这意味着当我进行平移或居中时,它实际上会偏移一定数量的像素或视口的百分比。 这将是地图级别设置。

我在许多 JS 映射库中都想要这个功能,但还没有找到。 不幸的是,它需要在 API 中处于相当低的级别。

对不起,我没有任何代码,只是想着想把它写下来。

感谢您的精彩项目!

feature

最有用的评论

以下是我目前在https://github.com/codeforamerica/lv-trucks-map (实时站点 http://clvfoodtrucks.com/)的项目中实现带有偏移的 panTo 的方式。 它修改了 L.map 原型对象,以便此方法在地图对象上“本机”可用,并且应该在创建地图之前将其添加到您的 JavaScript 代码中:

L.Map.prototype.panToOffset = function (latlng, offset, options) {
    var x = this.latLngToContainerPoint(latlng).x - offset[0]
    var y = this.latLngToContainerPoint(latlng).y - offset[1]
    var point = this.containerPointToLatLng([x, y])
    return this.setView(point, this._zoom, { pan: options })
}

它的工作原理与 panTo() 相同,但有一个附加参数offset ,它是一个[xOffset, yOffset]形式的数组。 您也可以修改它以创建 setViewOffset() 方法。

所有31条评论

在 Leaflet 中实际上很容易。 您需要的是map.containerPointToLatLng方法。 它将相对于地图左上角的一些像素坐标转换为地理位置。 所以你做这样的事情:

var centerPoint = map.getSize().divideBy(2),
    targetPoint = centerPoint.subtract([overlayWidth, 0]),
    targetLatLng = map.containerPointToLatLng(centerPoint);

map.panTo(targetLatLng);

这是否解决了您的用例?

嘿。 它有点,而且可能已经足够好了。 但是,这里的问题是它假设视口是您想要居中的位置,然后偏移它,所以这将是一个两步移动。 与接受 lat-lng、偏移、然后平移相反; 一个单一的动作。

哦,我的计算不正确。 您需要以偏移中心位于目标点的方式设置实际地图中心,并且偏移量将等于overlayWidth / 2 。 所以这里是单个动作的工作代码:

var targetPoint = map.project(targetLatLng, targetZoom).subtract([overlayWidth / 2, 0]),
    targetLatLng = map.unproject(targetPoint, targetZoom);

map.setView(targetLatLng, targetZoom);

这与我们的情况相同——地图顶部覆盖了一个略微不透明的侧边栏。 每当我们调用 fitBounds() 时,它都会有一点偏差,我们需要再做一次移动以使其正确。 我们正在考虑为此提出拉取请求,但最终只是将侧边栏移动到地图旁边,因为我们认为这可能需要一些工作并且认为这不是一个足够常见的用例。

@mourner这很有意义。 我想我错过了 project() 方法。 这确实处理了我的大部分用例。 非常感谢。

@ajbeaven一样,(GeoJSON)层的偏移 fitBounds 会非常棒,但这似乎会复杂得多。 我不认为仅仅偏移就足够了,因为视口的边界正在有效地改变,我也不知道如何在一次调用中做到这一点。

无论哪种方式,如果添加到库本身没有意义,请随时关闭它。 我想我可以使用您提供的代码来管理我的特定项目。 谢谢。

fitBounds有点冗长但也很简单。 只需复制粘贴此https://github.com/CloudMade/Leaflet/blob/master/src/map/Map.js#L275方法,修改它使用的大小,然后使用

var targetZoom = altGetBoundsZoom(map, bounds);
// ... calculate targetLatLng as above
map.setView(targetLatLng, targetZoom);

你好。 我使用fitBoundsLatLng[]作为参数,我尝试使用您在上面提供的代码片段,但没有运气。
我的网络有一个侧边栏叠加层,我希望地图在下面可见,但我想防止标记出现在这个叠加层下。 (也可以偏移地图中心,但这可以手动完成。)

您能否在 _offset map_ 上发布fitBounds的完整代码片段?
我对你上面写的很困惑。 问题是我没有任何targetLatLng也没有targetPoint我使用点数组。

顺便提一句。 如果可以选择在容器的每一侧定义半重叠空间的数量,那真是​​太棒了,您仍然可以在其中绘制地图,但不会在标记上放置任何控件。

你可以看到我在这里实现的代码。 (它绝不是一个很好的代码实例,但想法就在那里)它还内置在一个对象中,该对象包含一个地图属性,即 Leaflet 地图。 它有以下方法:

appSetView
appSetBounds
appGetBoundsZoom

https://github.com/MinnPost/minnpost-my-boundaries/blob/master/visualizations/index.html#L556

重新打开,因为添加此内置功能会很有用。

遇到了这个讨论,因为我必须在屏幕左侧有一个 550 像素的列表,后面有地图。 我认为重写 L.Map 的一些基本功能可能会更容易。 感谢您的所有想法。

希望我已经把它们都覆盖了,但我不是 100% 确定。

MapCenterOffsetMixin = {
    UIOffset: [550, 0], // x, y
    getBounds: function(){
        var a=this.getPixelBounds(),
            b=this.unproject(new L.Point(a.min.x+this.UIOffset[0],a.max.y+this.UIOffset[1]), this._zoom,!0),
            c=this.unproject(new L.Point(a.max.x,a.min.y),this._zoom,!0);
            return new L.LatLngBounds(b,c)
    },
    _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
        var targetPoint = this.project(newCenter, newCenter).subtract([this.UIOffset[0]/2, this.UIOffset[1]/2]),
            newCenter = this.unproject(targetPoint, newZoom);
        var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
        return this.project(latlng, newZoom)._subtract(topLeft);
    },
    _getCenterLayerPoint: function () {
        return this.containerPointToLayerPoint(this.getSize().divideBy(2).add([this.UIOffset[0]/2, this.UIOffset[1]/2]));
    },
    _resetView: function (a, b, c, d) {
        var e = this._zoom !== b;
        // Change the center
        var targetPoint = this.project(a, b).subtract([this.UIOffset[0] / 2, this.UIOffset[1]/2]),
            a = this.unproject(targetPoint, b);
        d || (this.fire("movestart"), e && this.fire("zoomstart")), this._zoom = b, this._initialTopLeftPoint = this._getNewTopLeftPoint(a);
        if (!c) L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
        else {
            var f = L.DomUtil.getPosition(this._mapPane);
            this._initialTopLeftPoint._add(f)
        }
        this._tileLayersToLoad = this._tileLayersNum, this.fire("viewreset", {
            hard: !c
        }), this.fire("move"), (e || d) && this.fire("zoomend"), this.fire("moveend"), this._loaded || (this._loaded = !0, this.fire("load"))
    }
}

L.Map.include(MapCenterOffsetMixin);

@averrips感谢您发布。 您的解决方案目前对我来说效果很好......除了缩放控件现在在 Firefox 中被破坏了:( 我得到了那种“catchall”错误:TypeError: t is undefined

这是 fitBoundsPadded 的一个实现,它应该存在于地图中:
像 fitBounds 一样使用它,但是传入你想要的填充,所以像这样使用它:

map.fitBoundsPadded(myBounds, new L.Point(0, 0), new L.Point(0, 150));

和代码:

    fitBoundsPadded: function (bounds, paddingTopLeft, paddingBottomRight) { //(LatLngBounds, Point, Point)

        var zoom = this.getBoundsZoom(bounds);
        var zoomToTry = zoom;

        while (true) {
            var newTopLeft = this.unproject(this.project(bounds.getNorthEast(), zoomToTry).add(paddingTopLeft), zoomToTry);
            var newBottomRight = this.unproject(this.project(bounds.getSouthWest(), zoomToTry).add(paddingBottomRight), zoomToTry);

            var paddedBounds = new L.LatLngBounds([bounds.getSouthWest(), bounds.getNorthEast(), newTopLeft, newBottomRight]);

            var zoom2 = this.getBoundsZoom(paddedBounds);

            if (zoom2 == zoomToTry) {
                return this.fitBounds(paddedBounds);
            } else {
                zoomToTry--;
            }
        }
    },

在master中实现! 像这样使用它:

map.fitBounds(bounds, [20, 30], [40, 50]); // bounds, topLeftPadding, bottomRightPadding

或者只传递第二个参数,右下角的填充将默认为左上角。

签名有点变化。 现在你像这样使用它:

map.fitBounds(bounds, {
    padding: [20, 30]
});

map.fitBounds(bounds, {
    paddingTopLeft: [20, 30],
    paddingbottomRight: [40, 50]
});

是否有计划为其他功能添加填充?

可能,是的。 至少对于setViewpanTo

应该有一个快捷方式来在所有方面应用相同的填充。 像这样的东西。

map.fitBounds(bounds, { padding: 20 });

而不是当前...

map.fitBounds(bounds, { padding: [20, 20] });

@averrips Mixin 非常有帮助,谢谢。

我还没有找到解决方案,所以你去(真的很容易):

功能中心(地图,latlng,offsetx,offsety){
 var center = map.project(latlng);
 center = new L.point(center.x+offsetx,center.y+offsety);
 var target = map.unproject(center);
 map.panTo(目标);
 }

请为“setView”和“panTo”添加相同的功能。
谢谢!

另外,有没有办法填充左|右控件?

以下是我目前在https://github.com/codeforamerica/lv-trucks-map (实时站点 http://clvfoodtrucks.com/)的项目中实现带有偏移的 panTo 的方式。 它修改了 L.map 原型对象,以便此方法在地图对象上“本机”可用,并且应该在创建地图之前将其添加到您的 JavaScript 代码中:

L.Map.prototype.panToOffset = function (latlng, offset, options) {
    var x = this.latLngToContainerPoint(latlng).x - offset[0]
    var y = this.latLngToContainerPoint(latlng).y - offset[1]
    var point = this.containerPointToLatLng([x, y])
    return this.setView(point, this._zoom, { pan: options })
}

它的工作原理与 panTo() 相同,但有一个附加参数offset ,它是一个[xOffset, yOffset]形式的数组。 您也可以修改它以创建 setViewOffset() 方法。

谢谢,呵呵!

作为参考,这个配置可以让我模拟在foursquare主页上看到的水平偏移。
https://gist.github.com/missinglink/7620340

谢谢@louh!

感谢@missinglink ,您的代码有效!

放大/缩小操作也有解决方案吗?

我正在使用这样的东西:

        var MapCenterOffsetMixin = {
            getMapOffset: function() {
                console.log('getMapOffset');
                return [$('#left-panel').offset().left, 0];
            },
            getBounds: function(){
                console.log('getBounds');
                var offset = this.getMapOffset(),
                    bounds = this.getPixelBounds(),
                    sw = this.unproject(new L.Point(bounds.min.x + offset[0], bounds.max.y + offset[1]), this._zoom, !0),
                    ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, !0);

                return new L.LatLngBounds(sw, ne)
            },
            _oldLatLngToNewLayerPoint: L.Map.prototype._latLngToNewLayerPoint,
            _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
                console.log('_latLngToNewLayerPoint');
                var offset = this.getMapOffset(),
                    targetPoint = this.project(newCenter, newCenter).subtract([offset[0] / 2, offset[1] / 2]);
                newCenter = this.unproject(targetPoint, newZoom);

                return this._oldLatLngToNewLayerPoint(latlng, newZoom, newCenter);
            },
            _getCenterLayerPoint: function () {
                console.log('_getCenterLayerPoint');
                var offset = this.getMapOffset();
                return this.containerPointToLayerPoint(this.getSize().divideBy(2).add([offset[0] / 2, offset[1] / 2]));
            },
            _oldResetView: L.Map.prototype._resetView,
            _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
                console.log('_resetView');
                var offset = this.getMapOffset(),
                    targetPoint = this.project(center, zoom).subtract([offset[0] / 2, offset[1] / 2]);
                center = this.unproject(targetPoint, zoom);

                this._oldResetView(center, zoom, preserveMapOffset, afterZoomAnim);
            }
        };
        L.Map.include(MapCenterOffsetMixin);

但它在缩放时失败。

这是解决方案:https://github.com/Mappy/Leaflet-active-area。 杰出的! :)

'map.fitBounds(bounds, topLeftPadding, bottomRightPadding)' 函数似乎使用值来调整纬度,而不是像素。 所以不同的缩放级别会对地图的调整程度产生不同的结果。 我确信有一个用例,但似乎每个人都提出的问题,设置大小的侧边栏或顶部菜单,调整不应该取决于缩放级别。 是否有基于像素而不是坐标添加填充的内置传单功能?

topLeftPaddingbottomRightPadding期望值以像素为单位: http ://leafletjs.com/reference.html#map -fitboundsoptions

出于某种原因,这对我来说似乎并非如此。 为了测试这一点,我以 15 的缩放级别将标记居中,然后运行此命令...
map.fitBounds(pointsArray, {paddingTopLeft: [0,0], paddingBottomRight: [800,0], maxZoom: map.getZoom()});
然后我以 19 的缩放级别重新定位标记并再次运行它。 在 15 级时,它的移动量几乎无法察觉。 但是,在第 19 级,它移动了大约一半的页面。 我在函数调用中做错了吗?

此页面是否有帮助?
0 / 5 - 0 等级