Thursday, 29 December 2016

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.

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

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

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.

Wednesday, 14 December 2016

NOTE: This blog post was left in draft limbo for several months. Some things in this post may feel disjointed and some things are now obviously being referenced in the past tense. DISCLAIMER: This is not an authoritative Oracle installation guide. Neither is this post the opinions of someone with expert knowledge of Oracle. This is just a blogger's tale of getting an installation of Oracle XE up and running with Docker with the minimum of pain and unnecessary administration so he can easily have an Oracle XE instance available for testing with MapGuide/FDO and other geospatial software.

So before I start the release cycle for the first beta release of MapGuide Open Source 3.1, I'd thought I'd try to investigate (and try to knock off) some long standing issues with Oracle with the King FDO provider.

Naturally, this requires I have an installation of Oracle (XE at a minimum) lying around, which I don't. So that means I've to install it. Having had the displeasure of working with Oracle in the past and encountering its horrible, UI-from-the-last-millennium setup process and administrative UIs, I really didn't want to go down that path.

And in YEAR(GETDATE()), I should no longer have to install Oracle to a bare metal OS. I shouldn't even have to do it inside a clean test Virtual Machine. We have docker, and there's bound to be an Oracle docker image on the docker hub, that I can pull down, spin up a container and have an Oracle installation ready to go.

So that's what I set out to do. Since Windows is the first priority platform of focus for any MapGuide/FDO issues, I needed to get docker installed on Windows. Unlike Linux, Docker support on Windows is not first-class (they're working on that ^) and currently requires a virtualization layer (eg. VirtualBox) for docker on windows to interface with. Fortunately, docker provides the Docker Toolbox package which makes setting up Docker on Windows or Mac an easy one click installer affair with everything to get docker up and running on Windows or Mac

Docker engine

Docker machine

Docker compose

VirtualBox to interface with the barebones Linux VM hosting the docker engine

Kitematic, for downloading of docker images and spinning up of docker containers through an easy to use GUI

With docker installed, it was now a case of finding an appropriate Oracle docker image on the docker hub, and Kitematic makes this dead simple. So after typing in "oracle xe" a few images were available, I gravitated towards the one with the one with the most likes and downloads.

Clicking the Create button starts the downloading process

Once downloaded, Kitematic automatically spins up a running container for the freshly downloaded docker image. Kitematic has a Web Preview option, which I presume points to the Oracle APEX manager, which I recall from my frail memory was the web-based admin for an Oracle XE installation. I don't know what black magic Kitematic does to auto-magically know this container was web-previewable, but the fact this was available was much appreciated. So clicking the preview button opened up Chrome to what I presumed to be the URL of Oracle APEX.

Well, at this point I got an unexpected http authentication prompt. Okay, so I tried the documented login for this container with no luck. What the ...? Okay, so I cancelled the prompt and tried refreshing the URL and then it suddenly takes me to the Oracle APEX login page. Weird!

So anyways, now that I'm at the login page, I login with the given credentials and was greeted with the visual confirmation that I now had a running Oracle XE installation via docker!

Except, this is not the APEX web admin, it's actually the database home page. Turns out, if I put in this address manually in the web browser (http://192.168.99.100:32768/apex) it will take me to APEX, if I open this through the web preview option in Kitematic, it takes me to the database home page. Weird!

Still at the end of the day, I had a running Oracle XE instance. Much more pleasant than my previous Oracle installation experiences!

Now it's time to bootstrap this Oracle database with some spatial data, so in the interest of eating my own dogfood, I gave FDO Toolbox a spin to see if I can load data into the fresh Oracle XE instance. So first things first, we check the running container in Kitematic for the IP and ports of this container and the ports that are exposed.

Then I proceed to use FDO Toolbox to try and create an Oracle data store via the King Oracle provider

But then ...

No dice, I don't know what happened here. I swore recalling some time ago in the long distant past that it should've been possible to bootstrap a clean Oracle database from scratch with the King Oracle FDO provider and FDO Toolbox, but try as I might, I just couldn't get it to work this time round!

So when FDO failed, it was time to make the hot tag and bring OGR into the fold. Running the venerable ogr2ogr utility brought the SHP file data into oracle flawlessly. Except, it is excruciatingly s l o w to get anything out of this Oracle database! A basic spatial table listing from (eg. A FDO feature class listing) takes an eternity.

It was a this point I paged a resident Oracle expert and realized that one shouldn't try connecting and loading in data as the SYS/SYSTEM user as that it means we bring in EVERYTHING when doing the simplest thing like listing spatially-enabled tables from SDO_GEOM_METADATA

So I fire up SQLPlus and connect to this XE container using the documented admin login

Now when I connect with FDO Toolbox again (with emphasis on specifying my oracle user's schema, otherwise it's going to I presume, hit ALL_SDO_GEOMETRY_METADATA un-filtered, which performance-wise means we'd be back to slow square one!)

It is now connecting and performing at the performance level we are expecting.

And if we can connect to this Oracle XE database with FDO Toolbox, it means we can also connect to it with MapGuide proper, which is a case of setting up a new Feature Source, with the same connection parameters.

And from here, it's the standard MapGuide authoring process of creating layers pointing to this Feature Source, styling them up and composing them together on a map.

So what have I learned from this exercise?

Docker is just plain awesome. I didn't want to touch the Oracle installation process and I also didn't want to "taint" my host environment with an Oracle installation either. Docker made spinning up a repeatable and disposable Oracle XE environment dead simple. Even docker on windows, which requires a Virtual Machine intermediate layer is still a seamless experience thanks to the Docker Toolbox suite of integrated tools.

Either something regressed or FDO Toolbox will need some work to be able to load data into Oracle using the King.Oracle provider, which is strange as FDO is supposed to be an abstraction layer and FDO Toolbox is just simply working against the abstractions provided by the FDO API. If I have to code in Oracle-specific code paths and behaviour to get this functionality to work, then that is a sign of a leaky abstraction. I hope I don't have to do this!

Thankfully where FDO failed in this task, OGR was able to pick up the slack. FDO and OGR have this nice duality where one's strengths covers the other's weakness.

Always create a new Oracle user after install and load the spatial data into this user's schema, so you don't have to ask yourself why the default SYS/SYSTEM login/schema takes an eternity querying SDO_GEOM_METADATA tables.

Be sure to set that OracleSchema connection property in your FDO connection, even if it's a non-SYSTEM user.

^ Windows 10 and Server 2016 now have native docker hosts which can run both Linux and Windows containers. No more intermediate virtualization layer required. Being able to containerize windows applications is game changer! Older versions of Windows will still have to go the virtualization route via VirtualBox for the Windows Docker client to work.

One of the "problems" we have right now with this new viewer is that although we have got most of the foundational stuff right, the UI lacks stylistic cohesion. This is due to the viewer being a mish-mash of various react components, each with their own unique styling quirks. I really wanted a react-based UI toolkit that had a good enough baseline set of widgets/components with a unified look for building desktop-centric web applications (with an option to go mobile down the road).

Most of these UI toolkits however, take the reverse direction. They are mobile/tablet first, trying to emulate Material Design, iOS or Bootstrap and when trying to adapt such toolkits for a desktop-centric web application design, just look horribly out of place. Most of the UI toolkits that would've passed my desired criteria (like kendo, ExtJS, jQuery UI, etc) suffered from being imperative JavaScript APIs (meaning it would be a painstaking effort to interop with React and/or TypeScript), or some had incompatible/undesirable licenses to boot.

Well, I think my wish for such a toolkit has now been fulfilled, and its name is Blueprint.

It is written in TypeScript and the library comes bundled with TypeScript definitions

It looks good!

With so many good selling points, I've decided to adopt Blueprint as the UI foundation for all my viewer templates. This means.

We have better looking modal dialogs (as seen in our updated Aqua template)

We have better styled UI for certain tools

And we have key components needed to start bringing across the other remaining Fusion templates, like the TurquoiseYellow template.

Compare the original Fusion template

With our blueprint-powered version

Looks good enough doesn't it?

Now there is one executive decision I'm making with the Fusion templates I'm porting over. The overview map will always be present as a toggle-able button on the main map viewer component and not outside of the map viewport.

Part of the problem is having the OpenLayers OverviewMap control render its content outside of the map viewport doesn't really play nice with React component updates in my attempts thus far, so I've taken the creative decision to not bother trying to get everything 1:1 when porting these templates over. As long as the main elements and styles are there, it's good enough for me.

Now sadly, despite having made several existing libraries and React components redundant (and they have been removed as a result), taking on Blueprint has added a lot of extra weight to our final bundle

Fortunately, this is still significantly under the current Fusion production bundle size, and as we are still in the process of reaching functional parity with our existing AJAX/Fusion viewers, bundle optimization is not a priority at the moment. When that time comes, we can look at things like custom OpenLayers build profiles and moving to Webpack 2 for its tree shaking feature, which should make some in-roads in cutting down our production bundle size to more acceptable levels.

Friday, 25 November 2016

This release has been updated to use the latest version of OpenLayers and fixes some breaking issues around viewing maps with Base Layer Groups (ie. tiled maps)

Sadly, no new Fusion templates have been ported over for this release as my hunt for a suitable tab and accordion component (that the remaining 4 fusion templates all use in one form or another) is still ongoing.

Monday, 17 October 2016

Previously, I mentioned a series of blocking issues (in the process of porting over the Aqua Fusion template) that I needed to clear before I can safely put out a new release of mapguide-react-layout. Well, those roadblocks have been cleared, so here's a new release.

It turns out that all we really needed to do was have each modal dialog containee component support the notion of a maximum height and overflow:scroll its inner content if that height is exceeded. As these modal dialogs are of a known height, we know what max height that will have to be applied to each top-level component that is to be housed inside a modal dialog

2. We sort of need InvokeScript support as that is what the original templates use to show the Task Pane / Selection Panel / Legend / etc.

I've decided to nip this one in the bud and just hard code into the template 3 toggling buttons that you can see on the top-right hand corner of the screenshot. If you close any of the affected modals, you can hit the respective button to bring it back.

3. Flyout menus don't play nice either

This one looked to be hard, but thankfully the react-flyout component I've chosen allowed for my workaround to (ahem) work with minimal amount of legwork.

The key was to introduce a new top-level FlyoutRegion component which all flyout menus render into (regardless of what toolbar they come from). As this FlyoutRegion component is top-level, the flyout menus rendered inside do not have a direct parent-child relationship with the toggling button (from whatever toolbar it may originate from), therefore it is not subject to being caught up in any toolbar CSS overflow shenanigans.

The only catch with this is that all the flyout menus will always render to the top left corner (I'm guessing this wasn't a usage scenario envisioned by the react-flyout author). So as a minor hack I've introduced new redux actions to dispatch the opening/closing of flyout menus, which the FlyoutRegion listens on, and the flyout menu buttons dispatch such actions when clicked, also flowing back DOM element dimensions (of the toggling button), so the listening FlyoutRegion component knows exactly where to position the flyout menu to be rendered.

This isn't perfect, as this animation shows

But it's a small (and acceptable) price to pay for what is now mostly functional flyout menus.

Application Definition (aka. Flexible Layout) support

As porting across the Aqua template would imply, the viewer now accepts Application Definition resource ids in addition to Web Layout resource ids. Please note that currently only certain templates can accept Web Layouts and others only accept Application Definitions. You can find out which template supports what in this document.

This support is not 100% yet, and so you will invariably find error placeholders for un-implemented widgets strewn across various toolbars and menus like so.

If you hover your mouse over these items, you can see what the viewer currently doesn't support or map across yet.

So when you encounter such buttons, the only practical thing to do at the moment is to remove such buttons from your Application Definition. The error placeholder mechanism is a nice and easy way to identify such unsupported elements.

If your Application Definition has external base layers defined, this release will recognize the following base layer types:

OpenStreetMap: Mapnik/CycleMap/TransportMap

Stamen: Toner/Terrain/Watercolor

Better Startup

The initial release of mapguide-react-layout wasn't really graceful about any errors that occur during the process of initialization. All you had was a white screen of nothingness to tell you that something went wrong. So naturally in the interest of providing a good out-of-the-box experience, this was something that needed to be rectified.

If there's a problem on startup, we'll show it to you now. For example, the MapGuide Server isn't running or reachable.

Another common silent error is loading a map with an EPSG code not known to proj4js (ie. Anything not EPSG:4326 or EPSG:3857). Now for such EPSG codes that aren't known to proj4js on startup, we'll automatically fetch the appropriate proj4js definition from https://epsg.io, register it with proj4js and continue on our merry way.

More OpenLayers goodies baked in

The base map viewer component now has these extra OL controls baked in.

An overview map

A rotation reset button that's shown when you rotate the map. You can rotate the map by holding ALT+SHIFT and dragging the map to rotate it.

Tuesday, 11 October 2016

The only change from RC1 is a fix to the QUERYMAPFEATURES operation and its consuming map viewers (both AJAX and Fusion) to respect the display order of attributes in Layer Definitions.

Along with this release are updated mg-desktop binaries and nuget packages. Just a friendly reminder about nuget packages: Install the version of the nuget package for the version of MapGuide you are intending to be running your application against.Download MapGuide

The first part was actually pretty simple. The Application Definition schema is actually simpler to model than the Web Layout one (due to the heavy emphasis on key-value pairs, which is why the schema is still at v1.0.0, the key-value pair mechanism allows for arbitrary extension to support new features). It was a case of mapping the loaded Application Definition to the common initialization structure that the Web Layout currently maps to.

Though we didn't have any of the 5 templates ported at this point, we could throw an Application Definition at the "ajax-viewer" template and it is able to understand a majority of the widgets, with the exception of toolbars, because toolbars are uniquely named for each Fusion template and the Application Definition is expected to be referencing the template-specific toolbars.

So at this point, it was now a case of porting across the 5 templates. As we currently lack an accordion or tab component, the logical place to start is the Aqua template, which uses neither.

We start off with the existing "ajax-viewer" template, re-arrange the layouts of the various toolbars/menus and using our modal dialogs to house the Task Pane, Legend, etc, and taking advantage of widespread modern browser support for CSS gradients and we get ...

Let's compare that to the original

What do you reckon? I think it's a good visual emulation of the original.

And here's an extra added bonus. Currently, the toolbars (in any layout component) do not handle overflowing toolbar items at all. It turns out that applying a standard CSS overflow:auto on the toolbar container element gives us a very usable solution:

I've decided to activate this for toolbars with horizontal orientation only, as this trick doesn't work very nicely on toolbars with vertical orientation and I am going on the assumption that one wouldn't pack a vertically oriented toolbar with too many items.

Unfortunately, the process of porting across the Aqua template has revealed some teething issues.

All the work in making these components full height presents issues when housed within our modal dialogs. For example, here's what the selection panel looks like

It completely overwrites the container for the modal dialog, so we can't hide the dialog or drag it around. The Task Pane has the same problem.

2. We sort of need InvokeScript support as that is what the original templates use to show the Task Pane / Selection Panel / Legend / etc.

Failing that, we probably have to hard-code this toggling functionality into the layout component.

3. Flyout menus don't play nice either

See that scrollbar? The flyout menu is overflowing inside the toolbar container! I hope the react-flyout component we're using allows us to break out of parent-child element relationships for a flyout menu and its toggling button, otherwise we'd have to look at an alternative.

Once these problems are solved. That's when I think I'll put out a new release.

Up until now, I have intentionally withheld the GitHub repo where all this development action was taking place as I wasn't going to reveal something that I didn't think was ready to be revealed. Well, it's now reached a point where I am comfortable with finally unveiling this project for general public consumption.

mapguide-react-layout requires at a minimum MapGuide Open Source 3.0 or an equivalent version of Autodesk Infrastructure Map Server. As I've stated in part 2 of this series, this viewer will require a modern web browser, which practically speaking is any of the following:

Google Chrome (stable channel)

Mozilla Firefox (stable channel)

Internet Explorer 11. Older versions of IE will not be supported

Microsoft Edge

For iOS: Mobile Safari

For Android: Google Chrome or Mozilla Firefox

Overview

Conceptually speaking, you can think of mapguide-react-layout as the logical successor to Fusion and the AJAX viewer and it carries similar high-level concepts as well:

mapguide-react-layout has templates just like Fusion.

Your application consists of various components (as opposed to widgets in Fusion)

Components subscribe and dispatch actions to push data to a centralized redux store for application state. (as opposed to widgets having to explicitly subscribing to each other with event handler spaghetti). Application state is significantly easier to reason about in mapguide-react-layout as opposed to Fusion or the AJAX viewer.

mapguide-react-layout offers the same extension points for custom functionality for your MapGuide applications, such as InvokeURL commands.

Just like before, a mapguide-react-layout viewer is driven by a Web Layout or a Application Definition (this is not implemented at this stage, but is something I intend to complete).

It is the last point which is probably the most salient. Because a mapguide-react-layout viewer accepts a Web Layout or Application Definition, the authoring and development workflow for MapGuide applications using this viewer remains unchanged.

Instead of pointing a Web Layout to an AJAX viewer URL or an Application Definition to a Fusion template URL, you just point either resource to your mapguide-react-layout viewer.

Templates

The mapguide-react-layout viewer includes the following layout templates out of the box:

1. AJAX Viewer

This template visually mimics the original MapGuide AJAX viewer (with much less frames!)

2. Sidebar

This template is a responsive layout based on the sidebar-v2 map viewer template. Well suited for phone/tablet sized displays.

In future releases (once I get the Application Definition support implemented), I'll look at bringing across the existing 5 Fusion templates as well.

Getting the viewer/code

The code to mapguide-react-layout can be found on my GitHub repo. There you will also find installation instructions and other various documentation/notes.

Saturday, 24 September 2016

Of the fixes made since the Beta 2 release, the most notable will probably be that the installer should now work properly on Windows 10. Didn't this work in the past? Yes it did, but when it did work, Windows 10 was still in preview and the version of IIS was most likely not the version it is now (10.0).

Why is this important? Because the windows installer does an IIS version check to determine what screens to present in the Windows Installer UI. Our version check was:

IISVERSIONMAJOR >= "#7"

Which basically says: show the IIS-specific UIs (and allow advancing through the IIS-specific screens) if the Windows Installer detected IIS (version 7 or higher) on the host machine. On Windows 10, the version reported is "#10".

And due to how lexicographical string comparisons generally work, the above check fails because "#10" is not greater than "#7". Ain't Microsoft versioning great? They skipped Windows 9 to avoid a whole class of version checking problems, but as we've found out some still slip through the cracks. The IIS installer check is now:

(NOT IISVERSIONMAJOR = "#0")

Which just means show the IIS-specific installer UI screens if IIS is present. We don't care what version it is, and "#0" signifies that IIS is not installed.

And the reason we don't have to care about the particular version is because the versions of Windows that our installer supports will have IIS 7 or higher. So unless Microsoft decides to move away from an appcmd.exe-based approach to IIS configuration in the future, the Windows installer shouldn't have anymore IIS-related issues in the foreseeable future.

So with that interesting version story out of the way, here's the binaries at the usual place.

Wednesday, 7 September 2016

Since MapGuide Open Source 2.6, the mapagent has an improved QUERYMAPFEATURES, which allows for greater flexibility to handle the selection and tooltip requirements of our various client-side map viewers. Previously, you would've needed custom server-side code using the MapGuide Web API to achieve this, but now that it is part of the mapagent, all you need is a basic AJAX request.

The main purpose of this operation is to produce an encoded feature selection based on an input geometry (from a mouse click or a box drag) or depending on what you ask for, it can also include:

Now what some of you may not know is that you can also use QUERYMAPFEATURES to pass in existing selection XML to get back the inline selection / tooltip / hyperlink / attributes. To do this, you have to omit the GEOMETRY parameter (which normally has the input WKT geometry from the mouse click or box drag) and specify a FEATUREFILTER parameter instead which contains your selection XML. Yes, it's a confusing name. You would've thought it would be named SELECTIONXML or something like that wouldn't you?

The LAYERATTRIBUTEFILTER parameter is a bitmask that applies layer-based restrictions on what part of the selection to render:

1 = Include visible layers

2 = Include selectable layers

4 = Include layers with tooltips defined

When the LAYERATTRIBUTEFILTER is not specified, MapGuide will internally default to the value of 3 (include visible and selectable layers). When you combine this with passing up selection XML instead of an input geometry, can you see why it may raise a false alarm? Because your selection XML may be for selected features that are not visible in your current view, so when you pass the selection XML up to QUERYMAPFEATURES without setting LAYERATTRIBUTEFILTER=0, MapGuide will use default visibility/selectability criteria and most likely omit your selected features from the selection image / tooltip / hyperlink / attributes and as a result, you may get back nothing despite passing up valid selection XML and asking for selection image / tooltip / hyperlink / attributes.

So if in doubt, if you're passing up selection XML to QUERYMAPFEATURES, be sure to pass up LAYERATTRIBUTEFILTER=0 as well to avoid any nasty surprises.

Saturday, 3 September 2016

Previously in this series, I had to take a momentary detour to restructure the viewer to use Redux for streamlined application state management as the viewer has grown to the point where having a Flux architecture was essential for viewer development to continue at a maintainable level.

The challenging part of this process was to get the map viewer component to play "dumb" (so that we can wrap a "smart" redux-aware presentational component around it), namely because this component is the one that wraps OpenLayers, and we had to modify our usage in a way that facilitates uni-directional data flow that is required by Redux. Instead of making direct calls to OpenLayers to pan/zoom/etc, it now pans/zooms/etc based on the component props and any changes to those props.

This presents a slightly awkward at first glance (but necessary) situation where any events from OpenLayers that would change the map view and display parameters, we have to intercept these changes and flow it back to the Redux store first, that would then propagate back to its "smart" parent component that will set the necessary prop changes to carry out the map actions.

This work is now mostly complete, so now we can focus back on continuing evolving the viewer.

As I've been implementing the following features described below, I've gotten this great feeling of validation in my choice of using React, because there is actually high conceptual synergy between React and our current Fusion viewer framework.

Fusion is based on widgets. React is based on components

A Fusion application is composed by widgets described by a Flexible Layout document. Our react-based viewer is composed by various components, which I intend to compose together through a configuration abstraction similar to a Web/Flexible Layout document in combination with our (now) centralized application state in the Redux store.

But unlike Fusion, we have a single source of truth for application state. In Fusion, application state is all over the place with some state requiring manual event subscription to be notified of any changes. Our react-based viewer has none of these problems thanks to Redux.

So now, here's what I've achieved since the last post in this series.

Layout Templates

We now have the notion of layout template components. This is the Fusion template analogue ported over to our React-based viewer. To support this template concept in the React world, we need a supporting cast:

1. We have various registry modules that allow various components and commands to be registered. This is also an extension point that allows for external components and commands to be registered as well.

2. We also have a notion of "placeholder" components which reference a particular component by its registered id. Layout template components consists of these placeholder components and various layout/placement code, which once the application has been initialized, these placeholder components will mount and will render the actual components from the registry by their component ids. The placeholder allows for a clean error boundary that allows us to show informative errors in its place should we try to render a component that has not been registered.

In practice what placeholder components give us can be illustrated like so:

Here's the viewer using the "AJAX Viewer" layout.

Should some components not be registered, say the Task Pane and Legend components, we get a clear error message that spells it all out.

In Fusion, such a situation would probably be a silent failure. Even in this state, the new viewer is still mostly functional as all these components are now independent of each other. Their only shared dependency is the centralized redux store. So with this clean separation of components and presentational layout now in place, I've also explored some other template designs. In particular, a port of my dream responsive map viewer layout.

Say hello to the "Sidebar" template, named after the sidebar-v2 template it was originally derived from:

Unlike the original template, all the jQuery that drives the original sidebar behavior has been ripped out and replaced with React components that do the same thing. Also it has some extra niceties, such as the main toolbar docked vertically, and some in-built smarts to auto-flyout Task Pane content when URL commands are invoked, which is important on mobile devices, as illustrated below when I try to do a buffer on an emulated smartphone display.

Web Layout compatibility

Here's something about the two templates above. They are both driven by the same Web Layout document. Why invent a new configuration mechanism when our existing Web Layout and Flexible Layout documents already do this for us?

Being able to support Web Layouts (and in the future, Flexible Layouts) is important as it means the authoring process for MapGuide applications using this new viewer remains unchanged. The only difference is you point the Web/Flexible Layout to this viewer instead of your AJAX/Fusion viewer. Now this support is not 100% compatible, there's still missing commands I've yet to port across, but feed a Web Layout document to this viewer and it should be able to recognize most of the commands and configuration options within.

As hinted in the previous paragraph, I intend to support Flexible Layouts as well. As mentioned previously, Fusion has such conceptual similarity to React that most of the things that drive Fusion should be easily portable to our new viewer as well.

Other cool stuff

Thanks to ol3-contextmenu, we got a functional context menu now. This is driven by context menu settings from the Web Layout.

The viewer now has basic modal dialog support as well, which is an important component for an "Aqua" style layout template when I get round to it, and also as an alternative target for Invoke URL command content.

Current weigh-in

Before I close out this post, here's the current weigh-in for our viewer bundle.

I have no doubt we'll cross the 1MB barrier soon, but once I start seriously looking at "paying for only what we use" with regards to all the libraries we're using, the final bundle size should hopefully approach something more reasonable

This viewer has reached a point where I must take a momentary and necessary detour to accommodate a change of architecture to make further development work more manageable. At this point the viewer was a series of components connected together through various event handlers. The encapsulating application component was turning into a big ball of event handler spaghetti trying to get various components to talk to each other.

Any React application that grows in complexity will eventually encounter such problems, and when such problems arise, it necessitates the introduction of Flux architecture to centralize and streamline application state. So if we're going to introduce Flux into this viewer, we might as well go with the best: Redux.

What sells Redux for me as the top-tier Flux library of choice for React applications is its jaw-dropping developer tools support. Just check out this video for a taste of the developer experience we get with a React application using Redux.

Or for something more relatable, here's our current viewer redux-ified

All our viewer actions are now dispatched to a central redux store where various reducers are in place to update/replace various parts of the now centralized application state, and in combination with the Redux DevTools, we get a live audit trail of all our application actions and their respective data payloads and state transitions. Our various redux-ified components then respond to various changes in this redux store to update/render themselves.

You may also notice that we've got our iconic zoom navigator on our map now. Notice how it's properly flashing the loading animation as the map is loading/refreshing? This is because it's connected to a branch of the redux store where the map viewer component is dispatching Map/SET_BUSY_COUNT actions to whenever our MapGuide image sources are about to start/finish a map image request. A non-zero value means show the animation (as one or more rendering operations are in progress), a zero value means to hide it.

That is the extent of the "wiring up" that our components need. Dispatch to update state, connect to listen and re-render against updated state. Redux handles and coordinates the rest.

The Redux DevTools also has a nice graph view that allows us to see our centralized application state in a nice animated graph (which also live updates with each action and change of state)

Can you imagine this kind of developer experience with our current viewer offerings? Not a chance!

This tree (and the values within) is what entirely drives and describes the state of the whole viewer application. This is what a Flux architecture gives us. A single and centralized source of truth about our application.

Can you imagine as well, the ease of reproducing an issue in this redux-ified viewer? You just export the state tree with the Dev Tools, and send it to the developer. He/she can load import that state tree and replay the whole series of dispatched actions up to the point of failure.

The process of redux-ifying the whole map viewer is basically splitting our various components into "smart" and "dumb" versions. The dumb components simply takes the props given to it and renders itself from that, whereas the smart components are redux-aware, wrap the respective dumb component and passes new props to it whenever their connected state branches are updated.

This redux-ification is still in progress, meaning things like the Task Pane (and its AJAX viewer API emulation) are currently broken, but once it's done we can continue onwards with a solid, robust and more maintainable foundation in place.

A mock Task Pane that will eventually function like its AJAX viewer and Fusion counterparts

So where are we at since that post? I think we have something that can almost replace the AJAX viewer.

The above screenshot may not fully cover the extent of changes, so let's cover the changes bit by bit.

External Base Layers

The map viewer can now have external layers incorporated. Currently, this can be OpenStreetMap or any XYZ-based tile set, like Stamen tiles as seen from the above screenshot.

One thing that will surprise you (as it did for me when I got this first working), is that there is no longer a hard EPSG:3857 requirement in order for your MapGuide Map Definitions to properly line up against OpenStreetMap and friends. The above screenshot shows our venerable Sheboygan sample map in its original EPSG:4326 coordinates lined up against EPSG:3857 stamen tiles.

This is possible due to client-side raster re-projection capabilities present in OpenLayers 3 itself. What this means is that as long as your Map Definition is in a coordinate system that has a corresponding EPSG code, OpenLayers will happily re-project other layers to line up with your Map Definition instead of vice versa. In the above screenshot, the Stamen tiles are being re-projected to line up with the Map Definition.

For maps that are not in EPSG:3857 or EPSG:4326, there is currently a small additional setup process to carry out.

What this error means (and something I can hopefully clean up), is that OpenLayers has no idea about the projection EPSG:28355 (the projection of the map). Although the viewer includes proj4js and automatically wires it up to OpenLayers, it still only knows about two projections out of the box: 4326 and 3857.

Foreign projections have to be registered to proj4js first (epsg.io conveniently provides proj4js snippets to show you what to do) before mounting the map viewer application component. Once I registered the projection for EPSG:28355 to proj4js, the Melbourne map shows up properly, with OpenLayers happily re-projecting the EPSG:3857 stamen tiles to line up.

Toolbar

The viewer now contains a floating toolbar where we can dock our various commands to. Toolbars are completely data-driven and commands inside can be individually be selected/enabled/disabled based on various states of the map viewer.

The react-flyout component is used to provide support for flyout menus

Task Pane

In our previous post, the Task Pane was nothing more than a plain UI mockup. Now it is a mostly functional simulation of what is provided by our existing viewers.

The Task Pane functions as a generic content container where various viewer commands can show their UI in. In order to fully simulate the Task Pane, it needs to provide the JS functions that Task Pane content can call back into. In other words, our viewer needs to shim/emulate the AJAX viewer APIs to allow existing Task Pane content to work.

So to this end, I've been using the official PHP Developer Guide samples as a "reference implementation" for Task Pane content and the AJAX Viewer APIs they're trying to call. The page in the first screenshot is the landing page of the developer guide samples. So what this means now, is we have emulated AJAX viewer APIs for:

Zooming/Panning to a particular location

Setting selections

Refreshing the map in response to server-side layer/group changes

Digitizing geometry

And much more!

The AJAX viewer API emulation works so well, we can throw things like the existing AJAX Viewer buffer tool at it, and it will work just fine in our new viewer environment.

Legend

Finally, as can be inferred from previous screenshots, the Legend component is now aware of external base layers and provides a switcher UI if more than one external layer is present.

So when you combine all these changes together, I think I will get agreement here that we have something that nearly approaches (in some cases, exceed) the AJAX viewer in functionality. The only glaring omissions at this point are various display elements (like a status bar), reaching command parity with the AJAX viewer, and a layout/configuration abstraction akin to a WebLayout or ApplicationDefinition, which is something I intend to implement in some form.

Before I close out this post, here's the current weigh-in for this production viewer bundle

The weight increase was mainly due to adding proj4js, but compared to Fusion we're still way smaller. As long as things stay under 1MB, I'd be content.