Thursday, 19 January 2017

React-ing to the need for a modern MapGuide viewer (Part 10): The end goal of this refactoring

This post is a little backstory as to why I needed to do some behind-the-scenes refactoring.

It is so we can finally bring across this important feature from Fusion and cross off another TODO list item in the process: Support for multiple maps!


To finally support multiple maps, we had to re-structure our redux state tree so that:

1. All map-specific state (selections, layer/group toggling state, current view/scale, etc) now exist in individual branches under the mapState reducer. Each branch is keyed on the name of the respective runtime map.



2. A new active map name property is added to the configuration state branch. We then simply change our respective mapStateToProps implementations in these components to interrogate this new property to determine which map state sub-branch to read their required map state from. The beauty of this is that the state that these redux-aware components pass down to child components is mostly un-changed. They are all insulated from having to know anything about the current map.



3. To service the MapMenu component, we store the array of available map names along side the new active map name property. The entries in the MapMenu component are bound from this array. Any change from this component dispatches an action to update the active map name, and all map-related components will then auto-magically update and re-render themselves to reflect the state of the new active map.



With this set up we get a nice encapsulation of map state as everything map-related now drives off of the active map name (that the MapMenu component has the sole responsibility of toggling), as best demonstrated by this animation below


The above animation demonstrates that map-specific state like selections, layer/group visibility, current view, external base layers, etc are all perfectly isolated within their respective redux state branches and the mere act of switching maps is just deciding which particular state branch we consider to be the "active" map. It is not possible for states in other maps to "bleed over" into the current map. We never got this kind of encapsulation or safety guarantees with the current Fusion implementation and no doubt is the cause of many bugs with Fusion and AppDefs with multiple Map Groups.

There's still some aspects of the viewer that still need verification under a multi-map configuration, but for the most part this is working pretty well.

Wednesday, 11 January 2017

React-ing to the need for a modern MapGuide viewer (Part 9): It's like a Christmas tree!

After figuring out how to make an accordion and getting the 0.7 release out the door, I needed to do some necessary refactoring of how we're currently modelling application state in redux to make my intended marquee feature of the next release possible.

After the refactoring, I saw some noticeable sluggishness in the viewer across all templates. This is where my choice to build this viewer on top of React has been validated (once again for the umpteenth time!), as it comes with top-notch developer tool support. So I flicked over to the React Developer Tools, activated update tracing and lo and behold ...


The viewer lit up like a Christmas tree, without any user interaction! What this was showing was that some React component was constantly updating and re-rendering itself and the sluggishness I was experiencing was in part due to that.

So it was clear that the refactoring caused some component to constantly be updating. But what?

That's where I turned to a new feature in the latest 15.4 release of React, which is by simply appending react_perf to the query string


It activates performance profiling in React itself. I then switched to the Timeline tab in the Chrome devtools and recorded 5 seconds of this "idle" activity


And we see there is activity that corresponds to this constant updating/re-rendering. If we drill down to one of these hot spots.


We finally identify the culprit:

  • It is the map viewer component
  • Something is causing the busy count to constantly go up and down. We store busy count as redux state so that the Navigator (aka. Zoom slider) component can listen on to determine whether to show/hide the "busy" animating indicator at the top of the component
The incrementBusyWorker and decrementBusyWorker functions are registered handlers for the imageloadstart and imageloadend events provided by OpenLayers MapGuide image sources. The only way these events would constantly fire from OpenLayers would be if something was constantly refreshing the map.

Which meant the possible suspect location is most likely in the component's componentWillReceiveProps lifecycle hook function which we use to interact with OpenLayers in response to component prop changes.


I stuck a breakpoint here to see if it was being hit while "idle", and it did!


While paused in this breakpoint, I evaluated the above two expressions to see what was being compared

And there was the problem! My visibility difference check was insufficient as it only did a shallow object reference comparison. The possible cause for this behaviour? My refactoring and over-zealous use of the new object spread operator in TypeScript 2.1 for shallow state cloning in my various redux reducers exposed and brought this problem into the light.

The fix was two parts. Firstly, to replace these (obviously incorrect) checks with helper functions that not only check the references (a null/undefined to set reference transition or vice versa is still a legit difference), but the actual object properties themselves.

Secondly, the layerGroupVisibility prop was a nested object prop that was being created in our component's connect function. This is actually a bad thing, which upon further reading makes perfect sense as returning new objects made in connect() will break the shallow object comparison that redux will do against a previous connect-invocation to short-circuit unnecessary component re-rendering. The fix here is to flatten this particular prop by replacing the layerGroupVisibility prop with the 4 individual array props and update all prop usages/references in the component code accordingly.

With this change in place, turning on React update tracing again shows that it no longer flashes around like a Christmas tree.


So what were the lessons learned here?
  • Component props and state, and how you structure and compare them are important when it comes to performance. The endless articles out there on the art of properly implementing shouldComponentUpdate underscores how important this fact is.
  • On the same vein, Redux has its own set of best practices for performance. This is good reference.
  • React's developer tooling is top-notch, and allows for performance issues like this one to be easily identified and debuggable.
Now to make sure this major refactoring didn't regress any other stuff.

Saturday, 7 January 2017

Announcing: mapguide-react-layout 0.7.1

Here's a quick bug fix release to address the following issues:

The base layer switcher now has a "NONE" option just like its Fusion counterpart


Thanks to some insights from the Blueprint devs, the Accordion component in the Slate and Maroon templates now initially shows the Task Pane when loaded as I had originally intended.

Finally, the scale display dropdown (shown when you have tiled maps) should now properly work when selecting a fractional scale.

Oh, and here's something I left out of the new features for 0.7. The viewer options now actually does something instead of being an empty placeholder. You can use it to toggle feature tooltips on/off should you not have a MapTip present in your toolbar.


Download

Tuesday, 3 January 2017

Announcing: mapguide-react-layout 0.7

It's a new year and the releases keep on coming!

Here's a summary of changes since the last release

Blueprint as UI foundation

I've adopted Blueprint as the UI foundation for mapguide-react-layout. This gives us a rich and cohesive toolbox of various React UI components and key components that allow us to port over the remaining Fusion templates across. Various UIs such as QuickPlot, Measure, Selection Panel, etc have got the Blueprint facelift.

 


All Fusion Templates available

Thanks to components provided by Blueprint, we now have the key ingredients to bring across the remaining Fusion templates.






Other Changes

The BasemapSwitcher widget has been ported over


An initial cut of the Geolocation widget has also been ported across


The scale display is also editable


For tiled maps, this turns into a dropdown


Also the zoom slider, it better reflects reality in terms of positioning, especially for tiled maps.


And much more is available from the release notes from the download link below.

Download

Announcing: mapguide-rest 1.0 RC5

Here's a long awaited new release of mapguide-rest.

This release is PHP 5.6 compatible (so it will work with MapGuide Open Source 3.1, that bundles PHP 5.6) and includes various fixes that can be found in the referenced change log

Download

So ... what's stopping me from putting an end to these endless 1.0 RC releases?

I need to answer (and commit to it) the age old question of: What is the best strategy for versioning REST APIs? Currently, I'm leaning towards the "put the version in the URL" approach. If you have played around with the mapguide-rest addin for Maestro, that is what the "v1" in the url represents when you connect to mapguide-rest from Maestro.


The bits I still have to figure out is once the "v1" API is set in stone, how do we evolve the API afterwards. I need a solid plan for post-v1 API evolution in place before I can slap the 1.0 final label.

Thursday, 29 December 2016

About that grand plan of 2016 ...

Not all plans go as intended. In my case, it was my grand plan for this year.

If you thought this was the mapguide-react-layout project, then I'm afraid you were mistaken. It was actually something else entirely (I did stress in that post that the project won't be MapGuide-specific). However, due to external circumstances, this plan never and probably will not materialize because I no longer see the need for it.

So since I don't see this grand plan going ahead in the near future, I might as well talk about what it was going to be. Basically, I was wanting to make a Cesium-based replacement for the Google Earth desktop application, using the stack of:

Why did I want to build this application (which strangely sounds like some chemistry/physics experiment, what's with these projects co-opting their terminology?). I wanted a replacement for Google Earth. 

Google Earth was good for only one thing: Visualizing KML files referenced against good quality satellite imagery.

It was absolutely horrible in all other aspects:
  • I can't mash KML together other geospatial data sources (SHP files, GeoJSON, etc)
  • KML is a horrible data interchange format not helped by the fact that Google Earth can't import/export to-and-from different geospatial data formats out of the box! You need a 10+ step data transformation pipeline involving OGR, QGIS and friends to get spatial data in and out of KML. OGR having 2 KML drivers adds an extra layer of confusion to the mix with one guaranteeing to trash any semblance of intelligence by outputting dumb KML geometry.
  • Creating KML files is a chore because of GE's cumbersome drawing tools
  • Modifying KML files is an even greater chore because GE lacks basic GIS-y tools like buffering, split, etc.
Going all in on QGIS wasn't desirable either because while it had the kitchen sink of GIS tools and functionality and rich support for countless geospatial data formats, it lacked the rich satellite imagery that GE provided and I've had a mixed experience with the various plugins available for QGIS that allowed me to drop-in OSM/Bing/GoogleMaps for real-world context when creating/editing features.

My hypothetical application was intended to be the sweet spot in between GE and QGIS and would've addressed most of these pain points with Cesium/OpenLayers doing most of the heavy lifting (Cesium has strong KML visualization capabilities and OpenLayers is the swiss army knife supporting many vector data sources and has rich editing capabilities) and having it all housed within an Electron host for a desktop experience.

But as I said, due to external circumstances, this idea has been mostly abandoned now. Those circumstances being mainly: I've found something that already addresses most of my pain points with Google Earth.

It's called geojson.io. Despite its deceptive name^ it solved most of my grievances with Google Earth:
  • I can bring in KML files and can easily visualize its geographic form
  • It provides serviceable tools for easily editing these geographic features. Creating new features is similarly dead simple. As these loaded features are now GeoJSON I can also easily hand-edit the GeoJSON itself if required.
  • Once done, I can export the data back out as KML
  • The provided mapbox vector/satellite layers provide a decent backdrop for geographic reference when drawing new features or editing existing ones.
So here's something that did 80-90% of what I needed from Google Earth without any of the pain and hassle of using Google Earth itself. The remaining percentages can be easily filled out with QGIS for editing beyond simple tracing of polygons and using GeoJSON or SHP as the interchange format (instead of KML) when I need to sling data between geojson.io and QGIS. The only time I needed to touch KML was when I am importing it into geojson.io or exporting it out. As a result, there was no longer such a need for this hypothetical replacement for Google Earth. I had what I wanted.

Now having said that, this wasn't ultimately all for nothing. I did get some fruits from this aborted plan. The mapguide-react-layout project is one (it uses most of the same technologies that I had originally intended to use for this Google Earth replacement). Another project (which I'll reveal in due course) was also born from the initial exploration phase of this idea.

But hey, if you think we could use an Electron/Cesium-based replacement for the Google Earth desktop application, by all means feel free to take this idea and run with it.

^ Seriously, I had known about the existence of this site for ages, but because of the name of the site I always assumed it only dealt in GeoJSON and glossed over the fact that I could import in KML and export back out as KML. This was the game-changing feature for me where one of my job workflows was having to produce KML files for certain geographic areas and the discovery of this feature made me scrap this grand plan. May I suggest a different name? Like ... GeoFiddle? That's what they call all online data playgrounds.

Friday, 23 December 2016

React-ing to the need for a modern MapGuide viewer (Part 8): I figured out how to make an accordion

Previously, I wrote about how the introduction of Blueprint as our UI toolkit gave us a nice solid set of UI components that not only replaces a whole bunch of existing (and disjoined) react components, but also provides all the necessary components to port over all of the remaining Fusion templates across.

Well that statement was half true, we got 3 of the 5 Fusion templates ported across. The remaining templates (Slate and Maroon) required an accordion-like component to dock the Legend/TaskPane/SelectionPanel in its respective sidebar.

Sadly, the Blueprint toolkit did not offer an accordion component out of the box. But based on some encouraging words, I set out to build my own accordion using the Collapse component as the foundation. A few hours later, I got a basic accordion component working, except I ran into that age old problem of how to get an element to fill 100% of its available height.

What this meant was that the "expanded" content needed either a fixed height (not acceptable) or some way where we can pre-calcuate the maximum available height for the "expanded" content when rendering the "expanded" content in the accordion. Fortunately, it turns out that there was a component that can do this legwork for us, it's called react-measure and using it was dead simple: Just wrap it around the component you need the height for and it will automagically calculate it for you.

And with that, we got a functional accordion to house our various components.


And if the look of that accordion looks familiar, you're right. Because now that we have a functional accordion, we can (and have) ported across the remaining Fusion templates. So say hello to the fully ported set of Fusion templates for mapguide-react-layout.

Slate


Maroon


Aqua


TurquoiseYellow


LimeGold



Expect a new release of mapguide-react-layout in the New Year with these Fusion templates and many other goodies in store.