So, this is where it all comes together… first, here is a shot of the app showing 2000+ wildfires from 2012
Recall from Part 4 that the heart of this application is showing wildfires from Geomac.gov. Their main map service (http://wildfire.cr.usgs.gov/ArcGIS/rest/services/geomac_dyn/MapServer) breaks out the fires across a number of layers.
- Current fires (aka “burning”) are in layer 0, with the associated perimeters (if any) in layer 1.
- All fires for the current year (active and inactive) are in layer 4.
- All perimeters from all previous years, are in layer 5,
- Layers 7 through 17 hold historic fires (points) from 2012 back to 2007.
In our app we want to have the following options:
a) View Current Fires and Perimeters
b) View Historic Fires, by year, back to 2005.
The layout of this service is pretty good for some uses, but for our app it would have been much easier if they had all the wildfires in one layer, and all the perimeters in another. Then switching between “years” would simply mean changing the “definition query” (essentially a “where-clause”) used when requesting the data. But, that’s not the case, and we don’t control the data, so we have to live with it.
Additionally, the cartography likely makes sense for fire fighters, but it’s not too great for the public, so using the MapServices directly is not going to work for us. We need more control.
In order to keep things separated, I put all the logic for fetching the data into FireManagerModule.js, and it’s job is to listen to one event: ‘FireLayer:ConfigChanged’.
That event is raised from the HistoricFiresModule when an item is clicked, and essentially we pass over the following structure:
The FireManagerModule, then takes this and the fun begins. The simplest thing would be to just load up the layer as a feature layer and assign a custom renderer, but we are trying to be decoupled here and if we wanted to swap out the map to Leaflet, we’d have a bunch more hoops to jump through to “teach” Leaflet how to work with a feature layer. So, we will use jQuery to fetch all the features into an array and throw that to the map and let it sort out how to handle things.
Sounds good right? Except the ArcGIS server can return a maximum of 1000 records. Hmmm… that makes it more interesting. The basic process boils down to this:
- call query on the layer specifying to only return the Ids (no maximum on this)
- loop over the id’s in set batches, and query to get the features
- accumulate the features in an array
- handle the fact we launch multiple async requests, and need to do something when they have ALL completed
- raise the Map:ShowFires event, and pass along the array of features
Sounds much worse than it is, and I’m sure the current implementation could use some love, but it’s working. Feel free to hack at this jsfiddle which is doing much the same thing, or fix it up and hit me with a pull request.
When the EsriMapModule gets the array of features, the next step is to stuff it into the map. This is where the esri.layers.FeatureLayer’s constructor overload really helped. Usually a FeatureLayer is connected to a FeatureService, but that’s not always needed – you can construct a FeatureLayer by just padding it a FeatureCollectionObject, which has a definition of the layer (attributes, renderer etc), and a featureSet. Conveniently a featureSet is an array of Features. Shazam! Ok, this is not very “backboney”, or “marionetteish”, but stuffing a collection in the middle of this was only going to make things more complex.
To get this done, I created a function that would return the layer Definition object:
With that in hand, I could just setup the rest of the layer like so:
Adding Year into the Router
This was more complex than I’d hoped, and I’m not thrilled with the current solution, so I’ll likely continue to hack on this over time. Anyhow – the easy part was adding an optional parameter to the route, which allowed the app to handle urls like
What was more complex was working out how to “set” the year in the router, and then how to correctly “re-hydrate” the map to the specified “state”.
I ended up having the router listen for the FireLayer:ConfigChanged event, as that’s what’s used to inform the rest of the application about a change in what’s shown on the map, and then the router held on to this value.
To get the map to re-hydrate correctly, I needed to wait until the map was loaded, and then raise the appropriate events. It was easy enough to add a Map:Loaded event into the mix, and have the router listen for that, and then, if it had values parsed from the url, raise the needed events as shown below…
The app is now live over at github, so here are some example Urls to play with:
High Park fire near Fort Collins, in 2012 http://dbouwman.github.com/geomacmapper/#map/-105.3/40.65/10/2012
Border 127 fire near San Diego in 2007 http://dbouwman.github.com/geomacmapper/#map/-116.82/32.69/11/2007
A Word about Collections
In looking back over this series, one thing that I did not cover was working with Backbone collections – specifically how they work with GET/PUT/POST/DELETE HTTP services. Since we did not have such a service, it was going to be a hack to work it into the mix, but if you are interested, here are some links to posts which cover this.
I learned a couple conceptual level things along the way which I’ll be sure to plan for moving forward. Here they are in no particular order…
Make a List of Events
This app is right at the edge of something that you can keep in your head easily. Moving forward, I’ll be keeping a list of the events and a description of what they are supposed to do in the project.
Plan your Routes
Plan ahead for how you are going to handle the router, and holding the context of the map in the url. When I first added the router, I just stored the center of the map and the zoom level, which was easy to tie into things as the map is all cleanly separated, and it’s easy to raise events from the map and use that to update the url. Adding in the “Year” was troublesome in that the value of the year is set as a result of selecting a historical year to view. Ok, not too bad for updating the url via Router.navigate, but parsing out the values and correctly restoring the map state was more complex than I’d have liked.
This also came up at the end. I had planned to show another “window” with details about a wildfire, which would be relatively simple – EXCEPT – the attributes for the current fires layer are DIFFERENT from those on the historical fire layers, which borked things up royally as all the rest of the code assumes that all the wildfire layers are the same. This certainly could be worked around, but since no one is paying for this app, I let it ride with a mouse-over that shows the fire name. I have some ideas about how to harvest all the data into a consistent format, hosted on ArcGIS Server 10.1 and then use some of the new Stats options to show interesting info about the data, but that will have to wait for a while.
The code for this drop is tagged v0.0.6 on github
and the latest version can be found at http://dbouwman.github.com/geomacmapper/