In the last post, we’ve extended the ngCesium module to have some extra functionality and viewer items. We’re now ready to extend our module with a bit more complex and relevant modules to see the benefit of combining angular and cesium.
As promised, we’re going to develop a drawing module, that will enable us to draw a polygon. Since it’s a bit more complex (and at the moment abstract) let’s plan ahead.
Our module would need to do the following:
- Expose a method that would allow our users to start a drawing session.
- Expose a method that would allow our users to end a drawing sessions.
- Allow the user to send some options. For this demo, we’ll send the obvious – polygon color.
- Return a promise to the sending function to notify when the polygon is complete every time we complete a polygon.
- Resolve the promise when we end the polygon drawing session.
I believe we cover the basics here, so let’s start implementing them.
We use the same construct as before: a module that is dependent on ngCesium. Inside we will have our entry point directive and a factory. Our factory will store the ngCesiumInstance and will store itself inside the ngCesiumInstance. Its prototype will also hold our API functions: startDrawing and stopDrawing.
It should already be familiar to you, so here it is – the code of the skeleton:
The ngCesiumPolygonDrawer module is at the bottom.
Now let’s start get messy: the ngCesiumPolygonDrawer has some properties we would like to setup in the constructor. More to the point – it has to keep an entity that will build upon, as well as a Cesium event handler. So we’ll just add it to the constructor:
// constructor function cesiumPolygonDrawerService(ngCesiumInstance){ this.ngCesiumInstance = ngCesiumInstance; ngCesiumInstance.cesiumPolygonDrawer = this; // initialize the polygon's positions - we will add more points as we draw this.positions = []; // create the polygon entity var options = { id: 'cesium drawing polygon', polyline: { show: true, positions: ngCesiumInstance.setCallbackProperty(that.positions) }, polygon: { show: false, hierarchy: ngCesiumInstance.setCallbackProperty(that.positions) } } this.polygonEntity = ngCesiumInstance._viewer.entities.add(options); // setup the events handler this.eventsHandler = ngCesiumInstance.getEventHandler(); };
Notice there are 2 new methods to the ngCesium module: setCallbackProperty and getEventHandler. These 2 methods are wrappers for methods that are a part of Cesium, but they make it easy for us to develop and extend our ngCesium without knowing too much about Cesium. You can read the extra info box below in order to learn more about these functions, but the important thing is that they return an instance of the CallbackProperty and the ScreenSpaceEventHandler already setup to work with our viewer.
Extra info: CallbackProperty. In Cesiumjs there’s a useful method to set properties asynchronously, on every cesium “cycle”. It’s called: CallbackProperty. The CallbackProperty can be used for a lot of things, but here we are going to use it in order to “listen” to changes in our positions array.
Extra info: ScreenSpaceEventHandler . ScreenSpaceEventHandler handles the events happening on the cesium canvas. With it we can capture events like mouse click, mouse move and so on, and act upon them.
So now we have a positions array that we will update, and the CallbackProperty is going to take care of the sync. every cesium cycle.
In addition, I’ve setup an events handler, to capture the events we need: click (to setup new points), move (to draw a polyline as we move from one point to the next) and double click (to notify we need to close the polygon).
These events will be set in the startDrawing method and will be destroyed in the stopDrawing method:
cesiumPolygonDrawerService.prototype = { // this starts the drawing on the cesium viewer startDrawing: function(options){ // setup event listeners this.eventsHandler.setInputAction(addPolylinePoint, Cesium.ScreenSpaceEventType.LEFT_CLICK); this.eventsHandler.setInputAction(updatePolyline, Cesium.ScreenSpaceEventType.MOUSE_MOVE); this.eventsHandler.setInputAction(closePolygon, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); }, endDrawing: function(){ // destroy event listeners this.eventsHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); this.eventsHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE); this.eventsHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } }
The startDrawing function needs 2 more things:
The first is to return a promise. Angularjs has a wonderful $q service, which returns a promise with 3 main features: reject (for errors), resolve (for completion) and notify (like resolve, but without “destroying” the promise). We’ll add it to the startDrawing function: that.deferred = $q.defer();
The second thing is handling the options our user sends to the startDrawing method. As mentioned above, in this demo, we’ll allow the user to send the polygon’s color. So we’ll just add a simple line: that.polygonEntity.polygon.material = options.color;. In the example I wrap it with an if clause in order to validate the input.
Now we can start filling up our events functions:
addPolylinePoint – would add a point every time we click.
updatePolyline -would add a temporary point every time we click.
closePolygon – would close the polygon and notify the promise.
Here’s the full code, documented:
You can play around with the app. Just some ideas: do something with the polygon in the notify or resolve functions (hint: you would need to clone it, because it is a reference to the drawer’s polygon), or extend the options one can send to the drawing module, or maybe communicate between two cesium instances (in part 3, the last example shows how each cesium instance is separated from the other).
As usual, if you need any clarification or have any question – don’t hesitate to use the form below.
Is there a github repository with the code you describe in your posts?