¿Cómo get correctamente el cuadro delimitador usando LocationRect.fromLocations () cuando las ubicaciones abarcan 180º meridiano?

Estoy usando el "control" de v7 Bing Maps Javascript (no sé por qué se llama "control" …). Estoy llamando a Microsoft.Maps.Map.setView({bounds: bounds}) y no está funcionando como esperaba o deseo.

Tengo un set 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 a setView() , el map se aleja waaaaaay .

enter image description here

¿Por qué? y cómo evitarlo?


Esta página 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 map que no tiene el meridiano 180 a la vista, no ocurre nada. Si hace doble clic cuando el map muestra el meridiano 180º, el map se restablece al nivel de zoom 1.

Miré en esto.

En particular, miré en veapicore.js, versión 7.0.20120123200232.91, disponible en

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

Este module se descarga cuando incluye el control de maps bing, como este:

 <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 function MapMath.locationRectToMercatorZoom (una function 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 function es utilizada por la function 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 set 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 set 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

Solucioné 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 deben llamarse 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 function 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 path 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 function calcula el máximo y mínimo de ese nuevo set de valores.

El resultado es dos cuadros delimitadores: uno con longitudes que van de -180 a +180 y otro con longitudes que van de 0 a 360. Estos cuadros tendrán diferentes anchos.

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 set de puntos que es más grande que la mitad de la Tierra.

Un ejemplo de uso podría verse así:

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

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

Hacer doble clic en el map nunca causa el efecto de alejamiento del zoom , como se vio en http://jsbin.com/emobav/2

Tal vez un enfoque mucho más simple.

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

LocationRect.fromLocations acepta una list de ubicaciones / array

Su polígono que ha envuelto a Australia tiene una function para devolver una matriz de ubicaciones, llamada getLocations () .

Parece que uno podría llamarlo así (syntax de verificació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 map, saca el map. Esto tiene mucho sentido porque solo ha agregado un manejador de events al map y establece los límites a los límites del map 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); 

Creo que necesitaría agregar su manejador de clics al polígono para extender la vista a un polígono específico.

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

Luego en su doble clic de devolución:

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

esto parece corregido en el veapicore.js como mínimo de v7.0 / 7.0.20130619132259.11