2012-03-02 6 views
5

Używam "kontroli" v7 Bing Maps Javascript (nie wiem, dlaczego nazywa się to "kontrola" ...). Dzwonię pod numer Microsoft.Maps.Map.setView({bounds: bounds}) i nie działa tak, jakbym oczekiwał lub pragnął.Jak poprawnie uzyskać obwiednię za pomocą funkcji LocationRect.fromLocations(), gdy lokalizacje są na 180-tym południku?

Mam zestaw wielokątów z punktami, które obejmują 180. południk. Przykładem jest granica wysp Nowej Zelandii - niektóre z nich znajdują się na zachód od 180. południka, niektóre porcje (Wyspy Chatham) znajdują się na wschodzie.

Po utworzeniu wielokąta z tymi granicami i wywołaniu setView(), mapa powiększa waaaaaay na zewnątrz.

enter image description here

Dlaczego? i jak tego uniknąć?


This page stanowi demonstrację problemu.

Oto kod.

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

Po dwukrotnym kliknięciem na mapie, która nie ma 180. południk w widoku, nic się nie dzieje. Jeśli klikniesz dwukrotnie, gdy mapa wyświetli 180-ny południk, mapa zostanie zresetowana do poziomu powiększenia 1.

Odpowiedz

11

Zajrzałem do tego.

W szczególności Zajrzałem veapicore.js, wersję 7.0.2012.91, dostępnego na

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

Moduł ten jest pobierany, gdy obejmuje kontrolę mapy Bing, na przykład:

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

Znalazłem to, co moim zdaniem to dwa różne problemy.

• Funkcja MapMath.locationRectToMercatorZoom (funkcja wewnętrzna, nieprzeznaczona do bezpośredniego użycia przez aplikacje) zawsze zwraca zoom o wartości 1, gdy pole ograniczające LocationRect obejmuje 180. południk. To jest niepoprawne. Ta funkcja jest używana przez funkcję Map.setView() do automatycznego ustawiania zoomu, co powoduje zjawisko wygaszenia zoomu.

• Funkcja LocationRect.fromLocations() używa naiwnego podejścia do określania obwiedni dla zestawu lokalizacji. W rzeczywistości nie ma gwarancji, że będzie to "minimalny prostokąt ograniczający" lub "minimalny prostokąt ograniczający". Skrzynka zwrócona z tej metody nigdy nie rozciąga się na 180-ym południku, o ile wiem. Na przykład funkcja LocationRect zwrócona dla zestawu lokalizacji reprezentujących granice wysp Nowej Zelandii rozpocznie się od -176 szerokości geograficznej i rozciągnie się na wschód, aż do + 165. południka. To po prostu źle.

Naprawiłem te problemy, kodując małpy kod w 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); 
    }; 
} 

Funkcje te muszą być nazywane raz przed użyciem, ale po załadowaniu. Pierwszy wydaje się być opóźniony, więc nie możesz zrobić łatania małpy na gotowym dokumencie; musisz poczekać, aż utworzysz Microsoft.Maps.Map.

Pierwszy z nich robi to, co właściwe, biorąc pod uwagę parametr LocationRect. Oryginalna metoda odwraca wschodnie i zachodnie krawędzie w przypadkach, gdy prostokąt obejmuje 180. południk.

Druga funkcja służy do ustalenia metody fromLocations. Oryginalna implementacja iteruje we wszystkich lokalizacjach i przyjmuje minimalną długość geograficzną jako "lewą" i maksymalną długość geograficzną jako "prawo". Dzieje się tak, gdy minimalna długość geograficzna znajduje się na wschód od 180. południka (powiedzmy -178), a maksymalna długość geograficzna jest na zachód od tej samej linii (powiedzmy +165). Wynikowa ramka ograniczająca powinna obejmować 180-y południk, ale w rzeczywistości wartość obliczona przy użyciu tego naiwnego podejścia jest bardzo duża.

Poprawiona implementacja oblicza to pole, a także oblicza drugą ramkę ograniczającą. W przypadku drugiego, zamiast używać wartości długości, używa ona wartości długości lub długości geograficznej + 360, gdy długość geograficzna jest ujemna. Wynikowa transformacja zmienia długość geograficzną z wartości z zakresu od -180 do 180 na wartość z zakresu od 0 do 360. Następnie funkcja oblicza maksymalny i minimalny nowy zestaw wartości.

Wynikiem są dwie ramki ograniczające: jedna o długościach od -180 do +180, a druga o długościach od 0 do 360. Te pola będą miały różne szerokości.

Ustalona implementacja wybiera pole o węższej szerokości, dzięki czemu można się domyślić, że mniejsze pole jest poprawną odpowiedzią. Ta heurystyka złamie się, jeśli próbujesz obliczyć obwiednię dla zbioru punktów, który jest większy niż połowa ziemi.

Przykładem użycia może wyglądać następująco:

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

Ta strona pokazuje: http://jsbin.com/emobav/4

Podwójne kliknięcie na mapie nie powoduje zoom waaay się efekt, jak odnotowano w http://jsbin.com/emobav/2

2

Być może o wiele prostsze podejście.

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

LocationRect.fromLocations accepts a list of locations/array

Twój wielokąta, które zostały owinięte wokół Australii posiada funkcję powrotu szereg miejsc, nazwanych getLocations().

Wygląda na to, że można to tak nazwać (składnia podwójnej kontroli);

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

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

Czy to nie działa przy rozpiętości 180? Nie rozumiem, dlaczego tak by nie było, ponieważ używa po prostu punktów z wielokąta. Jeśli tak, poniższe rozwiązanie może być bardzo proste.

Po dwukrotnym kliknięciu mapy powiesz, że patrzy na mapę. To sprawia, że ​​całkowity sens, ponieważ masz tylko dodania obsługi zdarzeń na mapie, a następnie ustawić granice do granic mapie w poniższym kodzie:

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

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

Myślę, że trzeba by dodać kliknij obsługi do wielokąta, aby rozciągnąć widok do określonego wielokąta.

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

Następnie w 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

* Czy to nie działa, gdy obejmujących 180? Nie rozumiem, dlaczego nie byłoby ... * Chris, to nie działa, ponieważ jest błąd w bibliotece map Microsoftu. Przeczytaj, co napisałem w mojej odpowiedzi. Łatwo jest wykazać błąd, przekazując dowolny zestaw punktów obejmujący 180. Algorytm, którego używają do obliczenia obwiedni lub centrum, jest naiwny. Przeczytaj moją odpowiedź, wyjaśniłem to wszystko. – Cheeso

+0

@Cheeso * Druga funkcja naprawia metodę fromLocations. * Przykro mi, nie zauważyłem, że to zLocations było również błędne podczas czytania za pierwszym razem. Źle, bo to spowodowałoby, że getLocations() byłby świetnym kandydatem! Brawo na twoim znalezieniu i napraw Cheeso, dobrze! – clamchoda

1

wydaje się ustalone w veapicore.js jako co najmniej v7.0/7.0.20130619132259.11

Powiązane problemy