Note: This tutorial is meant to work with MarkLogic 4.2 and 5.0. In MarkLogic 6, you can instead use the map visualization widget.
Using MarkLogic’s Application Builder makes it a snap to put together an application quickly. Whenever I build a search app this way, I feel like I’ve gotten 80% of what I need. This post shows how to take the next step and add a common feature: maps.
To illustrate these steps, I’m going to use an application I put together to track my souvenir pin collection. I wanted to keep track of when and where I got each of the pins, and a map is a natural way to display that. At a high level, here are the steps:
- Put some sample data in the database
- Use Application Builder to generate the basic application
- Add an element where the map will appear
- Create a module to return some data points
Now for the details:
I decided to go ahead and put my pin collection out there in the wild, so that you’ll have something concrete to use to work through this tutorial, if you have nothing better. The structure is nothing special, but it will do the job. Set up your database and load up the data. The easiest way is with MarkLogic Server 4.2′s new Information Studio (which you can learn about from a couple tutorials. or the docs). I put an date element range index on acquired-date so that I could have a facet, but it’s not required for this exercise.
As with Information Studio, I’m just going to give you a quick reference to the learning page on MarkLogic’s developer site, since I want to focus this tutorial on the map aspects. You can take all the defaults while going through the App Builder wizard. When you’re done this step, you should have a standard, out-of-the-box app where you can nose around my pin collection. If you search for “bell”, you should find my Liberty Bell pin.
Application Builder puts the generated code into a modules database. You can either use WebDAV to edit it there, or use the Support Package to get a copy to put on the file system and edit that way (in App Builder, click the down arrow next to the application’s name and choose Support Package from the drop menu; make sure you update the application server to point to the directory on the filesystem where you unzip). Either way, we’ll be working mostly in the application/custom/ directory.
Our goal here is to put the map on the first page we see. In an App Builder app, pages are identified by the “view”. Open up application/custom/appfunctions.xqy and find the app:get-content() function. The first thing you’ll notice is probably that it is commented out. When App Builder generates an app, it produces standard and modifiable copies of a bunch of functions that control how the app runs. Close the comment that precedes the function and remove the close-comment marker at the end of it so that this copy of the function will be in effect.
You’ll notice this line at the top of the function:
let $view := $config:CONTEXT/*:view
This identifies the view. We want to add a div element to house the map on the “intro” view. Here’s how the function ends up:
That gives us a place to put our map. Now we need to get some data for it.
This XQuery code retrieves the geo points in the database, along with the pin’s URI and name. (The where clause is necessary, because when a pin is added, there might be a description of where it came from without having lat & long.) Save the following to /application/custom/get-points.xqy:
Note that this isn’t anything fancy — it’s grabbing all the points. For an application with more data, you’d probably want to restrict the results to the area showing on the map, and perhaps by other criteria. To run good geo queries, you’ll want to add a geospatial index. We’ll leave that for another day and just grab all the data for now.
I created an /application/custom/js/maps.js file that handles initialization and display of points. Let’s take a look at that, and then we’ll bring it all together by adding the script include statements that we need.
We have an initialize() function that gets called when the page is ready. The initialize() function creates a map in the div element that we created to hold the map in step 3. It then calls loadPoints(), which makes an AJAX call back to the module we created in step 4. The results come back as XML. I use jQuery to parse through the results, create a set of markers, and figure out the correct bounds for the map so that it is zoomed just right to display our data. If you click on one of the markers, that will take you to the detail page for the individual pin (take another look at the get-points.xqy module and you’ll see that set up in the url element).
You may be looking at the code wondering why I made a buildMarkerClickEventHandler() function instead of just creating the listener inline within loadPoints(). In a word, closure. To keep this post focused, I’ll leave a more detailed explanation for another day (if you’re dying to know, say so in the comments, that will make me get to it faster).
You’ll also need to download jQuery, as I’m using that in my script. Put it in /application/custom/js/.
The inline comments mark what we’re adding. Remember to un-comment the function by moving the close-comment marker from the end to before the function, just after the preceding comment. When you're done, you'll know where all my pins came from!
So there you have it. Follow these steps, and you should find yourself with a working map display. Leave me a comment to tell me where more detail is needed or what cool stuff you’ve found to display on your maps. Happy coding!
This tutorial first appeared as a post on David's blog.