Leaflet: Permita el desplazamiento en el centro del mapa.

Creado en 31 jul. 2012  ·  31Comentarios  ·  Fuente: Leaflet/Leaflet

Hola. Sería genial poder desplazar el centro del mapa en relación con la ventana gráfica. Mi caso de uso principal es que tengo un cuadro grande superpuesto al mapa a la izquierda y me gustaría que el centro del mapa esté más a la derecha. Esto significa que cuando hago una panorámica o un centro, en realidad se desplazaría una cierta cantidad de píxeles o un porcentaje de la ventana gráfica. Esta sería una configuración de nivel de mapa.

He querido esta función en muchas bibliotecas de mapeo JS pero aún no la he encontrado. Desafortunadamente, debe tener un nivel bastante bajo en la API.

Lo siento, no tengo ningún código, solo estaba pensando y quería bajarlo.

¡Gracias por el increíble proyecto!

feature

Comentario más útil

Así es como implementé actualmente un panTo con compensación en nuestro proyecto en https://github.com/codeforamerica/lv-trucks-map (sitio en vivo en http://clvfoodtrucks.com/). Modifica el objeto prototipo L.map para que este método esté disponible "de forma nativa" en los objetos del mapa, y debe agregarse a su código JavaScript antes de crear un 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 igual que panTo() pero con un parámetro adicional, offset , que es una matriz en forma de [xOffset, yOffset] . También puede modificar esto para crear un método setViewOffset().

Todos 31 comentarios

En realidad, es bastante fácil en Leaflet. Lo que necesita es el método map.containerPointToLatLng . Convierte algunas coordenadas de píxeles en relación con la esquina superior izquierda del mapa en una ubicación geográfica. Así que haces algo como esto:

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

map.panTo(targetLatLng);

¿Resuelve esto tu caso de uso?

Oye. De alguna manera lo hace, y probablemente sea lo suficientemente bueno. Pero, el problema aquí es que asume que la ventana gráfica está donde desea centrarla y luego compensarla, por lo que sería un movimiento de dos pasos. A diferencia de tomar lat-lng, compensar y luego panoramizar; un solo movimiento.

Oh, mis cálculos no son correctos. Debe configurar el centro del mapa real de tal manera que el centro de compensación esté en el punto de destino, y la compensación será igual a overlayWidth / 2 . Así que aquí está el código de trabajo para un solo movimiento:

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

map.setView(targetLatLng, targetZoom);

Este es el mismo caso que tuvimos también: una barra lateral ligeramente opaca superpuesta en la parte superior del mapa. Cada vez que llamamos a fitBounds(), estaría un poco fuera de lugar y tendríamos que hacer otro movimiento para hacerlo bien. Estábamos considerando una solicitud de incorporación de cambios para esto, pero terminamos simplemente moviendo la barra lateral para colocarla al lado del mapa, ya que pensamos que podría ser un poco de trabajo y no pensamos que fuera un caso de uso lo suficientemente común.

@mourner Eso tiene mucho sentido. Supongo que me perdí los métodos project(). Esto maneja la mayor parte de mi caso de uso. Muchas gracias.

Al igual que @ajbeaven , un fitBounds compensado para una capa (GeoJSON) sería genial, pero parece que sería mucho más complicado. No creo que solo la compensación sea lo suficientemente buena, ya que los límites de la ventana gráfica están cambiando efectivamente, ni sabría cómo hacerlo en una sola llamada.

De cualquier manera, siéntase libre de cerrar esto si no tiene sentido agregar la biblioteca en sí. Creo que puedo administrar mi proyecto específico con el código proporcionado. Gracias.

fitBounds es un poco más detallado pero también fácil. Simplemente copie y pegue este método https://github.com/CloudMade/Leaflet/blob/master/src/map/Map.js#L275 , modifique el tamaño que usa y luego use

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

Hola. Estoy usando fitBounds con LatLng[] como parámetro y traté de usar los fragmentos de código que proporcionó anteriormente, pero no tuve suerte.
Mi web tiene una superposición de barra lateral donde quiero que el mapa sea visible debajo, pero me gustaría evitar que aparezcan marcadores debajo de esta superposición. (Sería genial compensar el centro del mapa también, pero eso se puede hacer manualmente).

¿Podría publicar un fragmento de código completo por fitBounds en _offset map_?
Estoy bastante confundido por lo que escribiste arriba. El problema es que no tengo targetLatLng ni targetPoint ya que uso array de puntos.

Por cierto. Sería realmente increíble tener una opción para definir la cantidad de espacio semisuperpuesto en cada lado del contenedor, donde aún dibujaría el mapa, pero no colocaría ningún control en los marcadores allí.

Puedes ver el código que implementé aquí. (de ninguna manera es una gran instancia de código, pero la idea está ahí) También está integrado en un objeto que contiene una propiedad de mapa que es el mapa del folleto. Tiene los siguientes métodos:

appSetView
appSetBounds
aplicaciónGetBoundsZoom

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

Reapertura, ya que sería útil agregar esta funcionalidad incorporada.

Encontré esta discusión porque tenía que tener una lista en el lado izquierdo de la pantalla de 550 px con el mapa detrás. Decidí que probablemente sería más fácil anular algunas funciones básicas de L.Map. Gracias por todas tus ideas.

Espero haberlos cubierto todos, pero no estoy 100% seguro.

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 Gracias por publicar eso. Su solución está funcionando bien para mí actualmente ... excepto que los controles de zoom están rotos en Firefox ahora :( Recibo el tipo de error "catchall": TypeError: t is undefined

Aquí hay una implementación de fitBoundsPadded, que debería vivir en el mapa:
Úselo como fitBounds, pero pase el relleno que desee, así que úselo como:

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

Y el 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 en maestro! Úsalo así:

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

O pase solo el segundo argumento y el relleno de la parte inferior derecha se establecerá de forma predeterminada en igual a la parte superior izquierda.

La firma cambió un poco. Ahora lo usas así:

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

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

¿Hay algún trabajo planificado para agregar relleno a otras funciones?

Posiblemente, sí. Por setView y panTo como mínimo.

Debe haber un atajo para aplicar el mismo relleno en todos los lados. Algo como esto.

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

En lugar de la actual...

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

@averrips That Mixin fue muy útil, gracias.

No pude encontrar la solución todavía, así que aquí tienes (muy fácil):

 función recentor(mapa,latlng,offsetx,offsety) {
 var centro = map.project(latlng);
 center = new L.point(center.x+offsetx,center.y+offsety);
 var target = map.unproject(centro);
 map.panTo(objetivo);
 }

Agregue la misma funcionalidad también para "setView" y "panTo".
¡Gracias!

Además, ¿hay alguna forma de rellenar también los controles izquierdo | derecho?

Así es como implementé actualmente un panTo con compensación en nuestro proyecto en https://github.com/codeforamerica/lv-trucks-map (sitio en vivo en http://clvfoodtrucks.com/). Modifica el objeto prototipo L.map para que este método esté disponible "de forma nativa" en los objetos del mapa, y debe agregarse a su código JavaScript antes de crear un 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 igual que panTo() pero con un parámetro adicional, offset , que es una matriz en forma de [xOffset, yOffset] . También puede modificar esto para crear un método setViewOffset().

¡Gracias, lou!

Como referencia, esta configuración me funciona para emular el desplazamiento horizontal como se ve en la página de inicio de Foursquare.
https://gist.github.com/missinglink/7620340

Gracias @louh!

Gracias @missinglink , ¡tu código funciona!

¿Hay alguna solución también para las acciones de acercar/alejar?

Estoy usando algo como esto:

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

pero falla al hacer zoom.

Aquí está la solución: https://github.com/Mappy/Leaflet-active-area. ¡Brillante! :)

La función 'map.fitBounds(bounds, topLeftPadding, bottomRightPadding)' parece estar usando valores para ajustar lat long, no píxeles. Por lo tanto, diferentes niveles de zoom tendrán diferentes resultados en cuanto al ajuste del mapa. Estoy seguro de que hay un caso de uso para esto, pero parece que para el problema que todos mencionan, una barra lateral o menú superior de tamaño establecido, el ajuste no debería depender del nivel de zoom. ¿Existe una función de folleto incorporada que agregue relleno basado en píxeles en lugar de coordenadas?

topLeftPadding y bottomRightPadding esperan valores en píxeles: http://leafletjs.com/reference.html#map -fitboundsoptions

Por alguna razón, ese no parece ser el caso para mí. Para probar esto, centro un marcador a un nivel de zoom de 15 y luego ejecuto este comando...
map.fitBounds(pointsArray, {paddingTopLeft: [0,0], paddingBottomRight: [800,0], maxZoom: map.getZoom()});
Luego vuelvo a centrar el marcador a un nivel de zoom de 19 y lo vuelvo a ejecutar. En el nivel 15 es casi imperceptible la cantidad que cambia. Sin embargo, en el nivel 19, se desplaza alrededor de la mitad de la página. ¿Estoy haciendo algo mal en mi llamada de función?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

onethread picture onethread  ·  3Comentarios

viswaug picture viswaug  ·  4Comentarios

ssured picture ssured  ·  3Comentarios

JonnyBGod picture JonnyBGod  ·  4Comentarios

piehei picture piehei  ·  3Comentarios