This article is part of a series of tutorials that show you how to develop your first application:
Goal
On the default LuciadRIA map, a user can navigate and zoom using the mouse.
The MapNavigator
class in the API also provides utility functions to navigate the map programmatically.
In this tutorial, you find out what you can do with MapNavigator
:
This tutorial explains the most common functions.
For a complete list of functions and detailed information about these functions, see the |
Starting point
In this tutorial, we take the code from
Learn how to load and display vector data on a LuciadRIA map
tutorial as the starting point, and expand it with new MapNavigator
functions.
Fitting to a bounding box
Fitting to a pre-defined area
If you want to fit to an area on the map, you can use the fit
function.
For example, after loading your application and adding your layers, you want to fit the map to an area that shows data of
interest.
In such a case, you can fit your map to that area using this code:
Bounds
import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
import {createBounds} from "@luciad/ria/shape/ShapeFactory.js";
const reference = getReference("CRS:84");
const bounds = createBounds(reference,
[-158.22554883458136, 98.56866079487072, 20.627551367182754, 40.905334466412214]);
map.mapNavigator.fit({
bounds: bounds,
animate: true
});
The code ensures that the map fits to the USA.
If you set the animate
property to true, you add an animation effect to the fitting operation, and the fit
function returns a Promise
.
You can chain animations with that Promise
: the Promise
is resolved when the animation is complete. At that point, you can start a new animation.
If the animation is interrupted, for example when a user starts a MapNavigator
operation while the animation is in progress, the Promise
is rejected with a CancelError
.
Even if animate
is false, you still need to wait for the returned Promise
to resolve if you want
to be sure that the map has navigated to the target position.
Fitting to a layer
The preceding example fits the map to a pre-defined area, and uses a statically defined Bounds
.
If you don’t know in which area the interesting data is visible, though, you can fit on the layer bounds instead. You can
use the same fit
function, but with the layer bounds this time.
// Fitting to the bounds of a raster layer
map.mapNavigator.fit({
bounds: layer.model.bounds,
animate: true
});
Fitting to a vector layer
LuciadRIA calculates the boundary of a layer dynamically. Whenever you add an object to the layer, or remove an object from
the layer, LuciadRIA re-calculates the layer boundary.
We use the QueryFinished
event of the FeatureLayer
's WorkingSet
to make sure that the data in the layer is loaded, and the boundary of the layer is calculated:
import {FeatureLayer} from "@luciad/ria/view/feature/FeatureLayer.js";
import {FeatureModel} from "@luciad/ria/model/feature/FeatureModel.js";
import {LayerType} from "@luciad/ria/view/LayerType.js";
import {Map} from "@luciad/ria/view/Map.js";
import {WFSFeatureStore} from "@luciad/ria/model/store/WFSFeatureStore.js";
export function createCitiesLayer(map: Map) {
const url = "https://sampleservices.luciad.com/wfs";
const featureTypeName = "cities";
// Create a WFS store
return WFSFeatureStore.createFromURL(url, featureTypeName).then(
(wfsSore: WFSFeatureStore) => {
// Create a model based on the created store
const featureModel = new FeatureModel(wfsSore);
// Create a feature layer
const featureLayer = new FeatureLayer(featureModel, {
label: "US Cities",
layerType: LayerType.STATIC,
selectable: true
});
// Add a layer to the map
map.layerTree.addChild(featureLayer);
return featureLayer;
});
}
Bounds
is ready
createCitiesLayer(map).then(function(citiesLayer) {
const queryFinishedHandle = citiesLayer.workingSet.on("QueryFinished", function() {
if (citiesLayer.bounds) {
map.mapNavigator.fit({
bounds: citiesLayer.bounds,
animate: true
});
}
queryFinishedHandle.remove();
});
});
Panning to a selected feature
The pan function is another function of MapNavigator
. You use it to change the center of the map, or shift the map in a certain direction.
You can use the pan function with a point in the model, map, or view reference.
With the MapNavigatorPanOptions
, you can configure the pan
operation.
Note that the MapNavigatorPanOptions
object also has an animate
property to enable the animation effect.
This example code pans the map to the selected city when a user selects a city object. To detect changes in the object selection, we must first make the layer selectable:
// Create a feature layer
const featureLayer = new FeatureLayer(featureModel, {
label: "US Cities",
layerType: LayerType.STATIC,
selectable: true
});
We then add an event listener to the SelectionChanged
event of the map.
If the user selects a new city on the map, we pan the map to that city.
main.ts
file updated with the action to take when a user clicks the zoom-in or zoom-out buttons
map.on("SelectionChanged", function(selectionChangeEvent) {
const selectedCity = selectionChangeEvent.selectionChanges[0].selected[0];
if (selectedCity && selectedCity.shape && selectedCity.shape.focusPoint) {
map.mapNavigator.pan({
animate: true,
targetLocation: selectedCity.shape.focusPoint
});
}
});
Zooming in or out
If you want to zoom in or zoom out on the map, or if you want to change the scale of the map, you can use the zoom
function.
You can use a single zoom function for both zoom-in and zoom-out operations.
Just like the methods in the MapNavigator
class, zoom
works with a MapNavigatorZoomOptions
object.
In the example, we add two new buttons to the map. The map zooms in or out, depending on the button that the user clicks. When you call the zoom functions, you need to specify the target scale or zoom factor. In this example, we use the zoom factor. The zoom factor determines whether to zoom in or out on the map. If the zoom factor value is greater than 1, LuciadRIA zooms in on the map. If it isn’t, LuciadRIA zooms the map out.
First, we need to add zoom-in and zoom-out buttons to the index.html
file.
<div>
element, updated with new <button>
elements for the zoom-in and zoom-out operations
<div id="map">
<button id="zoom-in">+</button>
<button id="zoom-out">-</button>
</div>
Now we add some CSS to main.css
to make these buttons appear on the right of the map:
#zoom-in {
position: absolute;
top: 10px;
right: 10px;
z-index: 1;
font-size: 30px;
padding: 5px;
width: 30px;
}
#zoom-out {
position: absolute;
top: 75px;
right: 10px;
z-index: 1;
font-size: 30px;
padding: 5px;
width: 30px;
}
Now, we can add the logic that we want to run when a user clicks the new buttons.
main.ts
file updated with the action to take when the user clicks the zoom-in
or zoom-out
buttons
// Add click event listeners to the zoom in and zoom out buttons
const zoomInButton = document.getElementById("zoom-in")!;
zoomInButton.addEventListener("click", function() {
map.mapNavigator.zoom({
factor: 1.25,
animate: true
});
});
const zoomOutButton = document.getElementById("zoom-out")!;
zoomOutButton.addEventListener("click", function() {
map.mapNavigator.zoom({
factor: 0.75,
animate: true
});
});
Full code
<!doctype html>
<html lang="en">
<head>
<title>Hello world</title>
<link href="main.css" rel="stylesheet"/>
</head>
<body>
<h1>Hello world</h1>
<div id="map">
<button id="zoom-in">+</button>
<button id="zoom-out">-</button>
</div>
import {WMSTileSetModel} from "@luciad/ria/model/tileset/WMSTileSetModel.js";
import {RasterTileSetLayer} from "@luciad/ria/view/tileset/RasterTileSetLayer.js";
import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
import {createBounds} from "@luciad/ria/shape/ShapeFactory.js";
import {createCitiesLayer} from "./vectorData.js";
import {WebGLMap} from "@luciad/ria/view/WebGLMap.js";
//Create a new map instance, and display it in the div with the "map" id
const map = new WebGLMap("map");
//Add some WMS data to the map
const server = "https://sampleservices.luciad.com/wms";
const dataSetName = "4ceea49c-3e7c-4e2d-973d-c608fb2fb07e";
WMSTileSetModel.createFromURL(server, [{layer: dataSetName}])
.then(model => {
const layer = new RasterTileSetLayer(model);
map.layerTree.addChild(layer, "bottom");
if (layer.model && layer.model.bounds) {
// Fitting to the bounds of a raster layer
map.mapNavigator.fit({
bounds: layer.model.bounds,
animate: true
});
}
});
createCitiesLayer(map).then(function(citiesLayer) {
const queryFinishedHandle = citiesLayer.workingSet.on("QueryFinished", function() {
if (citiesLayer.bounds) {
map.mapNavigator.fit({
bounds: citiesLayer.bounds,
animate: true
});
}
queryFinishedHandle.remove();
});
});
map.on("SelectionChanged", function(selectionChangeEvent) {
const selectedCity = selectionChangeEvent.selectionChanges[0].selected[0];
//@ts-ignore
if (selectedCity && selectedCity.shape && selectedCity.shape.focusPoint) {
map.mapNavigator.pan({
animate: true,
//@ts-ignore
targetLocation: selectedCity.shape.focusPoint
});
}
});
const reference = getReference("CRS:84");
const bounds = createBounds(reference,
[-158.22554883458136, 98.56866079487072, 20.627551367182754, 40.905334466412214]);
map.mapNavigator.fit({
bounds: bounds,
animate: true
});
// Add click event listeners to the zoom in and zoom out buttons
const zoomInButton = document.getElementById("zoom-in")!;
zoomInButton.addEventListener("click", function() {
map.mapNavigator.zoom({
factor: 1.25,
animate: true
});
});
const zoomOutButton = document.getElementById("zoom-out")!;
zoomOutButton.addEventListener("click", function() {
map.mapNavigator.zoom({
factor: 0.75,
animate: true
});
});
#map {
position: relative;
width: 100%;
height: 620px;
overflow: hidden;
border: 1px solid grey;
}
#zoom-in {
position: absolute;
top: 10px;
right: 10px;
z-index: 1;
font-size: 30px;
padding: 5px;
width: 30px;
}
#zoom-out {
position: absolute;
top: 75px;
right: 10px;
z-index: 1;
font-size: 30px;
padding: 5px;
width: 30px;
}
import {FeatureLayer} from "@luciad/ria/view/feature/FeatureLayer.js";
import {FeatureModel} from "@luciad/ria/model/feature/FeatureModel.js";
import {LayerType} from "@luciad/ria/view/LayerType.js";
import {Map} from "@luciad/ria/view/Map.js";
import {WFSFeatureStore} from "@luciad/ria/model/store/WFSFeatureStore.js";
export function createCitiesLayer(map: Map) {
const url = "https://sampleservices.luciad.com/wfs";
const featureTypeName = "cities";
// Create a WFS store
return WFSFeatureStore.createFromURL(url, featureTypeName).then(
(wfsSore: WFSFeatureStore) => {
// Create a model based on the created store
const featureModel = new FeatureModel(wfsSore);
// Create a feature layer
const featureLayer = new FeatureLayer(featureModel, {
label: "US Cities",
layerType: LayerType.STATIC,
selectable: true
});
// Add a layer to the map
map.layerTree.addChild(featureLayer);
return featureLayer;
});
}