Within the APEX development team it has been known for some time that maps are frequently required and this has been on the roadmap to be built in to the core product.
Since 2016 I have built and maintained plugins that make it easy to integrate Google Maps in your APEX applications. The most popular of these is the ReportMap Plugin which has received a wealth of features and enhancements, and is being used widely.
Introducing: Map regions
In the latest pre-production release APEX 21.1 which you can try today for yourself at apex.oracle.com, the APEX development team has introduced the new Map region. This component allows the developer to show a world map on the page, and add one or more “Layers”, each based on a table or SQL query, to show features on the map.
This is a welcome move because it means maps will be easy for all APEX developers to use, including those who avoid plugins (e.g. due to the lack of direct support from Oracle). It also means that maps added to applications based on the core feature will be fully supported in future releases of APEX without needing to rely on a 3rd party.
Note: this post is based solely on my first impressions of the new feature and is not intended to answer all questions about it. There will be more official blog posts coming directly from the APEX development team about this and other new features being introduced in APEX 21.1, so keep a look out for those!
The APEX Map region renders the map using the MapBox API, with a number of tile backgrounds included based on OpenStreetMap and HERE map data.
Implementing a Map Region
Let’s try the new Map region, and compare it to the jk64 ReportMap plugin. To add a map to a page, I added a region of type “Map” and nominated a data source for the Layer to show on it.
First thing you might notice is what I didn’t need:
no 3rd party plugin to install
no account on a 3rd party cloud service
no API key, and no APIs need to be enabled
no credit card details
The Map region is free to use within your applications, just like any other built-in feature of APEX.
If you already have a map based on the ReportMap plugin, you can if you wish convert it to a map by simply changing the region Type to Map – the SQL data source will be preserved and you can use the same query for the new Map region type. However, the danger with this approach is that you may lose some attributes and customisations that you had on the plugin region. Instead, I recommend using the Duplicate feature to create a copy of your map region, change the attributes on the copy, using the original as a reference as you go. Once you’re happy with the new map you can then delete the original plugin region.
You can choose to set the data source directly on each Layer (e.g. if you want to get data from multiple data sources), or you can set the data source on the map Region. If you set it on the Region, each Layers may refer to the Region data source by setting the layer’s Location attribute to Region Source.
If you have a single data source that you want to split up into multiple Layers, you can set the Row Assignment column which will be used to differentiate the data for each layer. This would be more efficient than adding separate SQL queries for each layer if the queries are based on the same table or view.
The first thing I noticed when converting my demo application was that since my data source was based on an APEX collection, the Lat and Lng columns are strings, not numbers; to use this as a data source for the Map region I had to convert the strings to numbers first using TO_NUMBER.
Once that was done, I could map the LAT and LNG columns to the layer by setting the Geometry Column Data Type to Longitude/Latitude:
This is the result:
I wanted to style it as close as possible to my plugin demo so I tweaked a few attributes on the Region:
Appearance / Template Options / Remove Body Padding = on
Background = Custom
Standard (tile background) = Oracle World Map
Height = 350
Controls / Options / Mousewheel Zoom = on
Legend / Show = off
Point Objects / Shape Scale = 1.5
Appearance / Fill Color = #e21818 (reddish)
Tooltip / Column = NAME
Info Window / Title Column = NAME
Info Window / Body Column = INFO
Much nicer colours, in my humble opinion 🙂
Map Dynamic Actions
The next thing I need to do is handle click events on the pins. To do this, I added a Dynamic Action to the map region, on the event Map Object Clicked.
Notice that the Map region can trigger the following events:
Map Initialized – the map has loaded, including all the data
Map Changed – the user pans the map or zooms in or out
Map Clicked – the user clicks in the map area (but not on a pin)
Map Object Clicked – the user clicks on a pin
At runtime in debug mode, the console log will then show the content of this.data which shows the structure it takes.
Based on this, I can see that we get the id, infoWindow, lat, lng, and tooltip attributes – similar to the structure provided by the ReportMap plugin events. If I want to display the Latitude and Longitude values on the page, I create a display-only item (e.g. P1_POSITION), and add a “Set Value” action to the dynamic action as follows:
When a pin is clicked, we now see this data on the page:
Of course, we could make this item hidden if we want, and use its value in other ways.
The Map Clicked event returns this.data.lat and this.data.lng to indicate where the user clicked.
The Map Changed event returns:
this.data.changeType – e.g. ‘map-resized’, ‘map-drag’, ‘map-zoom’
this.data.bbox – bounding box, an array of numbers of longitudes and latitudes [west, south, east, north]
this.data.layers – an array with attributes for each layer (id, name, visible)
The following is a short list of features or enhancements that the new Map region has, which are not (currently) supported by the ReportMap plugin:
Multiple data layers, each with their own distinct data source; you can also use one SQL query for multiple layers if you want
REST data sources
Declarative styles – options for customing how each data layer is rendered, including pin and feature icon, colour and size
Min/Max Zoom – each layer can be shown or hidden automatically depending on the zoom level (e.g. show a more detailed layer as the user zooms in)
Legend – each data layer (optionally) can be shown in a legend, allowing the user to selectively show/hide each layer
Extruded polygons – show a 3D polygon rising from the ground with the height determined by a column from your data source
Initial Position and Zoom is remembered between page requests
Circle tool – allows the user to draw a circle on the map; you can then respond to this by filtering data by points that fall within the circle
Distance tool – allows the user to calculate the distance between two points
That’s page 1 of my side-by-side demo done. Not too shabby!
More posts to come…
Q: Should we switch now, or wait?
Q: Is this the end for the JK64 ReportMap plugin?
I consider this one of the strengths of the plugin system: the APEX development team will never have the capacity to support every last feature needed for every last application built for any customer in the world, so developers and companies should be empowered to create and use plugins to fill those gaps.
These plugins often serve to prove there is demand and need for a particular feature, and this often prompts the APEX development team to consider building these features into the core product, making it better with each release. Exciting times are ahead of us.
The plugin now supports Overlays, which allows you to add almost any arbitrary HTML or image content to particular points on the map. For example, instead of showing the default red pin, you might want to show a pretty Information card at a location. The cards are clickable as well, and you can use a dynamic action to make your app respond however you wish.
You can even show an image as a map overlay, which will be scaled automatically as the user zooms and pans the map.
The Australian Oracle User Group is hosting a series of webinars this year and on 17 February I’ll be speaking about how to add an interactive Google Map to your APEX application. If you’re using APEX and want to see how easy it can be to integrate Google Map capabilities into your application, this is for you.
My session is at 9am in Perth time (1am UTC) – which is:
This is implemented as a radio item with a dynamic action on the Change event:
The dynamic action has the following attributes:
Note that in this case, it sets an Option – Map Type, based on the triggering element (the P28_MAP_TYPE item). The Affected Elements is required, and must refer to the map region that we want to change.
Notice anything missing? That’s right – No Code needed!
The plugin makes it easy to customise which of the default Google Map controls (buttons, etc.) are shown to the user:
Full Screen control
Map Type control
Street View Pegman control
Other options that can be set include:
Disable default UI
Maximum Zoom level
Minimum Zoom level
Restrict search to Country
In addition, the plugin allows you to restrict the map to a set of bounds, via the Restrict to Bounds or Restrict to Bounds Strict Mode actions.
Another enhancement included in this release is explicit support for the Table / View data source. This is simple to use, although not quite as flexible as the SQL Query option. Your table or view must include columns with the correct column names expected by the selected Visualisation – for example, if your Visualisation is Pins, the table or view must have columns named lat, lng, name and id. Click the Help tab on Table Name for more details, or review the WIKI (https://github.com/jeffreykemp/jk64-plugin-reportmap/wiki/SQL-Query-Examples).
NOTE: the plugin supports APEX 18.2 and later. It is no longer planned to include backports for older versions of APEX.
A big thanks to many APEX developers around the world who have installed and used the map plugin over the years. Your suggestions, questions and bug reports have contributed a great deal to improving the plugin.
Version 1.2 of the ReportMap Google Map plugin has been released today. While the rest of you have been idling away under Covid-19 restrictions, I’ve been happy as a clam working on some exciting enhancements to the plugin.
Included in this release are the following new features:
New visualisation: Spidifier
Show turn-by-turn Directions
Load large data sets in batches
Show spinner while data is loading
A bug when the new Friendly URLs feature of Oracle APEX 20.1 is used with the Clustering visualisation has also been fixed in this release.
The full list of enhancements and bugfixes, with links to the issues register, may be viewed here.
If you have a map that needs to show a lot of pins, especially ones that are close together, the plugin previously had the option of Clustering them at high zoom levels. The user could click on a cluster to zoom in enough to show the individual pins. One weakness of this approach is that if one or more pins are almost (or exactly) overlapping, the cluster never “unclusters” – the user cannot zoom in far enough to get the pins to show individually.
This release provides another Visualisation option, Spiderfier. This uses the OverlappingMarkerSpiderfier to control how pins react when clicked. When the user clicks a pin that is close or overlapping with other pins, it shifts the pins in that area into a ring, or a spiral (depending on how many pins are there) with lines pointing back to their original location. It also colours them blue to indicate they’ve been “spiderfied”. The user can then hover and click each marker separately.
If the user zooms in, the Spiderfier automatically returns all the pins to their original location.
The WIKI has been augmented with a handy guide to Map Icons. The plugin has long supported the ability to specify custom images for the marker icons. This release gives a whole lot more control over the markers to the developer:
If all the icons in the query are being loaded from the same location, you can now set the iconBasePath option once and just have a relative icon file name in the query. When there is a lot of data to show in the map, this can significantly reduce the volume of data loaded to the client, which can lead to a significant performance improvement.
For example, if the marker icon needs to be different according to some data value, you can send the data via one of the flex fields, and then write your custom function to set the marker icon depending on the value of the flex field.
You could also modify other characteristics of the marker, such as the title (hover text), info text (popup window), icon anchor point, opacity, and even position (although usually I’d expect your query would provide the correct lat/lng coordinates).
If you have a large number of custom icons you wish to use, along with a large data set of pins to render on the map, you could even compile the icons into a single sprite map to reduce network overhead. This means the image file is loaded once to the client, and then the map “cuts out” bits of the sprite map to render the marker icons. This can be done by setting just a few attributes of the marker’s Icon object. I haven’t tried it myself yet, but this tool looks like it would be useful for this purpose.
Loading Large Datasets
This release adds the Show Spinner and the Rows Per Batch attributes. These attributes are independent of each other, and they help to improve the quality of the experience for your end users when you are rendering a large number of pins on the map.
By default, new maps will have Show Spinner set on. For any existing maps, after upgrading you can turn this option on by setting it in the plugin attributes. This option causes the map to show the default APEX spinner while the data is loaded. The spinner is then removed when the last marker has been rendered. The effect is to give the user an indication that the map is “working”, and gives them immediate feedback when the data has finished loading and they may now interact with the map.
If the spinner seems to stay forever, it may indicate an issue with connectivity to the server (or perhaps that the server is under severe load or has stopped responding to requests).
When the APEX page has been rendered on the client, the Google Map is shown but the data is not immediately loaded; instead, a separate AJAX request is sent to the server to run your query and download all the data to render the pins on the map. By default, this is all done in one single AJAX call, which is the fastest way to get from start to finish; the downside is that the user will not see any pins on the map until all the data has been downloaded. You can change this behaviour by setting Rows Per Batch to some number (e.g. 1000). With this attribute set, the plugin will send a series of AJAX calls to the database (one at a time) and get a batch of records at a time. After loading a batch, the plugin will render the pins on the map (and if necessary, it will pan / zoom the map to show them all) and then send another AJAX request to get the next batch. When it has finished receiving all the batches, it adds any finishing touches needed (e.g. for a visualisation) and returns control to the user.
The advantage of this approach is that the user can see the pins being shown gradually, and they will know that “something is working”. This may help to give them a nicer user experience.
The downside of this approach is that it may cause a bigger load on the server (because each AJAX request requires running a new query, with an offset) and will usually take longer from start to finish. Generally, if your data comprises only a few hundred records at most, you will probably want to leave the Rows Per Batch setting blank.
There are still a few little enhancements on my “todo” list, but I’m keen to hear how you are using (or perhaps planning to use) this plugin, and if there are any new features or improvements that you need or want. If so, please raise them on the GitHub Issues page.
Quite a few people have raised questions or ideas in the past and sometimes I’ve incorporated them straight away, and other times it’s taken a little longer but I get there eventually. If you’re keen to contribute, feel free to have a poke around in the code and perhaps even do a pull request on the GitHub source to suggest a change. It would be great to collaborate with you because everyone has something unique to offer.
Long-term, I’m watching with interest the future direction of Oracle APEX. I remember at one point they were talking about incorporating some sort of new map region into the product, although the mention of this seems to have been dropped from the Statement of Direction (or maybe my memory is misleading me). I guess time will tell.