2012-03-02 13 views
5

Sto usando il "controllo" Javascript di Bing Maps v7 (non so perché si chiami "controllo" ...). Sto chiamando Microsoft.Maps.Map.setView({bounds: bounds}) e non funziona come mi aspetterei o desidererei.Come ottenere correttamente il riquadro di delimitazione utilizzando LocationRect.fromLocations() quando le posizioni coprono il 180 ° meridiano?

Ho un set di poligoni con punti che coprono il 180 ° meridiano. Un esempio è il confine tra le isole della Nuova Zelanda - alcune sono ad ovest del 180 ° meridiano, alcune parti (Chatham ISlands) sono ad est.

Quando creo un poligono con quei limiti e chiamo setView(), la mappa ingrandisce waaaaaay out.

enter image description here

Perché? e come evitarlo?


This page fornisce una dimostrazione del problema.

Ecco il codice.

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

Se si fa doppio clic su una mappa che non ha il 180 ° meridiano in vista, non accade nulla. Se si fa doppio clic quando la mappa mostra il 180 ° meridiano, la mappa si ripristina al livello di zoom 1.

risposta

11

Ho esaminato questo.

In particolare ho guardato in veapicore.js, versione 7.0.2012.91, disponibile presso

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

Questo modulo viene scaricato quando si include il controllo Bing Maps, in questo modo:

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

Ho trovato quello che penso siano due problemi distinti.

• La funzione MapMath.locationRectToMercatorZoom (una funzione interna, non destinata all'uso diretto da parte delle applicazioni) restituisce sempre uno zoom di 1 quando il riquadro di delimitazione di LocationRect si estende sul 180 ° meridiano. Questo non è corretto Questa funzione è utilizzata dalla funzione Map.setView() per impostare automaticamente lo zoom, che si traduce nel fenomeno di zoom waaay out.

• LocationRect.fromLocations() utilizza un approccio ingenuo per determinare il riquadro di delimitazione per un insieme di posizioni. In effetti non è garantito che sia un "riquadro di limitazione minimo" o un "rettangolo di limitazione minimo". La scatola restituita da quel metodo non copre mai il 180 ° meridiano, per quanto posso dire. Ad esempio, LocationRect restituito per un insieme di località che rappresentano i confini delle isole della Nuova Zelanda, inizierà alla latitudine di -166 e si estenderà verso est fino al meridiano + 165 °. Questo è semplicemente sbagliato.

Ho risolto questi problemi eseguendo il patch-patch del codice in 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); 
    }; 
} 

Queste funzioni devono essere chiamati volta prima dell'uso, ma dopo il caricamento. Il primo viene caricato con il ritardo, penso, quindi non è possibile eseguire la patch delle scimmie sul documento pronto; è necessario attendere fino a dopo aver creato uno Microsoft.Maps.Map.

Il primo fa la cosa giusta, dato un LocationRect. Il metodo originale inverte i bordi est e ovest nei casi in cui il rettangolo si estende sul 180 ° meridiano.

La seconda funzione corregge il metodo fromLocations. L'implementazione originale itera su tutte le posizioni e prende la longitudine minima per essere la "sinistra" e la longitudine massima per essere la "giusta". Questo fallisce quando la longitudine minima è solo ad est del 180 ° meridiano (diciamo, -178), e il valore di longitudine massima è solo ad ovest della stessa linea (per esempio, +165). Il riquadro di delimitazione risultante dovrebbe estendersi sul 180 ° meridiano, ma in realtà il valore calcolato utilizzando questo approccio ingenuo va ben oltre.

L'implementazione corretta calcola tale casella e calcola anche un secondo riquadro di delimitazione. Per la seconda, anziché utilizzare il valore di longitudine, utilizza il valore di longitudine o la longitudine + 360, quando la longitudine è negativa. La trasformazione risultante modifica la longitudine da un valore compreso tra -180 e 180, in un valore compreso tra 0 e 360. Quindi la funzione calcola il massimo e il minimo di quel nuovo insieme di valori.

Il risultato sono due riquadri di delimitazione: uno con lunghezze che vanno da -180 a +180 e un altro con lunghezze che vanno da 0 a 360. Queste caselle saranno di larghezze diverse.

L'implementazione fissa sceglie la casella con la larghezza più stretta, facendo un'ipotesi che la casella più piccola sia la risposta corretta. Questa euristica si interromperà se stai cercando di calcolare il riquadro di delimitazione per un insieme di punti che è più grande della metà della terra.

Un esempio d'uso potrebbe assomigliare a questo:

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

Questa pagina dimostra: http://jsbin.com/emobav/4

Facendo doppio click sulla mappa non fa sì che il zoom mooolto fuori effetto, come si è visto in http://jsbin.com/emobav/2

2

Forse un approccio molto più semplice.

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

LocationRect.fromLocations accepts a list of locations/array

poligono che si è avvolto intorno l'Australia detiene una funzione per restituire una serie di località, di nome getLocations().

Sembra che lo si possa chiamare così (doppia sintassi di controllo);

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

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

Non funziona quando si estende il 180? Non vedo perché non lo farebbe perché utilizza solo i punti del poligono. Se è così, la seguente potrebbe essere una soluzione molto semplice per te.

Si dice che ogni volta che si fa doppio clic sulla mappa, viene visualizzata la mappa. Questo ha un senso totale, perché è stato aggiunto solo un gestore di eventi per la mappa, e impostare i limiti ai confini della mappa nel codice seguente:

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

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

Vorrei pensare che si avrebbe bisogno di aggiungere il gestore click al poligono per estendere la vista a un poligono specificato.

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

Poi, nel tuo 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

* Questo non funziona quando si attraversa il 180? Non vedo perché non dovrebbe ... * Chris, non funziona, perché c'è un bug nella libreria delle mappe di Microsoft. Leggi cosa ho scritto nella mia risposta. È facile dimostrare il bug passando un qualsiasi insieme di punti che si estendono su 180. L'algoritmo che usano per calcolare il riquadro di delimitazione o il centro è ingenuo. Leggi la mia risposta, ho spiegato tutto questo. – Cheeso

+0

@Cheeso * La seconda funzione corregge il metodo fromLocations. * Spiacente, non ho capito che quello diLocations era anche buggato durante la lettura della prima volta. A male perché questo avrebbe reso getLocations() un ottimo candidato! Bravo alla tua ricerca e fissa Cheeso, bravo! – clamchoda

1

questo sembra fissato nelle veapicore.js come di almeno V7.0/7.0.20130619132259.11

Problemi correlati