This tutorial will introduce the basics of harp.gl, a new and beta 3D map rendering engine for the web.
background
harp.gl is a beta product and we are always looking to improve it with your feedback. For any comments, suggestions, or bug reports, we encourage you to create an issue on the harp.gl GitHub repository
Prerequisites
Laptop with a modern web browser (Chrome, Firefox, Safari, etc.)
Now that you have a base setup of harp.gl, let’s review some key concepts.
MapView
A MapView is the high-level main object in harp.gl. This is the object you’ll add new layers to or customize the style of.
Data source
A data source is a source of data you’ll add to the map. Generally, when working with maps, we’ll be referring to two different types of data sources:
Static data source: a single object, commonly in the geojson format. Think like a .json file or a javascript object.
Tiled data source: a dynamic data source that is broken up into “tiles”. These tiles, referenced by parameters {x}, {y} and {z}, are divided by different locations and zoom levels on the map. Tiled data sources are preferred for large data sets because the map only requests data for the current view of the renderer.
HERE Studio provides a hosting and tiling service we will be using later on in this tutorial. You’ll be able to upload large datasets and Studio will provide and endpoint to access this data using the {x}, {y} and {z} parameters.
GeoJSON
GeoJSON is a popular and common format for storing geo data. harp.gl accepts GeoJSON and we will be using this format throughout the tutorial.
Generally speaking, there are common types of GeoJSON objects: Point, LineString, Polygon.
Note: GeoJSON uses the format [Longitude, Latitude] for the coordinate system, while harp.gl uses [Latitude, Longitude]. Be careful not to get these confused!
harp.gl style sheets and syntax
harp.gl has its own syntax for styling the map. The styling syntax lives inside a json file and contains rules for how the visuals are drawn. For example, in the style sheet, you can specify things like.
Technically, all maps have been lying to you. That’s because it’s difficult to project the spherical earth onto a perfect rectangle.
Side note: for some fun with mercator projections, check out The True Size of, a cool web app to explore the different sizes of countries depending on where they are.
harp.gl provides two different views:
mercator: the classic and one of the most popular flat projections, demo available here.
globe: an accurate representation of earth as a sphere, as seen from space, demo available here.
Mercator
mercator
Globe
|
Now onto the fun part… making some maps! 🌍
Acquire credentials
harp.gl is an open-source and free software project. However, harp.gl needs to be connected to a data source in order to display a map.
HERE Studio, another HERE product, is a service for storing and managing geospatial data. HERE XYZ will provide the vector tile data endpoint and authentication for harp.gl.
After you sign up, you will me taken to a page which says “Project Details” on the left and “Get your credentials:” in the middle (if you have already signed up, when you login, you will be taken to a list of projects, first click the project, then you can continue).
Under the REST section, click “Generate App”, then click “Create API key”. The result should look like:
create-api-key
Copy the key you are given.
You can test it works by appending the app id to the following URL
Please note, you need to confirm your email address, otherwise the key you generate will expire. Go to: https://account.here.com/ and click on “Confirm you email”, if you haven’t already received an email.
Install harp.gl
You can get started with harp.gl on the web with three different methods, ordered by ease of use.
using the yeoman generator
linking a single bundle as a <script> tag in your html page
installing harp.gl as a set of TypeScript modules through npm
In this tutorial, you are free to choose whatever method you please, but for simplicity, we recommend using the harp.gl yeoman generator (method #1), because it gets you coding the the fastest. This tutorial’s code will be based on this method.
Method 1: Using the harp.gl yeoman generator (beginner)
Setting up harp.gl is very simple with the yeoman generator:
mkdir harp.gl-example && cd harp.gl-example
npx -p yo -p @here/generator-harp.gl yo @here/harp.gl, It will ask you to enter
package name
TypeScript or JavaScript
API key you generated above
npm start
open http://localhost:8080/ in a browser of your choice
All going well, you should see a map with buildings in New York.
Further information including pre-requisites are here
Method 2: Linking a single script bundle to your html (intermediate)
In your command line, create a new directory and navigate into it:
mkdir harp.gl-tutorial
cd harp.gl-tutorial
Create two files: index.html and index.js:
touch index.js
touch index.html
Copy and paste the following code into each of the files.
Note also, for those who want to have typings, they are available here: harp and just need to be referenced in the tsconfig.json
Method 3: Install harp.gl modules through npm (advanced)
We have packages available on npm that can be ingested and used immediately, the most commonly used package is: review, this is best used if you already have some way to bundle the code like webpack
There are many examples available which show different features and use cases.
Modify the map
At this point, you should have a map up and running.
Change the map’s center and zoom
You can center the map at whatever point you would like in the world with this method:
// center the camera to New York at zoom level 16
mapView.lookAt({
target: new GeoCoordinates(40.70398928, -74.01319808),
zoomLevel: 16,
});
Try changing the map to where you come from! To find the coordinates of your city: latlong.net
Change the map’s pitch and rotation
Since harp.gl is a 3D engine, you can change the pitch and bearing to view the map at a different perspective.
To change the pitch and bearing with a user event:
on a mac trackpad: two finger hold and drag
on a mouse: right click hold and drag
To programmatically change the map’s perspective you can reuse the lookAt method you are already familiar with (units in degrees):
mapView.lookAt({
tilt: 60,
heading: 90,
});
Note, assuming the target and zoomLevel were already set, subsequent calls to this method only need the tilt and heading, because the other properties are kept the same.
To implement a globe projection, you will need to go to View.ts and change:
import { sphereProjection } from "@here/harp-geoutils";
...
const mapView = new MapView({
... other properties
projection: sphereProjection
});
In index.ts:
//And set it to a view where you can see the whole world:
mapView.lookAt({
zoomLevel: 4,
});
Change the map’s theme
harp.gl comes with three theme variants called Berlin. These Berlin themes were inspired by the vibrant city of Berlin, where most of the development for harp.gl is done.
const map = new MapView({
/*...*/
theme: "resources/berlin_tilezen_night_reduced.json"
});
This works because webpack copies the themes into a resources directory. Alternatively, you can use a url to link to the theme:
const map = new MapView({
/*...*/
theme: "https://unpkg.com/@here/harp-map-theme@latest/resources/berlin_tilezen_night_reduced.json"
});
You can also extend a theme, if you are happy with the base theme but just want to tweak it a bit, see the real time shadow example and search for extends.
If you’d like to modify the stylesheet directly (which, we’ll be doing in the next section), you can simply download the stylesheet and save it in your local directory. For example, if you have a theme dark.json in the resources directory, you can reference it as:
const map = new MapView({
/*...*/
theme: "resources/dark.json"
});
Add a rotating camera
With harp.gl, you can add a rotating camera for a slick visual effect.
Since harp.gl is a vector-based renderer, you can style the map on the client side to look however you like. You can style any map layer as you like to match your organization’s brand guidelines, or just to have fun with your favorite colors.
harp.gl has a companion interactive map styling tool that helps with the map design prototyping process. You can access it here:
Play around with the editor to change the style properties of some of the layers.
harp-map-editor
When you are happy with the map style you’ve created, you can download the style by pressing the download icon in the center vertical toolbar.
Move the file to your project’s resources directory, and update the style source file in the constructor:
const app = new MapView({
/*...*/
theme: "resources/my-beautiful-new-style-made-with-harp-map-editor.json"
});
Add data to the map
At this point, you should have experimented with a few cool tricks with controlling the map and maybe even added a custom style. In this section we’ll learn how to add data to the map.
As mentioned in the Key Concepts section of this tutorial, there are two different types of datasources to add to harp.gl: static and tiled. Static data sources are non-tiled data sources that can be added to the map at once. Tiled data sources come from endpoints that return data only required based on the map’s current viewport.
Add static GeoJSON to the map
Download the file called wireless-hotspots.geojson [LINK]. This dataset is a list of all the wireless hotspot locations throughout Singapore. The dataset is from the Singapore Public Data Website.
Download this file and save it into your project’s resources directory.
Let’s add it to our map using this code (add to the end of index.ts):
// Near the top of the file:
import { VectorTileDataSource, GeoJsonDataProvider } from "@here/harp-vectortile-datasource";
....
// At the bottom of the file:
async function getWirelessHotspots() {
const res = await fetch("resources/wireless-hotspots.geojson");
const data = await res.json();
const dataProvider = new GeoJsonDataProvider("wireless-hotspots", data);
const geoJsonDataSource = new VectorTileDataSource({
dataProvider,
name: "wireless-hotspots",
});
}
getWirelessHotspots();
In the above code block, what we’ve done so far is:
fetch the data wireless-hotspots.geojson from our local directory with the fetch method and await
create a new GeoJsonDataProvider with the following parameters: name of data source (String) and the GeoJSON data object (Object). The GeoJsonDataProvider helps parse and tile the data.
create a new data source from the provider with VectorTileDataSource, the default data source type for harp.gl. We configure it using the following parameters.
dataProvider: the data provider object we created in the previous line
name: the name of the data source for reference
styleSetName: the style group that will be applied to the object. Only if you are referencing a style group from the main map theme. In this example, this is not necessary.
The next steps we need to do before seeing the data on the map are:
adding the data source to the map
styling the data source
setting the camera in the correct location
Add the following code snippet to the getWirelessHotspots function.
With the above code, we’ve binded the styling rules to the datasource. Refresh your browser and take a look at the map, you should see something similar to:
wireless-hotspots
Feel free to add some GeoJSON of your own to the map and style it!
What to make a map of? Here are some places you can access geojson data to get some inspiration!
In this next step, we’ll try adding a different type of data source to the map: tiled geojson from a server.
For simplicity, we’ll be using a sample data source already uploaded to an XYZ Space. However, if you’d like to learn how to upload your own data to an XYZ Space in order to add tiled data to a map, please follow this tutorial: Using the Data Hub CLI.
To add tiled data from an XYZ Space, we’ll be using the VectorTileDataSource class again. VectorTileDataSource can accept a few different types of data sources. For more information, please take a look at: OmvRestClient.ts.
Create a new object from VectorTileDataSource called globalRailroads.
const globalRailroads = new VectorTileDataSource({
baseUrl: "https://xyz.api.here.com/hub/spaces/hUJ4ZHJR/tile/web",
apiFormat: APIFormat.XYZSpace,
authenticationCode: 'ACbs-cqcFI4FlPRLK_VF1co', //Use this token!
});
NOTE: in earlier examples, we were using your own Studio token within authenticationCode. However, in this example, we are accessing a shared dataset, so please use the access token in the example above.
The above code will create and connect to the new data source, but we still need to display it on the map:
// At the top of the index.ts
import{
VectorTileDataSource,
APIFormat,
} from "@here/harp-vectortile-datasource";
// At the bottom of index.ts
async function addRailRoads() {
const globalRailroads = new VectorTileDataSource({
baseUrl: "https://xyz.api.here.com/hub/spaces/hUJ4ZHJR/tile/web",
apiFormat: APIFormat.XYZSpace,
authenticationCode: 'ACbs-cqcFI4FlPRLK_VF1co', //Use this token!
});
await mapView.addDataSource(globalRailroads);
const styles = [
{
when: ["==", ["geometry-type"], "LineString"],
renderOrder: 1000,
technique: "solid-line",
color: "#D73060",
opacity: 1,
metricUnit: "Pixel",
lineWidth: 1,
}
];
globalRailroads.setStyleSet(styles);
mapView.update();
}
addRailRoads();
To add the datasource, we’ll use the command await map.addDataSource(source) (just like what we did before). Note, to use await, you need to wrap it in an async function.
This is where we assign the styling rules (see the section Key Concepts for more information about styling rules). Since our dataset consists only of lines, we will use the geometry type line.
Finally, we set the style using .setStyleSet(styles) and update the map with map.update().
Your map should look something like:
rails no data driven styling
Data driven styling
The globals railroads map looks great, but every line is the same color. It might be a little more interesting to color the features by a certain property.
Let’s take a look at what a sample feature looks like in the dataset:
We can make this map a little bit interesting by styling railways that are open and closed. Instead of every line being the same color, we can style open railways one color and closed a different color.
This is called data driven styling. Styling features based on certain attributes in the dataset.
Let’s slightly modify the styles block from before:
What’s going on here? We are searching for all conditions which are true: - the geometry type is a LineString - the value of status (one of the properties in the data) is Open
When a feature matches that certain criteria, we apply the styling rules As you can see from the code block, we’ve created two styling rules, a rule for when status is open, closed or unkown.
This should give us a map with two different colors, depending on the feature’s status. There aren’t too many closed rails, but they are the ones highlighted in red.
If you are interested in learning more about how to style the map, see the README. The possible expressions are documented in the guide to Style Expressions.
Alternatively, if you prefer to learn by a real example, try playing around with the harp-map-editor.
Add 3D objects to the map
Since harp.gl is built upon three.js, you can add any 3D to the map scene, just like you would with any other three.js scene.
For more information on three.js scenes and objects, please take a look at the three.js manual.
Add a simple cube
Let’s take a look at how to add a basic 3d object: a cube. When the user clicks on the map, we’ll add a cube to the clicked location on the map.
First we will register the click event:
window.addEventListener("click", (evt) => {
//TODO: Create the three.js cube
//TODO: Get the position of the click
//TODO: Add object to the map
});
Then we will create the 3D cube. Note that we use the MapAnchor type which extends the three.js Object3D class by adding additional properties.
const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00fe });
const cube: MapAnchor = new THREE.Mesh(geometry, material);
cube.renderOrder = 100000;
Next, we’ll get the coordinates of the click event and assign it to the cube.
window.addEventListener("click", (evt) => {
//Create the three.js cube
const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00fe });
const cube = new THREE.Mesh(geometry, material);
cube.renderOrder = 100000;
//Get the position of the click
const geoPosition = mapView.getGeoCoordinatesAt(evt.pageX, evt.pageY);
cube.anchor = geoPosition;
//Add object to the map
mapView.mapAnchors.add(cube);
mapView.update();
});
After clicking on the map a few times, you should have a result that looks like:
cubes
Add an animating 3D object
Let’s be honest, the simple cube on the map wasn’t anything special. How about an animating 3D person?
First, we’ll need to add some additional three.js imports to the html. We’ll be adding inflate.min.js and FBXLoader.js, which will help with loading the 3D object.
index.html
<head>
<!-- these scripts go below three.js and harp.js imports -->
<script src="https://threejs.org/examples/js/libs/inflate.min.js"></script>
<script src="https://threejs.org/examples/js/loaders/FBXLoader.js"></script>
</head>
We’ve included a file called dancing.fbx. Make sure to download this into your directory so we can properly load it.
onst clock = new THREE.Clock();
let mixer;
//Initialize the loader
const loader = new THREE.FBXLoader();
window.addEventListener("click", (evt) => {
loader.load("resources/dancing.fbx", (obj) => {
mixer = new THREE.AnimationMixer(obj);
const action = mixer.clipAction(obj.animations[0]);
action.play();
obj.traverse((child) => (child.renderOrder = 10000));
obj.renderOrder = 10000;
obj.rotateX(Math.PI / 2);
obj.scale.set(2.3, 2.3, 2.3);
obj.name = "human";
//Assign the coordinates to the obj
const geoPosition = mapView.getGeoCoordinatesAt(evt.pageX, evt.pageY);
obj.anchor = geoPosition;
mapView.mapAnchors.add(obj);
});
});