2012-03-02 12 views
5

Estoy usando el "control" de v7 Bing Maps Javascript (no sé por qué se llama "control" ...). Estoy llamando al Microsoft.Maps.Map.setView({bounds: bounds}) y no está funcionando como esperaba o deseo.¿Cómo se obtiene correctamente el cuadro delimitador con LocationRect.fromLocations() cuando las ubicaciones abarcan 180º meridiano?

Tengo un conjunto de polígonos con puntos que abarcan el meridiano 180º. Un ejemplo es el límite de las islas de Nueva Zelanda, algunas de ellas al oeste del meridiano 180, algunas partes (IS de Chatham) están al este.

Cuando creo un polígono con esos límites y llamo setView() en el mapa zoom waaaaaay a cabo.

enter image description here

¿Por qué? y cómo evitarlo?


This page proporciona una demostración del problema.

Aquí está el código.

var map, MM = Microsoft.Maps; 

function showMap(m) { 
    var options = { 
    mapTypeId: MM.MapTypeId.road // aerial, 
    // center will be recalculated 
    // zoom will be recalculated 
    }, 
    map1 = new MM.Map(m, options); 
    return map1; 
} 

function doubleclickCallback(e) { 
    e.handled = true; 
    var bounds = map.getBounds(); 
    map.setView({ bounds: bounds }); 
} 

function init() { 
    var mapDiv = document.getElementById("map1"); 
    map = showMap(mapDiv); 

    MM.Events.addHandler(map, "dblclick", doubleclickCallback); 
} 

Si hace doble clic en un mapa que no tiene el meridiano 180 a la vista, no pasa nada. Si hace doble clic cuando el mapa muestra el meridiano 180º, el mapa se restablece al nivel de zoom 1.

Respuesta

11

He investigado esto.

En particular me miraba en veapicore.js, versión 7.0.2012.91, disponible en

http://ecn.dev.virtualearth.net/mapcontrol/v7.0/js/bin/7.0.2012.91/en-us/veapicore.js

Este módulo se descarga cuando se incluye el control de los mapas de Bing, como esto:

<script charset="UTF-8" type="text/javascript" 
     src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"> 
</script> 

Encontré lo que creo que son dos problemas distintos.

• La función MapMath.locationRectToMercatorZoom (una función interna, no destinada para uso directo por las aplicaciones) siempre devuelve un zoom de 1 cuando el cuadro delimitador del LocationRect se extiende por el meridiano 180º. Esto es incorrecto. Esta función es utilizada por la función Map.setView() para establecer automáticamente el zoom, lo que da como resultado el fenómeno de zoom waaay out.

• LocationRect.fromLocations() utiliza un enfoque ingenuo para determinar el cuadro delimitador para un conjunto de ubicaciones. De hecho, no se garantiza que sea un "cuadro delimitador mínimo" o un "rectángulo delimitador mínimo". La caja devuelta por ese método nunca se extiende por el meridiano 180, por lo que puedo ver. Por ejemplo, el LocationRect devuelto para un conjunto de ubicaciones que representan las fronteras de las islas de Nueva Zelanda, se iniciará en la latitud -176º y se extenderá hacia el este, hasta el meridiano + 165º. Esto es simplemente incorrecto

Resolví estos problemas mediante el parche del código en veapicore.js.

function monkeyPatchMapMath() { 
    Microsoft.Maps.InternalNamespaceForDelay.MapMath. 
    locationRectToMercatorZoom = function (windowDimensions, bounds) { 
     var ins = Microsoft.Maps.InternalNamespaceForDelay, 
     d = windowDimensions, 
     g = Microsoft.Maps.Globals, 
     n = bounds.getNorth(), 
     s = bounds.getSouth(), 
     e = bounds.getEast(), 
     w = bounds.getWest(), 
     f = ((e+360 - w) % 360)/360, 
     //f = Math.abs(w - e)/360, 
     u = Math.abs(ins.MercatorCube.latitudeToY(n) - 
        ins.MercatorCube.latitudeToY(s)), 
     r = Math.min(d.width/(g.zoomOriginWidth * f), 
        d.height/(g.zoomOriginWidth * u)); 
     return ins.VectorMath.log2(r); 
    }; 
} 



function monkeyPatchFromLocations() { 
    Microsoft.Maps.LocationRect.fromLocations = function() { 
    var com = Microsoft.Maps.InternalNamespaceForDelay.Common, 
     o = com.isArray(arguments[0]) ? arguments[0] : arguments, 
     latMax, latMin, lngMin1, lngMin2, lngMax1, lngMax2, c, 
     lngMin, lngMax, LL, dx1, dx2, 
     pt = Microsoft.Maps.AltitudeReference, 
     s, e, n, f = o.length; 

    while (f--) 
     n = o[f], 
    isFinite(n.latitude) && isFinite(n.longitude) && 
     (latMax = latMax === c ? n.latitude : Math.max(latMax, n.latitude), 
     latMin = latMin === c ? n.latitude : Math.min(latMin, n.latitude), 
     lngMax1 = lngMax1 === c ? n.longitude : Math.max(lngMax1, n.longitude), 
     lngMin1 = lngMin1 === c ? n.longitude : Math.min(lngMin1, n.longitude), 
     LL = n.longitude, 
     (LL < 0) && (LL += 360), 
     lngMax2 = lngMax2 === c ? LL : Math.max(lngMax2, LL), 
     lngMin2 = lngMin2 === c ? LL : Math.min(lngMin2, LL), 
     isFinite(n.altitude) && pt.isValid(n.altitudeReference) && 
     (e = n.altitude, s = n.altitudeReference)); 

    dx1 = lngMax1 - lngMin1, 
    dx2 = lngMax2 - lngMin2, 
    lngMax = (dx1 > dx2) ? lngMax2 : lngMax1, 
    lngMin = (dx1 > dx2) ? lngMin2 : lngMin1; 

    return Microsoft.Maps.LocationRect.fromEdges(latMax, lngMin, latMin, lngMax, e, s); 
    }; 
} 

Estas funciones necesitan ser llamados una vez antes de su uso, pero después de la carga. El primero tiene carga de retraso, creo, así que no puedes hacer el parche de mono en el documento listo; debe esperar hasta después de haber creado un Microsoft.Maps.Map.

El primero simplemente hace lo correcto dado un LocationRect. El método original voltea los bordes este y oeste en los casos en que el rectángulo se extiende por el meridiano 180º.

La segunda función corrige el método fromLocations. La implementación original recorre todas las ubicaciones y toma la longitud mínima para ser la "izquierda" y la longitud máxima para ser la "derecha". Esto falla cuando la longitud mínima está justo al este del meridiano 180 (por ejemplo, -178), y el valor de longitud máxima está justo al oeste de la misma línea (digamos, +165). El cuadro delimitador resultante debe abarcar el meridiano 180 pero, de hecho, el valor calculado con este enfoque ingenuo sigue el camino más largo.

La implementación corregida calcula ese cuadro y también calcula un segundo cuadro delimitador. Para el segundo, en lugar de usar el valor de longitud, utiliza el valor de longitud o la longitud + 360, cuando la longitud es negativa. La transformación resultante cambia la longitud de un valor que oscila entre -180 y 180, en un valor que va de 0 a 360. Y luego la función calcula el máximo y mínimo de ese nuevo conjunto de valores.

El resultado son dos cuadros delimitadores: uno con longitudes que van de -180 a +180, y otro con longitudes que van de 0 a 360. Estos cuadros serán de diferentes anchuras.

La implementación fija elige la caja con el ancho más estrecho, adivinando que la caja más pequeña es la respuesta correcta. Esta heurística se romperá si está intentando calcular el cuadro delimitador para un conjunto de puntos que es más grande que la mitad de la Tierra.

Un ejemplo de uso podría tener este aspecto:

monkeyPatchFromLocations(); 
bounds = Microsoft.Maps.LocationRect.fromLocations(allPoints); 
monkeyPatchMapMath(); 
map1.setView({bounds:bounds}); 

Esta página demuestra: http://jsbin.com/emobav/4

Haciendo doble clic sobre el mapa nunca hace que el zoom MUUCHO cabo efecto, como se vio en http://jsbin.com/emobav/2

2

Tal vez un enfoque mucho más simple.

bounds = Microsoft.Maps.LocationRect.fromLocations(allPoints); 

LocationRect.fromLocations accepts a list of locations/array

Su polígono que ha envuelto alrededor de Australia tiene una función para devolver una matriz de localizaciones, nombrados getLocations().

Parece que se podría llamar así (sintaxis de comprobación doble);

var viewBoundaries = Microsoft.Maps.LocationRect.fromLocations(polygon.getLocations()); 

         map.setView({ bounds: viewBoundaries }); 
         map.setView({ zoom: 10 }); 

¿Esto no funciona cuando abarca el 180? No veo por qué no lo haría porque solo usa los puntos del polígono. Si es así, la siguiente podría ser una solución muy simple para usted.

Usted dice que cada vez que hace doble clic en el mapa, se desplaza por el mapa. Esto tiene todo el sentido, ya que sólo ha añadido un controlador de eventos al mapa, y establecer los límites de los límites del mapa en el siguiente código:

function doubleclickCallback(e) { 
    e.handled = true; 
    var bounds = map.getBounds(); 
    map.setView({ bounds: bounds }); 
} 

MM.Events.addHandler(map, "dblclick", doubleclickCallback); 

Me gustaría pensar que lo que se necesita añadir su controlador de clic al polígono para extender la vista a un polígono específico.

Microsoft.Maps.Events.addHandler(polygon, 'click', doubleclickCallback); 

Luego, en su doubleclickCallback:

function doubleclickCallback(e) { 
    // Now we are getting the boundaries of our polygon 
    var bounds = e.target.getLocations(); 
    map.setView({ bounds: bounds }); 
    map.setView({ zoom: 9}); 
} 
+0

* ¿Este no funciona cuando atraviesa el 180? No veo por qué no ... * Chris, no funciona, porque hay un error en la biblioteca de mapas de Microsoft. Lea lo que escribí en mi respuesta. Es fácil demostrar el error pasando cualquier conjunto de puntos que abarquen los 180. El algoritmo que utilizan para calcular el cuadro delimitador o el centro es ingenuo. Lea mi respuesta, expliqué todo esto. – Cheeso

+0

@ Cheeso * La segunda función corrige el método fromLocations. * Lo siento, no entendí que fromLocations también tenía errores al leer la primera vez. ¡Malo porque eso hubiera convertido a getLocations() en un gran candidato! ¡Bravo en tu búsqueda y arregla a Cheeso, buena persona! – clamchoda

1

esto parece fijo en las veapicore.js como de al menos v7.0/7.0.20130619132259.11

Cuestiones relacionadas