Leaflet: Permitir deslocamento no centro do mapa.

Criado em 31 jul. 2012  ·  31Comentários  ·  Fonte: Leaflet/Leaflet

Oi. Seria super incrível poder deslocar o centro do mapa em relação à janela de visualização. Meu caso de uso principal é que eu tenho uma caixa grande sobrepondo o mapa à esquerda e gostaria que o centro do mapa ficasse mais à direita. Isso significa que quando eu faço uma panorâmica ou centralização, na verdade seria deslocado uma certa quantidade de pixels ou porcentagem de viewport. Esta seria uma configuração de nível de mapa.

Eu queria esse recurso em muitas bibliotecas de mapeamento JS, mas ainda não o encontrei. Infelizmente, ele precisa ser de nível bastante baixo na API.

Desculpe, eu não tenho nenhum código, estava apenas pensando e queria baixá-lo.

Obrigado pelo projeto incrível!

feature

Comentários muito úteis

Veja como implementei atualmente um panTo com deslocamento em nosso projeto em https://github.com/codeforamerica/lv-trucks-map (site ao vivo em http://clvfoodtrucks.com/). Ele modifica o objeto protótipo L.map para que esse método esteja "nativamente" disponível em objetos de mapa e deve ser adicionado ao seu código JavaScript antes de criar um mapa:

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 })
}

Funciona da mesma forma que panTo(), mas com um parâmetro adicional, offset , que é um array na forma de [xOffset, yOffset] . Você pode modificar isso para criar um método setViewOffset() também.

Todos 31 comentários

Na verdade, é muito fácil no Leaflet. O que você precisa é do método map.containerPointToLatLng . Ele converte algumas coordenadas de pixel relativas ao canto superior esquerdo do mapa em localização geográfica. Então você faz algo assim:

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

map.panTo(targetLatLng);

Isso resolve seu caso de uso?

Ei. Ele meio que faz, e provavelmente é bom o suficiente. Mas, o problema aqui é que ele assume que a janela de visualização está onde você deseja centralizar, então desloque-a, então seria um movimento de duas etapas. Ao invés de tomar em lat-lng, offset e depois panning; um único movimento.

Ah, meus cálculos não estão corretos. Você precisa definir o centro real do mapa de forma que o centro do deslocamento esteja no ponto de destino e o deslocamento seja igual overlayWidth / 2 . Então aqui está o código de trabalho para um único movimento:

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

map.setView(targetLatLng, targetZoom);

Este é o mesmo caso que tivemos - uma barra lateral ligeiramente opaca sobreposta no topo do mapa. Sempre que chamávamos fitBounds(), ficaria um pouco errado e precisaríamos fazer outro movimento para acertar. Estávamos considerando uma solicitação de pull para isso, mas acabamos movendo a barra lateral para ficar ao lado do mapa, pois achamos que poderia ser um pouco trabalhoso e não achamos que era um caso de uso comum o suficiente.

@mourner Isso faz muito sentido. Acho que perdi os métodos project(). Isso lida com a maior parte do meu caso de uso. Muito Obrigado.

Como @ajbeaven , um ajuste fitBounds para uma camada (GeoJSON) seria super incrível, mas parece que seria muito mais complicado. Eu não acho que apenas o deslocamento seja bom o suficiente, pois os limites da janela de visualização estão mudando efetivamente, nem eu saberia como fazer isso em uma chamada.

De qualquer forma, sinta-se à vontade para fechar isso se não fizer sentido adicionar na própria biblioteca. Acho que posso gerenciar meu projeto específico com o código fornecido. Obrigado.

fitBounds é um pouco mais detalhado, mas também fácil. Basta copiar e colar este método https://github.com/CloudMade/Leaflet/blob/master/src/map/Map.js#L275 , modificando o tamanho que ele usa e, em seguida, use

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

Oi. Estou usando fitBounds com LatLng[] como parâmetro e tentei usar trechos de código que você forneceu acima, mas sem sorte.
Minha web tem uma sobreposição de barra lateral onde quero que o mapa fique visível por baixo, mas gostaria de evitar que marcadores apareçam sob essa sobreposição. (Seria ótimo deslocar o centro do mapa também, mas isso pode ser feito manualmente.)

Você poderia postar um trecho de código completo para fitBounds em _offset map_?
Estou bastante confuso com o que você escreveu acima. O problema é que não tenho targetLatLng nem targetPoint pois uso array de pontos.

Por falar nisso. seria realmente incrível ter uma opção para definir a quantidade de espaço semi-sobreposto em cada lado do contêiner, onde você ainda desenharia o mapa, mas não colocaria nenhum controle nos marcadores lá.

Você pode ver o código que implementei aqui. (não é de forma alguma uma grande instância de código, mas a ideia está lá) Ele também está embutido em um objeto que contém uma propriedade de mapa que é o mapa Leaflet. Possui os seguintes métodos:

appSetView
appSetBounds
appGetBoundsZoom

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

Reabrindo, pois seria útil adicionar essa funcionalidade incorporada.

Me deparei com essa discussão porque eu tinha que ter uma lista no lado esquerdo da tela de 550px com o mapa atrás dela. Decidi que provavelmente seria mais fácil substituir algumas funções básicas do L.Map. Obrigado por todas as suas idéias.

Espero ter coberto todos eles, mas não tenho 100% de certeza.

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 Obrigado por postar isso. Sua solução está funcionando bem para mim atualmente ... exceto que os controles de zoom estão quebrados no Firefox agora :( Estou recebendo o tipo de erro "catchall": TypeError: t is undefined

Aqui está uma implementação de fitBoundsPadded, que deve estar no mapa:
Use-o como fitBounds, mas passe o preenchimento desejado, então use-o como:

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

E o código:

    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--;
            }
        }
    },

Implementado no mestre! Use assim:

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

Ou passe apenas o segundo argumento e o preenchimento do canto inferior direito será padronizado para o canto superior esquerdo.

A assinatura mudou um pouco. Agora você usa assim:

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

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

Existe algum trabalho planejado para adicionar preenchimento a outras funções?

Possivelmente, sim. Por setView e panTo pelo menos.

Deve haver um atalho para aplicar o mesmo preenchimento em todos os lados. Algo assim.

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

Ao invés do atual...

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

@averrips Esse Mixin foi muito útil, obrigado.

Eu não consegui encontrar a solução ainda, então aqui vai (muito fácil):

 function recentr(map,latlng,offsetx,offsety) {
 var center = map.project(latlng);
 centro = new L.ponto(centro.x+offsetx,center.y+offsety);
 var alvo = map.unproject(center);
 map.panTo(destino);
 }

Por favor, adicione a mesma funcionalidade também para "setView" e "panTo".
Obrigado!

Além disso, existe uma maneira de preencher também os controles esquerdo | direito?

Veja como implementei atualmente um panTo com deslocamento em nosso projeto em https://github.com/codeforamerica/lv-trucks-map (site ao vivo em http://clvfoodtrucks.com/). Ele modifica o objeto protótipo L.map para que esse método esteja "nativamente" disponível em objetos de mapa e deve ser adicionado ao seu código JavaScript antes de criar um mapa:

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 })
}

Funciona da mesma forma que panTo(), mas com um parâmetro adicional, offset , que é um array na forma de [xOffset, yOffset] . Você pode modificar isso para criar um método setViewOffset() também.

Obrigado, lu!

Para referência, esta configuração funciona para mim para emular o deslocamento horizontal como visto na página inicial do foursquare.
https://gist.github.com/missinglink/7620340

Obrigado @louh!

Obrigado @missinglink , seu código funciona!

Existe uma solução também para as ações de zoom in/out?

Estou usando algo assim:

        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);

mas falha no zoom.

Aqui está a solução: https://github.com/Mappy/Leaflet-active-area. Brilhante! :)

A função 'map.fitBounds(bounds, topLeftPadding, bottomRightPadding)' parece estar usando valores para ajustar a latitude, não pixels. Portanto, diferentes níveis de zoom terão resultados diferentes sobre o quanto o mapa é ajustado. Tenho certeza de que há um caso de uso para isso, mas parece que para o problema que todos estão levantando, uma barra lateral de tamanho definido ou menu superior, o ajuste não deve depender do nível de zoom. Existe uma função de folheto embutida que adiciona preenchimento com base em pixels em vez de coordenadas?

topLeftPadding e bottomRightPadding esperam valores em pixels: http://leafletjs.com/reference.html#map -fitboundsoptions

Por alguma razão, não parece ser o meu caso. Para testar isso, centro um marcador em um nível de zoom de 15 e, em seguida, executo este comando ...
map.fitBounds(pointsArray, {paddingTopLeft: [0,0], paddingBottomRight: [800,0], maxZoom: map.getZoom()});
Então eu recentralizo o marcador em um nível de zoom de 19 e o executo novamente. No nível 15 é quase imperceptível a quantidade que muda. No entanto, no nível 19, ele muda cerca de metade da página. Estou fazendo algo errado na minha chamada de função?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

JonnyBGod picture JonnyBGod  ·  4Comentários

jblarsen picture jblarsen  ·  3Comentários

onethread picture onethread  ·  3Comentários

broofa picture broofa  ·  4Comentários

arminghm picture arminghm  ·  3Comentários