6 Stimmen

Wie wird das Begrenzungsrechteck mit LocationRect.fromLocations() korrekt ermittelt, wenn die Orte den 180. Meridian überspannen?

Ich verwende die v7 Bing Maps Javascript "Kontrolle" (ich weiß nicht, warum es eine "Kontrolle" genannt wird...). Ich rufe Microsoft.Maps.Map.setView({bounds: bounds}) und es funktioniert nicht so, wie ich es erwarten oder wünschen würde.

Ich habe eine Reihe von Polygonen mit Punkten, die den 180. Meridian überspannen. Ein Beispiel ist die Grenze der neuseeländischen Inseln - einige liegen westlich des 180. Meridians, andere (Chatham ISlands) östlich.

Wenn ich ein Polygon mit diesen Begrenzungen erstelle und die setView() wird die Karte vergrößert gaaaaanz aus.

enter image description here

Warum? und wie kann man es vermeiden?


Diese Seite bietet eine Demonstration des Problems.

Hier ist der Code.

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

Wenn Sie auf eine Karte doppelklicken, auf der der 180. Meridian nicht angezeigt wird, passiert nichts. Wenn Sie auf eine Karte doppelklicken, auf der der 180. Meridian angezeigt wird, wird die Karte auf Zoomstufe 1 zurückgesetzt.

11voto

Cheeso Punkte 184210

Ich habe mir das angesehen.

Insbesondere habe ich in veapicore.js , Version 7.0.20120123200232.91 , nachgeschaut, verfügbar unter

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

Dieses Modul wird heruntergeladen, wenn Sie das Bing-Maps-Steuerelement einbinden, etwa so:

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

Ich bin auf zwei unterschiedliche Probleme gestoßen.

- Die Funktion MapMath.locationRectToMercatorZoom (eine interne Funktion, die nicht für die direkte Verwendung durch Anwendungen gedacht ist) gibt immer einen Zoom von 1 zurück, wenn die Bounding Box des LocationRects den 180sten Meridian überspannt. Dies ist nicht korrekt. Diese Funktion wird von der Funktion Map.setView() verwendet, um den Zoom automatisch einzustellen, was dazu führt, dass der Zoom sehr weit außen liegt.

- Die Funktion LocationRect.fromLocations() verwendet einen naiven Ansatz zur Bestimmung des Begrenzungsrahmens für eine Gruppe von Orten. Es ist nicht garantiert, dass es sich um eine "Minimum Bounding Box" oder ein "Minimum Bounding Rectangle" handelt. Die von dieser Methode zurückgegebene Box umspannt nie den 180. Meridian, soweit ich das beurteilen kann. Das LocationRect, das für eine Reihe von Orten zurückgegeben wird, die die Grenzen der neuseeländischen Inseln darstellen, beginnt zum Beispiel am -176. Breitengrad und erstreckt sich nach Osten bis zum +165. Das ist einfach falsch.

Ich habe diese Probleme durch monkey-Parcheando des Codes in veapicore.js behoben.

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

Diese Funktionen müssen aufgerufen werden einmal vor dem Gebrauch, sondern nach dem Laden. Das erste wird mit Verzögerung geladen, also kann man das monkey-Parcheando nicht ausführen, wenn das Dokument bereit ist; man muss warten, bis man ein Microsoft.Maps.Map .

Der erste macht einfach das Richtige, wenn er ein LocationRect hat. Die ursprüngliche Methode spiegelt die Ost- und Westkanten in den Fällen, in denen sich das Rechteck über den 180.

Die zweite Funktion fixiert die fromLocations Methode. Die ursprüngliche Implementierung durchläuft alle Orte und nimmt den minimalen Längengrad als "links" und den maximalen Längengrad als "rechts". Dies schlägt fehl, wenn der minimale Längengrad gerade östlich des 180. Längengrades (z. B. -178) und der maximale Längengrad gerade westlich derselben Linie (z. B. +165) liegt. Der resultierende Begrenzungsrahmen sollte den 180. Meridian umspannen, aber in Wirklichkeit geht der mit diesem naiven Ansatz berechnete Wert in die andere Richtung.

Die korrigierte Implementierung berechnet diese Box und außerdem eine zweite Bounding Box. Für die zweite Boundingbox wird nicht der Längengradwert verwendet, sondern der Längengradwert oder der Längengrad + 360, wenn der Längengrad negativ ist. Die resultierende Transformation ändert den Längengrad von einem Wert zwischen -180 und 180 in einen Wert zwischen 0 und 360. Anschließend berechnet die Funktion das Maximum und Minimum dieser neuen Wertegruppe.

Das Ergebnis sind zwei Bounding Boxes: eine mit Längengraden im Bereich von -180 bis +180 und eine weitere mit Längengraden im Bereich von 0 bis 360. Diese Boxen haben unterschiedliche Breiten.

Die feste Implementierung wählt das Kästchen mit der geringeren Breite und vermutet, dass das kleinere Kästchen die richtige Antwort ist. Diese Heuristik versagt, wenn Sie versuchen, den Begrenzungsrahmen für eine Gruppe von Punkten zu berechnen, die größer als die halbe Erde ist.

Ein Beispiel für die Verwendung könnte folgendermaßen aussehen:

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

Diese Seite demonstriert: http://jsbin.com/emobav/4

Ein Doppelklick auf die Karte führt nie dazu, dass die weit herauszoomen Wirkung, wie sie in http://jsbin.com/emobav/2

4voto

clamchoda Punkte 3347

Vielleicht ein viel einfacheres Konzept.

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

LocationRect.fromLocations akzeptiert eine Liste von Orten / Array

Ihr Polygon, das Sie um Australien gewickelt haben, enthält eine Funktion, die ein Array von Orten zurückgibt, das getLocations() .

Es sieht so aus, als ob man es so nennen könnte (überprüfen Sie die Syntax);

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

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

Funktioniert das nicht, wenn man den 180er überbrückt? Ich wüsste nicht, warum nicht, denn es werden ja nur die Punkte des Polygons verwendet. Wenn ja, könnte das Folgende eine sehr einfache Lösung für Sie sein.

Sie sagen, wenn Sie auf die Karte doppelklicken, wird die Karte geschwenkt. Das macht durchaus Sinn, denn Sie haben der Karte nur einen Event-Handler hinzugefügt und die Grenzen der Karte im folgenden Code festgelegt:

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

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

Ich würde denken, dass Sie Ihre Klick-Handler auf das Polygon hinzufügen müssen, um die Ansicht auf ein bestimmtes Polygon zu spannen.

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

Dann in Ihrem 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});
}

1voto

rob Punkte 7557

Dies scheint in der veapicore.js ab mindestens v7.0/7.0.20130619132259.11 behoben zu sein

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X