Saturday, October 7, 2017

Angular Carto: Compound basemaps

This post is part of a series covering how to create reusable Carto map components. Read the introduction to learn why one would even want to do such a thing. The previous post in the series was voluminous, so I will change things up and describe a minor enhancement to basemap switching: basemaps that consist of more than one tile layer. This is particularly useful for adding labels to a basemap.

Enhancing the basemap prototype

This post builds off of the previous post on basemap selection. I will describe how specific components will change to accommodate multiple tile layers in a basemap. You may find it helpful to read the previous post to understand how the other components work.

First, consider the basemap prototype that requires exactly one tile layer. Its public interface exposes addToMap() and removeFromMap() methods.

Accommodating multiple tile layers in the basemap is as simple as adding a for loop to both methods. Note that after each tile layer is added to the map there is the invocation of setZIndex(layer.zIndex). Leaflet relies on a z-index to specify the order in which layers are rendered. The appropriate z-index is stored in the zIndex property of each layer.

Tweaking the basemap() function

Next, consider the basemap() factory function. Its parameters are the Carto/Leaflet map, a label to use in the user interface, and the tile layer to encapsulate, and creates a new object from the basemap prototype.

Accommodating multiple tile layers in the basemap() factory is as simple as allowing the layers parameter to be either a single tile layer or an array of tile layers. First, the function checks whether the layers argument is an array; if it is not, it converts it to an array with 1 element (lines 40–42). Next, it sets a zIndex property on each layer in ascending order relative to their position in the layers argument. This means that each element in the layers array will be drawn on top of all previous elements in the array (lines 43–45).

The list of basemaps

Now that our basemap prototype and factory can handle multiple tile layers, let’s throw some labels on top of a Google imagery layer. Carto hosts a variety of label-only layers, so we’ll use one of those. Thanks to the modifications we’ve made, it’s as simple as passing an array to the basemap() function.

Bringing it all together

You can Fiddle around with a working compound basemap selector below.

Why would you do such a thing?

Sometimes a basemap with just one tile layer just won’t cut it.

Other posts in this series

Sunday, October 1, 2017

Angular Carto: Switching Basemaps

This post is part of a series covering how to create reusable Carto map components. Read the introduction to learn why one would even want to do such a thing. In this post, we will discuss how to give the user control of the basemap and how to do some customizations on the basemap selector.

Manipulating the basemap

Before we begin, we need to ask ourselves: what is a basemap, and what does it do? In the context of a Carto map, a basemap is a layer of map tiles that sits under the informational layers. In order to be able to switch between different basemaps, each basemap should know how to add itself to the Carto map and how to remove itself from the Carto map.

This functionality is implemented in the basemap prototype, cleverly named basemapProto. The prototype has two properties and two methods:

tileLayer
The Leaflet tile layer for the basemap.
map
The Carto/Leaflet map whose basemap is to be manipulated.
addToMap()
Add the basemap to the Carto/Leaflet map object.
removeFromMap()
Remove the basemap from the Carto/Leaflet map object.

Now that we’ve defined a prototype for the basemaps, we need a way of building new basemaps from the prototype. To do this, we can define a factory function, cleverly named basemap. This function takes three parameters:

map
The Carto/Leaflet map whose basemap is to be manipulated.
label
A descriptive label for the map to be used in the user interface.
tileLayer
The Leaflet tile layer for the basemap.

What are controllers?

We’ve written code to change the basemap, but we still need to build a user interface that calls this code. We will do this by writing a controller for basemap selection. Somewhat confusingly, the most common way of using Angular controllers is actually an example of the Model-View-View Model (MVVM for short) user interface architecture. Consequently, you will often see the phrases “controller” and “view model” used interchangeably. To avoid confusion, I will generally stick with “controller” when referring to an Angular component.

My favorite guide to the MVVM design pattern remains Martin Fowler’s, despite being over a decade old as of this writing.

The essence of a [view model] is of a fully self-contained class that represents all the data and behavior of the UI window, but without any of the controls used to render that UI on the screen. A view then simply projects the state of the [view model] onto the glass.

To do this the [view model] will have data fields for all the dynamic information of the view. This won’t just include the contents of controls, but also things like whether or not they are enabled. In general the [view model] does not need to hold all of this control state (which would be lot) but any state that may change during the interaction of the user. So if a field is always enabled, there won’t be extra data for its state in the [view model].

Since the [view model] contains data that the view needs to display the controls you need to synchronize the [view model] with the view.

Projecting the state onto the glass

Before we discuss the implementation of the controller, it will be useful to discuss how Angular “projects the state of the [controller] onto the glass.” The controller exposes two properties:

basemaps
The list of basemaps available for the user to select.
selectedBasemap
The currently selected basemap.
We can use Angular’s ng-options directive to bind the options in a drop-down box to the basemaps property. Let’s break down the meaning of the ng-options expression in the sample code; somewhat confusingly, the expression is read right-to-left:
  • basemap in ctrl.basemap means that the options in the drop-down box are bound to each of the items in the controller’s basemaps property. Within the expression each item in the list of basemaps is aliased as basemap.
  • basemap as basemap.label means that when projected on the glass, the label property of the basemap will be displayed to the user.

The Angular ng-model directive binds the currently selected item in the drop-down box to the selectedBasemap property of the controller.

To sum up, the ng-options and ng-model directives ensure that the available options in the drop-down box are bound to the controller’s basemaps property, and the selected option is bound to the selectedBasemap property.

The state that is to be projected

The function that creates basemap controller’s is cleverly named basemapController(). It takes one parameter: a list of basemaps for the basemaps property of the controller. Delegating the basemaps property to a parameter of the basemapController() function enhances code reusability. The logic for switching between basemaps is the same for every map we build, but the list of available basemaps is likely to vary from map to map.

The selectedBasemap property has a more complicated implementation. The property setter (line 68 below) first checks whether a basemap is currently selected; if so, it removes that basemap from the map by calling its removeFromMap() function and sets the basemap’s isSelected property to false. Next, it adds the newly selected basemap to the map by calling its addToMap() function and sets its isSelected property to true. Finally, it saves the newly selected basemap in the backing store of the property.

Last of all, if no basemap is selected the map will be unbelievably boring, so the basemapController() function selects the first basemap in the list before returning the controller (lines 85–87).

The list of basemaps

For this example, we will make three basemaps available for selection: Google imagery, OpenSteetMap, and the ArcGIS world topo map. To construct the tile layers, we use Leaflet’s L.tileLayer() function. The available list of basemaps is fully customizable by returning a different list from the getBasemaps() function.

Register the basemap components

To get the basemap selection feature up and running, we will have to register everything with Angular. We register the getBasemaps() function as a factory under the name “basemaps”. The basemapController() creation function is registered as a controller under the name “basemapController.” Thanks to Angular dependency injection, Angular will resolve the basemaps parameter of the basemapController function by looking up the “basemaps” name, which we registered previously.

Calling the basemap controller from the HTML

To use the basemap controller, we use the ng-controller directive to indicate that a particular controller should be used for an HTML element. The ng-controller expression BasemapController as ctrl indicates that Angular should use whatever controller is registered under the name “BasemapController”, and that the alias ctrl should be used within that HTML element to refer to the controller.

Customizing the appearance of the basemap selector

A dropdown list is not the only choice available for controlling basemap selection. Another option is to use the ng-repeat directive to make a stack of buttons instead. The astute reader will note that the basemap’s isSelected property is not used by the dropdown, but is used by the button repeater.

Bringing it all together

You can Fiddle around with a working Angular Carto basemap selector below.

Why would you do such a thing?

I don’t want to have to rewrite the logic for selecting basemaps from scratch every time, especially because the only thing that ever changes is the list of basemaps, not the selection logic. Do you want to?

Design pattern bingo

  • Angular controller
  • Angular dependency injection
  • Angular provider (factory)

Other posts in this series

Wednesday, June 28, 2017

Angular Carto: Custom Zoom Buttons

This is part of a series covering how to create reusable Carto map components. Read the introduction to learn why one would even want to do such a thing. Read the previous installment, Carto Map Factory, to learn about adding a Carto map to a webpage and registering the map with the Angular dependency injector.

In this post, we will discuss how to add custom zoom buttons to the map.

What are directives?

We will implement our custom zoom buttons by encapsulating them in an Angular directive. Directives are a technique for packaging up bundles of HTML content and JavaScript code. Once a directive has been registered in your Angular module, it can be interpolated into the HTML of your web page, most commonly as an HTML element or as an attribute on a standard element. A good use case for directives is a text field with a date picker. The date picker should pop up a window with a navigable calendar, allow the user to browse through the calendar to find the date they want, and then save the date in the text field attached to the date picker. Additionally, the ideal date picker should be reusable throughout a website with a minimum of effort on the part of the programmer.

If directives are a new concept to you, I recommend Sandeep Panda’s “A Practical Guide to AngularJS Directives.”

The zoom buttons directive

Registering the directive

Directives are added to Angular modules using the directive function. The first argument is the name under which we are registering the directive; the second argument is a factory function for creating instances of the directive. Note that the registered name of the directive, zoomButtons, is camel-cased.

Note that in this sample code, the directive factory is an anonymous function, but you can (and perhaps should!) define a named function in a site-wide library and register the named function wherever zoom buttons are needed. Note also that the directive factory takes a parameter named map. At run time, the Angular dependency injector will pass whatever is registered under the name “map” as an argument to the directive factory. For more information about registering a Carto map with the Angular dependency injector, please see my previous post “Map Factory.”

Restricting calling style

Angular provides several different ways of calling a directive from HTML. The two most common are element style (<zoom-buttons></zoom-buttons>) and attribute style (<div zoom-buttons></div>). All calling styles produce the same results, so the Angular team recommends choosing the calling style to signal how the directive is used.

When should I use an attribute versus an element? Use an element when you are creating a component that is in control of the template. The common case for this is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.
To allow those two calling styles, we specify A (for attribute-style) and E (for element-style) in the restrict property of the directive. There are other directive calling styles, but it is not necessary to discuss them here.

Writing HTML to be interpolated

Next, we need to define an HTML template for the content that will be interpolated into the web page when the directive is invoked.

Each directive has its own $scope. If you are not familiar with $scope, for the purposes of this article it is best understood as the view model for an Angular component; in this case, $scope is the view model for the zoom buttons directive. The ng-click data binding and double-brace string interpolation are bound to methods and properties on this directive’s $scope.

This directive defines two buttons for zooming in and out. When clicked, the buttons call, respectively, the zoomIn() and zoomOut() functions on the $scope. The labels on the buttons are likewise defined in the zoomInLabel and zoomOutLabel properties on the $scope. Finally, to ensure that the caller of the directive has control over the appearance of the buttons, their class and style attributes are bound to the buttonClass and buttonStyle properties of the $scope.

You can define your HTML template either using a string literal, or by pulling it into a separate file if it is too unwieldy to manage within a string literal. Beware: the sample code below shows both techniques, but you should pick one or the other.

Configuring the directive $scope

When configuring a directive, we define a link() function that configures the directive $scope. The link() function is takes information from the directive invocation in the web page and translates that into the $scope required by the HTML template.

The first parameter of the link() function is the $scope to be configured. The second parameter is the HTML element on which the directive was invoked. The third parameter is the attributes of said element. In this particular link() function, information from the attributes are used to set the label and styling properties on the $scope. Note that the $scope.zoom*() functions are set to be map.zoom*.bind(map). Why not just set them to map.zoom* instead? Because the map.zoom* functions contain a variety of references to this. If you use $scope.zoom* = map.zoom*, those this references will be treated as referring to $scope rather than map. Using $scope.zoom* = map.zoom*.bind(map) binds the zoom methods so that they will still treat their thises as the map rather than the scope.

Bringing it all together

You can Fiddle around with a working Angular Carto map with custom buttons below.

Why would you do such a thing?

Carto/Leaflet maps come with a built-in zoom control. We had to explicitly turn it off with the option zoomControl: false. What’s so great about writing our own control in an Angular directive instead of using the built-in control? The main reason is that it gives us control over the look-and-feel of the zoom controls. If the built in Leaflet controls clash with the styling we’re using in the rest of the site, we can create buttons with the same functionality but a more appropriate appearance. Still not convinced? That’s okay. You’ve learned a lot about Angular directives, and that will serve you well in the rest of the series.

Other posts in this series

Friday, June 2, 2017

Angular Carto: Map Factory

This is part of a series covering how to create reusable Carto map components. Read the introduction to learn why one would even want to do such a thing.

In this post, we will discuss how to add a Carto map to a website and how to register the map with Angular so that our Angular components can manipulate the map display.

Reference the scripts

Before jumping into your new Carto map, you will first need to make sure that your web page has references to the Carto and Angular javascript libraries. In order for the map to tile correctly, you will also have to reference Carto’s standard stylesheet.

Sizing web page and containers

If you’re looking for an immersive map that takes up the entire web page, you will need to make sure that the html and body elements and the map container are all styled to fill all space available to them. My preferred method is to define a site-wide style implementing those requirements, including a carto-map-container CSS class that can be applied to the element where the Carto map is inserted.

Creating a container for the map

The map is inserted in a placeholder div element. You will need to assign that element an id. This id will later be passed to Carto to indicate where the map will be created. You will also need to assign the container div to the carto-map-container CSS class, which we defined above to expand to fill all space available. Experience has shown me that the map container div must be the first child of the body element for Carto to work correctly in Internet Explorer.

Registering a map factory with Angular

In a vanilla Carto web page, adding a map is as simple as instantiating the L.map class. (Technically, this is a Leaflet class, but the distinction is not relevant to the present post.) The complication of using Angular is that we will be building Angular components that manipulate the map, so we need a reliable way for those components to find the Leaflet/Carto map object. The method Angular uses for this is dependency injection. As we build angular components throughout this series, they will all take a parameter named map as a parameter. The Angular dependency injector will inspect each component’s constructor and provide the map object registered under the name “map” as the argument to the map parameter.

To register the map with Angular, we will register an anonymous function that instantiates and configures the map as factory under the name “map”. (The many options and functions to customize Leaflet/Carto map objects are beyond the scope of this post, so review the Leaflet documentation if you need to learn more.) Once another Angular component requests the map by having a parameter named “map” in its constructor, Angular will invoke the anonymous function to build the map, and store the resulting value to provide for all future requests of the map. In other words, the map creation function is called only once.

You should also note that there is another dependency injection at work in this code. The first argument of the L.map initializer is the element ID of the Carto map container. Rather than providing this ID as a string literal, map factory takes a parameter mapContainerID. The actual element ID is registered with the Angular dependency injector using the value. Putting the dependency injector in control of where the map is created loosens the coupling between the map factory and the HTML.

Bringing it all together

In a normal application where there’s at least one component that requires the map, all we would have to do is register the map factory. However, the example code doesn’t have any Angular components, so we need to request the map from the Angular dependency injector ourselves. You can safely ignore the window.onload handler in the sample code, because having at least one map-dependent Angular component on your page will obviate the need for it.

You can Fiddle around with a working Angular Carto map below.

Why would you do such a thing?

Why are we registering an anonymous function as an Angular factory, then jumping through hoops to get the Angular dependency injector to invoke the registered function? Why not just instantiate and configure the Leaflet map and be done with it? Good question! The example code provided in this post is definitely more complicated than necessary for the task of putting a map on a web page. Dependency injection shines when we have a complicated application with many components that manipulate the map. Over the course of this series, I think you will begin to see what I mean.

Design pattern bingo

  • Dependency injection
  • Angular provider (factory)

Other posts in this series

Revision history

  • 2017-06-06: The example code in this page originally passed the element ID carto-map of the Carto map container as a string literal to the map initialization function L.map. That was poor Angular style and has been rectified.

Wednesday, May 31, 2017

Angular Carto: Introduction

If you work with geospatial data and haven't had a chance to work with Carto, you are missing out. Carto is a platform for creating interactive geospatial visualizations on the web. The canned maps that you can build from the web-based designer are surprisingly powerful, especially if you are already familiar with SQL and CSS. If you are a proficient web programmer, you can use their JavaScript API to customize your map in virtually any way you can imagine.

Carto hits a sweet spot of simplicity and flexibility that suits me nicely. I work on a team whose job is to help our clients make sense of data. The efficiency with which we can produce Carto web applications frees up our time to add value in other areas.

As great as Carto is, one problem that is becoming more pressing is that we're making a lot of the same maps on different data sets. Consequently, we're starting to do a lot of copy-and-paste code reuse. Over the next year, we will be refactoring essential map functionality into reusable components to make setting up and customizing a new map even more efficient. Our framework of choice for this project is Angular.

Angular is a JavaScript framework for developing dynamic web pages. It has a variety of features that interest me, including:

Fundamentally, though, what appeals to me about Angular is that you can use it to encapsulate up a bunch of low-level DOM manipulations into higher level routines that better fit how I think about the behavior of a web page.

As we package the our Carto functionality into Angular components, I will be blogging through the techniques that we used at each step. This introduction will also server as the index for the series as it progresses. Thanks for reading.

Index