2016-08-15 9 views
2

Ich erstelle eine MapboxGL-Karte, die viel Interaktivität über Tasten und Schieberegler unterstützt.Führen Sie eine Sequenz nach einer anderen Sequenz mit RxJS

MapboxGL Karte Instanzen sind Stateful und haben temperament Lifecycles wegen viel async Abrufen von Fliesen und Quellen. Dies bedeutet, dass die meiste Arbeit in Event-Handlern erledigt wird. Das lässt mich denken, dass RxJS ein solider Kandidat für die Verwaltung all dieser Interaktivität ist.

Wenn Sie zum Beispiel einen Feature-Layer zu einer Karte hinzufügen möchten, geben Sie the mapbox example an, alles in einem map.on('load', handler) zu tun. Das ist gut für statische Karten, aber es ist mühsam, imperative Event-Handler für eine Karte zu koordinieren, die über 20 Umschalt-/Schieberegler/Steuerelemente verfügt.

Was ich tun möchte, ist Sequenzen für jede einzelne Operation zu erstellen. Die grundlegendste davon ist das Laden der Karte dann das Hinzufügen der Basis-Feature-Layer nach der Karte lädt.

Also hier ist, was ich habe, ich habe RxJS für ca. 5 Stunden insgesamt, also vielleicht gibt es etwas, ich weiß einfach nicht, dass ich nicht weiß.

Snippet:

var map = new mapboxgl.Map({ 
     container: 'map', 
     style: 'mapbox://styles/mapbox/dark-v9', 
     center: [-122.78, 38.43], 
     zoom: 10 
    }); 

    // Check every 100ms if the map is loaded, if so emit 
    var loaded$ = Rx.Observable 
    .interval(100) 
    .map(() => map.loaded()) 
    .filter(x => x) 
    .first(); 

    // We must wait until the map is loaded before attaching the feature layer. 
    var addLayer = loaded$.subscribe(
    baseMapLoaded, errorHandler, addFeatureLayerAndSource 
); 

    // Attaching the feature layer sets the map.loaded() to false 
    // while it fetches the tiles. 
    // 
    // When the feature layer and source are loaded, 
    // log 'all tiles loaded' then 'completed' to the console 
    // 
    // Why can I declare `addLayer` and `completed` like this? 
    // Shouldn't these both fire after the loaded$ emits the first time, 
    // causing an error? 
    var completed = loaded$.subscribe(tilesLoaded, errorHandler, completeHandler); 

    /* Misc Handlers */ 
    function addFeatureLayerAndSource() { 
    console.log(2, 'adding feature layer and source'); 
    map.addSource('parcel_data', { 
     'type': 'vector', 
     'tiles': ['http://localhost:5000/api/v1/tiles/{z}/{x}/{y}'] 
    }); 

    map.addLayer({ 
     "id": "parcel_data", 
     "type": "fill", 
     "source": "parcel_data", 
     "source-layer": "parcel_data", 
    }); 
    } 
    function baseMapLoaded() { console.log(1, 'base map loaded'); } 
    function tilesLoaded() { console.log(3, 'all tiles loaded'); } 
    function errorHandler(err) { console.log('error handling', err); } 
    function completeHandler() { console.log('completed'); } 

EDIT: Aktualisiert das Snippet @ CalvinBeldin Hilfe zu reflektieren. Derzeit versucht herauszufinden, warum das tatsächlich funktioniert. Die Reihenfolge meiner console.logs ist richtig:

1 "base map loaded" 
2 "adding feature layer and source" 
3 "all tiles loaded" 
completed 

Antwort

2

die beobachtbaren zurück von renderObservable.last() ist nie da renderObservable ‚s komplette Veranstaltung nie nur für render Ereignisse hören halten (renderObservable tritt Feuer gehen, bis es so angeordnet ist, in diesem Sinne wird es niemals "vervollständigt").

Sie müssen ein neues Observable erstellen, das beim letzten Rendern ausgegeben wird. Pro Einschränkung mit dem MapboxGL API kann eine Polling-Lösung ist der einzige Weg zu gehen:

// Emits the value of map.loaded() every 100ms; you can update this 
// time interval to better suit your needs, or get super fancy 
// and implement a different polling strategy. 
const loaded$ = Rx.Observable.interval(100).map(() => map.loaded()); 

// Will emit once map.loaded() is true. 
const isLoaded$ = loaded$.filter(x => x).first(); 
+0

Danke für die Hilfe! Gemäß dem MapboxGL-Issue-Tracker ist die einzige Möglichkeit, zu wissen, wann das letzte Render-Ereignis ausgelöst wurde, zu prüfen, ob 'map.loaded() === true' aus dem Ereignis-Handler 'render' stammt. Aus diesem Grund habe ich '.last()' mit der Prädikatfunktion verwendet. Wegen dieser Clogginess hoffe ich, einen einfachen Weg zu finden, das letzte der gefeuerten Render-Events zu abonnieren, damit ich es an mehreren Stellen wiederverwenden kann. –

+0

Sie könnten ein Observable einrichten, das die Map nur gelegentlich abfragt, nach dem geladen wird, wenn es wahr ist, und dann emittieren. Wird die Antwort bald mit Code aktualisieren. –

+0

Danke, das würde ich gerne sehen. –

0

ich nicht ganz verstanden, was könnte das Problem ist. Die wichtigsten Punkte:

  • Halten Sie das Load-Ereignis an die Beobachter, die nach dem Ladeereignis bereits happned abonniert ((Behavior | Replay) Thema)
  • Return Ereignisse machen erst nach Ereignis machen aufgetreten (flatMap)

loadSource$ = Observable.fromEvent(map, 'load').first(); 

// You need load$ to be a BehaviorSubject or replay last value on 
// In order to keep state of wheter laod event happend or not 
// For observers which subscribed after load event already happened 
load$ = loadSource.replay(1); 
render$ = Observable.fromEvent(map, 'render'); 
renderAfterLoad$ = load$.flatMap(() => render$); 

load$.subscribe(() => { 
    map.addSource('source_data', { 
    'type': 'vector', 
    'tiles': ['http://localhost:5000/api/v1/tiles/{z}/{x}/{y}'] 
    }); 

    map.addLayer({ 
    "id": "layer_data", 
    "type": "fill", 
    "source": "source_data", 
    "source-layer": "source_layer_id", 
    }); 
}); 

render$.subscribe(() => { 
    console.log('all tiles loaded'); 
}); 
Verwandte Themen