Put your map in a ES2015 class

Of late I have been leveraging ES2015 classes for my map components. It’s a good thing to do. It started as a side effect of React, but a lot of the niceties are from ES6 classes independent of React.


JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

MDN

In other words, they’re pretty and they’re functional. Much better than closures, for which I will forever remember watching Douglas Crockford pointing at the hanging () and proclaiming “Dog balls!”.

As a bonus, you’re abstracting the mapping library away from your app. This means if you ever decide to move your app to a different mapping backend (spoiler alert: you will), it will be much easier.

For a Print/Embed project tangent to the Quality of Life project, I need to:

  • Make a map
  • Add a GeoJSON overlay
  • Style the overlay
  • Highlight specific GeoJSON features
  • Report map zoom/center changes to parent if in an iframe

As a ES2015 class, that gets expressed like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import mapboxgl from 'mapbox-gl';

let Map = class {
constructor(mapOptions, geoJSON, breaks, colors, selected = []) {
this.mapOptions = mapOptions;
this.geoJSON = geoJSON;
this.breaks = breaks;
this.colors = colors;
this.selected = selected;
}

// initialize the map
createMap() {
this.map = new mapboxgl.Map(this.mapOptions);
let map = this.map;
let component = this;
map.on('moveend', function() {
let center = map.getCenter();
if (window!=window.top) {
parent.postMessage({"mapzoom": map.getZoom(), "mapcenter": [center.lng, center.lat]},"*");
}
});
map.on('load', function () {
component.neighborhoodInit();
component.neighborhoodStyle();
component.neighborhoodSelected();
});
}

// create neighborhood layers (choropleth, regular outline, highlight outline)
neighborhoodInit() {
let map = this.map;
let geoJSON = this.geoJSON;

this.choroplethSource = map.addSource('neighborhoods', {
'type': 'geojson',
'data': geoJSON
});

map.addLayer({
'id': 'neighborhoods-line',
'type': 'line',
'source': 'neighborhoods',
'layout': {},
'paint': {
'line-color': '#cccccc',
'line-width': 0.5
}
}, 'building');

map.addLayer({
'id': 'neighborhoods-line-selected',
'type': 'line',
'source': 'neighborhoods',
'layout': {},
"filter": ["in", "id", "-999999"],
'paint': {
'line-color': '#FFA400',
'line-width': 5
}
}, 'building');

map.addLayer({
'id': 'neighborhoods-fill',
'type': 'fill',
'source': 'neighborhoods',
'layout': {},
'filter': ['!=', 'choropleth', 'null'],
'paint': {
'fill-opacity': 1
}
}, 'neighborhoods-line');
}

// filter the neighborhood line highlights
neighborhoodSelected() {
let map = this.map;
let selected = this.selected;
let filter;

if (selected.length > 0) {
filter = ["in", "id"];
for (let i = 0; i < selected.length; i++) {
filter.push(selected[i]);
}
} else {
filter = ["in", "id", "-999999"];
}

map.setFilter("neighborhoods-line-selected", filter);
}

// style the neighborhood polygons
neighborhoodStyle() {
let breaks = this.breaks;
let colors = this.colors;
let map = this.map;
let fillColor = {
property: 'choropleth',
stops: [
[breaks[1], colors[0]],
[breaks[2], colors[1]],
[breaks[3], colors[2]],
[breaks[4], colors[3]],
[breaks[5], colors[4]]
]
};
map.setPaintProperty("neighborhoods-fill", 'fill-color', fillColor);
}

};

export default Map;

And I can instantiate it like this:

1
2
3
// Create map
let map = new Map(mapOptions, geojson, breaksArray, colorsArray, selectedArray);
map.createMap();

And…done. What if I want to change the breaks?

1
2
map.breaks = newBreaksArray;
map.neighborhoodStyle();

It’s a simple and clean way to manage the map, and I could (relatively) easily swap out GL JS for Leaflet and only have to futz with this one class.

Note you’ll have to use something like Babel to transform the JavaScript to something browsers can understand. But with classes and a bunch of other ES2015 features at your fingertips, you should be using Babel anyway.