Rendering meteorites with Canvas 2D
In OpenLayers 6, each layer in a map has an independent renderer. Previously, all layer rendering was managed by a single map renderer and depended on a single rendering strategy. So prior to OpenLayers 6, you needed to choose between Canvas 2D or WebGL rendering for all layers. In OpenLayers 6, you can have a map composed of layers with different rendering strategies. For example, you can render some layers with a Canvas 2D renderer and others with a WebGL renderer.
The ol/layer/Vector
class uses Canvas 2D to render points, lines, or polygons. This layer has a full featured renderer with a ton of flexibility in feature styling. For very large numbers of features, WebGL is a more appropriate technology. In this module, we'll start by using Canvas 2D to render 45,000 meteorite locations and then migrate the example to WebGL.
First, edit your index.html
so we're ready to render a full page map:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>OpenLayers</title>
<style>
@import "node_modules/ol/ol.css";
</style>
<style>
html, body, #map-container {
margin: 0;
height: 100%;
width: 100%;
font-family: sans-serif;
}
</style>
</head>
<body>
<div id="map-container"></div>
<script src="./main.js" type="module"></script>
</body>
</html>
Next we'll fetch and parse data from a local CSV file, add the resulting features to a vector source, and render it on the map with a vector layer.
The meteorites.csv
file has data in it that looks like this:
name,mass,year,reclat,reclong
Aachen,21,1880,50.775000,6.083330
...
The first line of the file is a header line that we'll skip over when parsing. Subsequent lines have the name of the site, the mass of the meteorite, the year of impact, the latitude, and the longitude separated by commas. We'll use an XMLHttpRequest
client to fetch the data and write a short function to parse lines in the file as features with point geometries.
Update your main.js
to load and render a local CSV file with data representing meteorite impacts:
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import TileLayer from 'ol/layer/Tile';
import VectorLayer from 'ol/layer/Vector';
import {Map, View} from 'ol';
import {Stamen, Vector as VectorSource} from 'ol/source';
import {fromLonLat} from 'ol/proj';
const source = new VectorSource();
const client = new XMLHttpRequest();
client.open('GET', './data/meteorites.csv');
client.onload = function () {
const csv = client.responseText;
const features = [];
let prevIndex = csv.indexOf('\n') + 1; // scan past the header line
let curIndex;
while ((curIndex = csv.indexOf('\n', prevIndex)) != -1) {
const line = csv.substr(prevIndex, curIndex - prevIndex).split(',');
prevIndex = curIndex + 1;
const coords = fromLonLat([parseFloat(line[4]), parseFloat(line[3])]);
if (isNaN(coords[0]) || isNaN(coords[1])) {
// guard against bad data
continue;
}
features.push(
new Feature({
mass: parseFloat(line[1]) || 0,
year: parseInt(line[2]) || 0,
geometry: new Point(coords),
})
);
}
source.addFeatures(features);
};
client.send();
const meteorites = new VectorLayer({
source: source,
});
new Map({
target: 'map-container',
layers: [
new TileLayer({
source: new Stamen({
layer: 'toner',
}),
}),
meteorites,
],
view: new View({
center: [0, 0],
zoom: 2,
}),
});
After fetching and parsing the data, features are added to a vector source. This source is rendered in a vector layer over a tile layer. We aren't doing any custom styling of the features here, the point is just to see how it feels to use the map with 45,000 features rendered with Canvas 2D.